On LRUCache a read promotes the entry: in, get(), cache[key] and setdefault() move it to the back of the internal list. Doing that while an iterator is alive leaves the iterator on a relinked list, and the process dies with an access violation.
import cachebox
cache = cachebox.LRUCache(10)
for key in ('a', 'b', 'c'):
cache[key] = 1
it = cache.keys()
print('promoting "a" with a lookup ...', flush=True)
assert 'a' in cache
print('walking the live iterator ...', flush=True)
print('got:', list(it))
What list(it) gives after that promotion, by version:
|
6.2.1 and 6.2.2 |
6.2.3 and 6.2.4 |
with the fix |
list(it) |
got: ['a'], two of the three keys silently vanish |
the process dies with an access violation |
RuntimeError, same as after an insert |
cache.get('a'), cache['a'] and cache.setdefault('a') behave the same as 'a' in cache. Checked on Windows, CPython 3.12.
The missing bump is old, only its price changed. On 6.2.1 and 6.2.2 the iterator follows the promoted entry to the back of the list and ends right after it, so keys quietly vanish. The linked list rework from #66 made the same walk die instead, which is actually a step forward: a loud failure gets noticed, while the quiet one had been eating keys unnoticed all along.
An insert under a live iterator raises RuntimeError. A promoting read rewires the same list, it just does not bump the generation, so the iterator never notices.
On
LRUCachea read promotes the entry:in,get(),cache[key]andsetdefault()move it to the back of the internal list. Doing that while an iterator is alive leaves the iterator on a relinked list, and the process dies with an access violation.What
list(it)gives after that promotion, by version:list(it)got: ['a'], two of the three keys silently vanishcache.get('a'),cache['a']andcache.setdefault('a')behave the same as'a' in cache. Checked on Windows, CPython 3.12.The missing bump is old, only its price changed. On 6.2.1 and 6.2.2 the iterator follows the promoted entry to the back of the list and ends right after it, so keys quietly vanish. The linked list rework from #66 made the same walk die instead, which is actually a step forward: a loud failure gets noticed, while the quiet one had been eating keys unnoticed all along.
An insert under a live iterator raises RuntimeError. A promoting read rewires the same list, it just does not bump the generation, so the iterator never notices.