Skip to content

Commit

Permalink
Reduce number of decorator lock/unlock operations in case of cache miss.
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Kemmer committed Feb 20, 2025
1 parent 2252883 commit 72b4a32
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 30 deletions.
35 changes: 17 additions & 18 deletions src/cachetools/_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,20 @@ def _cached_locked_info(func, cache, key, lock, info):
def wrapper(*args, **kwargs):
nonlocal hits, misses
k = key(*args, **kwargs)
try:
with lock:
with lock:
try:
result = cache[k]
hits += 1
return result
except KeyError:
with lock:
except KeyError:
misses += 1
v = func(*args, **kwargs)
# in case of a race, prefer the item already in the cache
try:
with lock:
with lock:
try:
# in case of a race, prefer the item already in the cache
return cache.setdefault(k, v)
except ValueError:
return v # value too large
except ValueError:
return v # value too large

def cache_clear():
nonlocal hits, misses
Expand Down Expand Up @@ -87,18 +86,18 @@ def cache_clear():
def _cached_locked(func, cache, key, lock):
def wrapper(*args, **kwargs):
k = key(*args, **kwargs)
try:
with lock:
with lock:
try:
return cache[k]
except KeyError:
pass # key not found
except KeyError:
pass # key not found
v = func(*args, **kwargs)
# in case of a race, prefer the item already in the cache
try:
with lock:
with lock:
try:
# in case of a race, prefer the item already in the cache
return cache.setdefault(k, v)
except ValueError:
return v # value too large
except ValueError:
return v # value too large

def cache_clear():
with lock:
Expand Down
20 changes: 8 additions & 12 deletions tests/test_cached.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,32 +165,28 @@ def test_decorator_info(self):
self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))

def test_decorator_lock_info(self):
# hit: lock.count += 1
# miss: lock.count += 3 (get, update missed, set)
# info: lock.count += 1
# clear: lock.count += 1
cache = self.cache(2)
lock = CountedLock()
wrapper = cachetools.cached(cache, lock=lock, info=True)(self.func)
self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))
self.assertEqual(lock.count, 1)
self.assertEqual(wrapper(0), 0)
self.assertEqual(lock.count, 4)
self.assertEqual(lock.count, 3)
self.assertEqual(wrapper.cache_info(), (0, 1, 2, 1))
self.assertEqual(lock.count, 5)
self.assertEqual(lock.count, 4)
self.assertEqual(wrapper(1), 1)
self.assertEqual(lock.count, 8)
self.assertEqual(lock.count, 6)
self.assertEqual(wrapper.cache_info(), (0, 2, 2, 2))
self.assertEqual(lock.count, 9)
self.assertEqual(lock.count, 7)
self.assertEqual(wrapper(0), 0)
self.assertEqual(lock.count, 10)
self.assertEqual(lock.count, 8)
self.assertEqual(wrapper.cache_info(), (1, 2, 2, 2))
self.assertEqual(lock.count, 11)
self.assertEqual(lock.count, 9)
wrapper.cache_clear()
self.assertEqual(lock.count, 12)
self.assertEqual(lock.count, 10)
self.assertEqual(len(cache), 0)
self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))
self.assertEqual(lock.count, 13)
self.assertEqual(lock.count, 11)

def test_zero_size_cache_decorator(self):
cache = self.cache(0)
Expand Down

0 comments on commit 72b4a32

Please sign in to comment.