mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 03:00:18 +02:00
App data: serialise restores of a package with a lock beside its data
Two clients restoring the same package could each swap directories and then delete the other's pre-restore copy. The swap and retention cleanup now run under flock on .<package>.restore.lock. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a7261b1601
commit
6c41a341e5
2 files changed
+49
-11
No files matched your search
@@ -140,6 +140,31 @@ class BackupTests(unittest.TestCase):
|
||||
self.assertFalse((source / 'lib').exists() or (source / 'lib').is_symlink())
|
||||
self.assertEqual((source / 'files/save-link').read_bytes(), b'save')
|
||||
|
||||
def test_concurrent_restores_of_a_package_are_serialised(self):
|
||||
import fcntl, threading
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
(root / PKG).mkdir()
|
||||
(root / PKG / 'save').write_bytes(b'backup')
|
||||
archive = io.BytesIO()
|
||||
REMOTE['backup'](root, PKG, META['instance'], archive)
|
||||
(root / PKG / 'save').write_bytes(b'current')
|
||||
first = REMOTE['restore'](root, PKG, META['instance'], io.BytesIO(archive.getvalue()))['previous']
|
||||
# Another client's restore is mid-swap: it holds the package lock.
|
||||
fd = os.open(str(root / ('.' + PKG + '.restore.lock')), os.O_RDWR | os.O_CREAT, 0o600)
|
||||
fcntl.flock(fd, fcntl.LOCK_EX)
|
||||
results = []
|
||||
second = threading.Thread(target=lambda: results.append(
|
||||
REMOTE['restore'](root, PKG, META['instance'], io.BytesIO(archive.getvalue()))))
|
||||
second.start()
|
||||
second.join(.5)
|
||||
self.assertTrue(second.is_alive()) # waiting, so it can't swap or delete the other's copy
|
||||
self.assertEqual(sorted(root.glob('.' + PKG + '.before-restore-*')), [Path(first)])
|
||||
os.close(fd)
|
||||
second.join(5)
|
||||
self.assertEqual(sorted(root.glob('.' + PKG + '.before-restore-*')), [Path(results[0]['previous'])])
|
||||
self.assertEqual((root / PKG / 'save').read_bytes(), b'backup')
|
||||
|
||||
def test_restore_keeps_only_latest_previous_copy(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
|
||||
Reference in new issue
Block a user