Refuse to run a second eris instance on the same directory

flock on .eris/lock prevents two eris processes racing the cache, which
previously produced a mixed-format summary (int-list pages) and crashed
sync_with_filesystem. The lock is acquired before manage_cache, the lock
file survives checksum-mismatch cleanup, and the holder's PID is written
to the lock file so a refused instance can report it.
This commit is contained in:
Andrew Hamilton 2026-08-04 09:25:49 +10:00
parent 980ee7071c
commit 329f500c8e
2 changed files with 115 additions and 13 deletions

View file

@ -201,5 +201,60 @@ class SummarySyncWithFilesystemTestCase(unittest.TestCase):
id(self.summary._entries[3])) # zoo
self.assertTrue(self.jobs_added_event.is_set())
class CacheLockTestCase(unittest.TestCase):
def setUp(self):
self.temp_dir = tempfile.mkdtemp()
self.cache_path = os.path.join(self.temp_dir, tools.CACHE_PATH)
def tearDown(self):
shutil.rmtree(self.temp_dir)
def test_acquire_lock_conflict_and_release(self):
lock_fd = __main__.acquire_lock(self.cache_path)
lock_path = os.path.join(self.cache_path, "lock")
self.assertTrue(os.path.exists(lock_path))
with open(lock_path) as lock_file:
self.assertEqual(lock_file.read(), str(os.getpid()))
with self.assertRaises(__main__.LockError) as error:
__main__.acquire_lock(self.cache_path)
self.assertEqual(error.exception.holder_pid, str(os.getpid()))
os.close(lock_fd)
lock_fd = __main__.acquire_lock(self.cache_path)
os.close(lock_fd)
def test_manage_cache_preserves_lock_on_checksum_mismatch(self):
lock_fd = __main__.acquire_lock(self.cache_path)
lock_path = os.path.join(self.cache_path, "lock")
with open(os.path.join(self.cache_path, "source_checksum"), "w") as checksum_file:
checksum_file.write("stale-checksum")
stale_pickle = os.path.join(self.cache_path, "summary.pickle")
stale_dir = os.path.join(self.cache_path, "summary_dir")
_touch(stale_pickle)
os.mkdir(stale_dir)
original_checksum = __main__.source_checksum
__main__.source_checksum = lambda: "new-checksum"
try:
__main__.manage_cache(self.temp_dir)
finally:
__main__.source_checksum = original_checksum
os.close(lock_fd)
self.assertTrue(os.path.exists(lock_path))
self.assertFalse(os.path.exists(stale_pickle))
self.assertFalse(os.path.exists(stale_dir))
with open(os.path.join(self.cache_path, "source_checksum")) as checksum_file:
self.assertEqual(checksum_file.read(), "new-checksum")
self.assertTrue(os.path.exists(os.path.join(self.cache_path, ".gitignore")))
def test_manage_cache_first_run_keeps_lock(self):
lock_fd = __main__.acquire_lock(self.cache_path)
os.close(lock_fd)
__main__.manage_cache(self.temp_dir)
self.assertTrue(os.path.exists(os.path.join(self.cache_path, "lock")))
with open(os.path.join(self.cache_path, "source_checksum")) as checksum_file:
self.assertEqual(checksum_file.read(), __main__.source_checksum())
self.assertTrue(os.path.exists(os.path.join(self.cache_path, ".gitignore")))
if __name__ == "__main__":
golden.main()