diff --git a/src/c3nav/mapdata/utils/cache/local.py b/src/c3nav/mapdata/utils/cache/local.py index e9866cf6..a1b9c1ae 100644 --- a/src/c3nav/mapdata/utils/cache/local.py +++ b/src/c3nav/mapdata/utils/cache/local.py @@ -1,4 +1,5 @@ from collections import OrderedDict +from contextvars import ContextVar from django.core.cache import cache from django.conf import settings @@ -10,42 +11,46 @@ class NoneFromCache: class LocalCacheProxy: # django cache, buffered using a LRU cache - # only usable for stuff that never changes, obviously - # todo: ensure thread-safety, compatible with async + daphne etc + # only usable for stuff that never needs to know about changes made by other cache clients, obviously def __init__(self, maxsize=128): self._maxsize = maxsize self._mapupdate = None - self._items = OrderedDict() + self._items: ContextVar[OrderedDict] = ContextVar("cache items") + # do not use a default of a dict, this can lead to same instance in different contexts + # we don't particularly care about this for LocalCacheProxy, + # but we DEFINITELY care about this for the local request cache. + # Most importantly, this is why the clear function always sets a new dictionary to be extra sure. + self.clear() def get(self, key, default=None): if self._mapupdate is None: self._check_mapupdate() try: # first check out cache - result = self._items[key] + result = self._items.get()[key] except KeyError: # not in our cache result = cache.get(key, default=NoneFromCache) if result is not NoneFromCache: - self._items[key] = result + self._items.get()[key] = result self._prune() else: result = default else: - self._items.move_to_end(key, last=True) + self._items.get().move_to_end(key, last=True) return result def _prune(self): # remove old items - while len(self._items) > self._maxsize: - self._items.pop(next(iter(self._items.keys()))) + while len(self._items.get()) > self._maxsize: + self._items.get().pop(next(iter(self._items.get().keys()))) def _check_mapupdate(self): # todo: thanks to enable_globally() we shouldn't need this any more from c3nav.mapdata.models import MapUpdate mapupdate = MapUpdate.current_cache_key() if self._mapupdate != mapupdate: - self._items = OrderedDict() + self.clear() self._mapupdate = mapupdate enabled = False @@ -61,11 +66,11 @@ class LocalCacheProxy: self._check_mapupdate() cache.set(key, value, expire) if LocalCacheProxy.enabled: - self._items[key] = value + self._items.get()[key] = value self._prune() def clear(self): - self._items.clear() + self._items.set(OrderedDict()) class RequestLocalCacheProxy(LocalCacheProxy):