Skip to content

Commit c69e671

Browse files
author
Thomas Kemmer
committed
Improve handling of race conditions in "cachetools.func" decorators.
1 parent 72b4a32 commit c69e671

File tree

2 files changed

+50
-3
lines changed

2 files changed

+50
-3
lines changed

src/cachetools/_decorators.py

+47
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,51 @@
11
"""Extensible memoizing decorator helpers."""
22

33

4+
def _cached_cond_info(func, cache, key, cond, info):
5+
hits = misses = 0
6+
pending = set()
7+
8+
def wrapper(*args, **kwargs):
9+
nonlocal hits, misses
10+
k = key(*args, **kwargs)
11+
with cond:
12+
cond.wait_for(lambda: k not in pending)
13+
try:
14+
result = cache[k]
15+
hits += 1
16+
return result
17+
except KeyError:
18+
pass
19+
misses += 1
20+
pending.add(k)
21+
try:
22+
v = func(*args, **kwargs)
23+
try:
24+
with cond:
25+
cache[k] = v
26+
except ValueError:
27+
pass # value too large
28+
return v
29+
finally:
30+
with cond:
31+
pending.remove(k)
32+
cond.notify_all()
33+
34+
def cache_clear():
35+
nonlocal hits, misses
36+
with cond:
37+
cache.clear()
38+
hits = misses = 0
39+
40+
def cache_info():
41+
with cond:
42+
return info(hits, misses)
43+
44+
wrapper.cache_clear = cache_clear
45+
wrapper.cache_info = cache_info
46+
return wrapper
47+
48+
449
def _cached_locked_info(func, cache, key, lock, info):
550
hits = misses = 0
651

@@ -139,6 +184,8 @@ def _cached_wrapper(func, cache, key, lock, info):
139184
wrapper = _uncached_info(func, info)
140185
elif lock is None:
141186
wrapper = _cached_unlocked_info(func, cache, key, info)
187+
elif hasattr(lock, "wait_for") and hasattr(lock, "notify_all"):
188+
wrapper = _cached_cond_info(func, cache, key, lock, info)
142189
else:
143190
wrapper = _cached_locked_info(func, cache, key, lock, info)
144191
else:

src/cachetools/func.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
import time
88

99
try:
10-
from threading import RLock
10+
from threading import Condition
1111
except ImportError: # pragma: no cover
12-
from dummy_threading import RLock
12+
from dummy_threading import Condition
1313

1414
from . import FIFOCache, LFUCache, LRUCache, MRUCache, RRCache, TTLCache
1515
from . import cached
@@ -28,7 +28,7 @@ def maxsize(self):
2828
def _cache(cache, maxsize, typed):
2929
def decorator(func):
3030
key = keys.typedkey if typed else keys.hashkey
31-
wrapper = cached(cache=cache, key=key, lock=RLock(), info=True)(func)
31+
wrapper = cached(cache=cache, key=key, lock=Condition(), info=True)(func)
3232
wrapper.cache_parameters = lambda: {"maxsize": maxsize, "typed": typed}
3333
return wrapper
3434

0 commit comments

Comments
 (0)