fix: key the arcade sha1 cache by the database

The cache compared id(db), so a database freed and replaced at the same
address answered with the first one's Arcade set.
This commit is contained in:
Abdessamad Derraz committed 2026-11-06 03:15:07 +01:00
1 parent 55ac3162f5
commit 8c3e653234
2 files changed
+34 -3

No files matched your search

+5 -3
View File
@@ -372,19 +372,21 @@ def _in_arcade(sha1: str, db: dict) -> bool:
return sha1 in _arcade_suffix_sha1s(db)
_suffix_cache: list[tuple[int, frozenset[str]] | None] = [None]
_suffix_cache: list[tuple[dict, frozenset[str]] | None] = [None]
def _arcade_suffix_sha1s(db: dict) -> frozenset[str]:
# Holding the database itself, never id(db): a database freed and
# replaced at the same address answered with the first one's set.
cached = _suffix_cache[0]
if cached is not None and cached[0] == id(db):
if cached is not None and cached[0] is db:
return cached[1]
found = frozenset(
sha1s[0]
for tail, sha1s in db.get("indexes", {}).get("by_path_suffix", {}).items()
if tail.startswith("Arcade/") and len(sha1s) == 1
)
_suffix_cache[0] = (id(db), found)
_suffix_cache[0] = (db, found)
return found
+29
View File
@@ -503,5 +503,34 @@ class NoTooltipInsideAName(unittest.TestCase):
self.assertIn('>BIOS</abbr>', marked)
class TheArcadeCacheHoldsItsDatabase(unittest.TestCase):
"""The set of sha1s filed under Arcade/ was cached by id(db): a database
freed and replaced at the same address answered with the first one's set,
and an arcade card of the second landed in another section."""
@staticmethod
def _db(sha1: str) -> dict:
return {"files": {}, "indexes": {"by_path_suffix": {"Arcade/x.zip": [sha1]}}}
def test_a_new_database_at_a_reused_address_is_read_again(self):
saved = siterender._suffix_cache[0]
self.addCleanup(siterender._suffix_cache.__setitem__, 0, saved)
first = self._db("a" * 40)
self.assertTrue(siterender._in_arcade("a" * 40, first))
address = id(first)
del first
# CPython hands a freed dict's address to a later one of its size,
# unless the cache still holds the first database.
for _ in range(1000):
second = self._db("b" * 40)
if id(second) == address:
break
del second
else:
second = self._db("b" * 40)
self.assertTrue(siterender._in_arcade("b" * 40, second))
self.assertFalse(siterender._in_arcade("a" * 40, second))
if __name__ == "__main__":
unittest.main()