fix: key the manifest hash cache by its check

The cache of manifest hashes was keyed by the database's id without
holding the database, so a freed database replaced at the same
address got another scan's verified hashes back. The key now carries
the sha1 that database expects for the path.
This commit is contained in:
Abdessamad Derraz committed 2026-10-18 04:13:32 +02:00
1 parent 5a4432742c
commit ef0023d78c
2 files changed
+15 -2

No files matched your search

+5 -2
View File
@@ -3236,13 +3236,16 @@ def _stable_hashes(local_path: str, db: dict) -> dict:
refused rather than published.
"""
stat = os.stat(local_path)
key = (os.path.normpath(local_path), stat.st_size, stat.st_mtime_ns, id(db))
expected = _db_sha1_by_path(db).get(os.path.normpath(local_path))
# Keyed by what was checked, the sha1 this database expects there, never
# by id(db): a database freed and replaced at the same address answered
# with hashes verified against the first one.
key = (os.path.normpath(local_path), stat.st_size, stat.st_mtime_ns, expected)
known = _STABLE_HASHES.get(key)
if known is not None:
# Verified against the scan once: twelve manifests name a file up
# to a dozen times, and rehashing it each time spent 163 s of 218.
return known
expected = _db_sha1_by_path(db).get(os.path.normpath(local_path))
hashes = compute_hashes(local_path, _MANIFEST_HASHES)
for _attempt in range(2):
if expected is None or hashes["sha1"] == expected:
+10
View File
@@ -212,6 +212,16 @@ class ManifestHashesMatchTheScan(unittest.TestCase):
generate_pack._stable_hashes(str(path), bad)
self.assertEqual(generate_pack._stable_hashes(str(path), {"files": {}})["sha1"], real)
def test_the_cache_is_keyed_by_what_was_checked(self):
"""Keyed by id(db), a database freed and replaced at the same address
got hashes verified against the first one back, unchecked."""
source = (REPO_ROOT / "scripts" / "generate_pack.py").read_text(encoding="utf-8")
body = source[source.index("def _stable_hashes("):]
body = body[: body.index("\ndef ")]
key_line = next(line for line in body.splitlines() if line.strip().startswith("key ="))
self.assertNotIn("id(", key_line)
self.assertIn("expected", key_line)
class RetriesWait(unittest.TestCase):
def test_a_server_error_is_retried_after_the_delay_it_asks(self):