編集:
私が思いついた最終的な解決策は、PyPI パッケージにコンパイルされています: https://pypi.org/project/django-request-cache/
編集 2016-06-15:
私はこの問題に対する非常に簡単な解決策を発見し、最初からこれがどれほど簡単であったかを理解していなかったことに顔をしかめました。
from django.core.cache.backends.base import BaseCache
from django.core.cache.backends.locmem import LocMemCache
from django.utils.synch import RWLock
class RequestCache(LocMemCache):
"""
RequestCache is a customized LocMemCache which stores its data cache as an instance attribute, rather than
a global. It's designed to live only as long as the request object that RequestCacheMiddleware attaches it to.
"""
def __init__(self):
# We explicitly do not call super() here, because while we want BaseCache.__init__() to run, we *don't*
# want LocMemCache.__init__() to run, because that would store our caches in its globals.
BaseCache.__init__(self, {})
self._cache = {}
self._expire_info = {}
self._lock = RWLock()
class RequestCacheMiddleware(object):
"""
Creates a fresh cache instance as request.cache. The cache instance lives only as long as request does.
"""
def process_request(self, request):
request.cache = RequestCache()
request.cache
これにより、が存続する限り存続するキャッシュ インスタンスとして使用できrequest
、要求が完了するとガベージ コレクタによって完全にクリーンアップされます。
通常はアクセスできないコンテキストからオブジェクトにアクセスする必要がある場合はrequest
、オンラインで見つけることができる、いわゆる「グローバル リクエスト ミドルウェア」のさまざまな実装の 1 つを使用できます。
** 最初の回答: **
ここで解決できる解決策が他にない主要な問題は、1 つのプロセスの存続期間中に複数の LocMemCache を作成および破棄すると、LocMemCache がメモリ リークを起こすという事実です。django.core.cache.backends.locmem
は、すべての LocalMemCache インスタンスのキャッシュ データへの参照を保持する複数のグローバル ディクショナリを定義します。これらのディクショナリは決して空にはなりません。
次のコードは、この問題を解決します。@href_の回答と、@squarelogic.haydenのコメントにリンクされているコードで使用されるよりクリーンなロジックの組み合わせとして始まり、それをさらに改良しました。
from uuid import uuid4
from threading import current_thread
from django.core.cache.backends.base import BaseCache
from django.core.cache.backends.locmem import LocMemCache
from django.utils.synch import RWLock
# Global in-memory store of cache data. Keyed by name, to provides multiple
# named local memory caches.
_caches = {}
_expire_info = {}
_locks = {}
class RequestCache(LocMemCache):
"""
RequestCache is a customized LocMemCache with a destructor, ensuring that creating
and destroying RequestCache objects over and over doesn't leak memory.
"""
def __init__(self):
# We explicitly do not call super() here, because while we want
# BaseCache.__init__() to run, we *don't* want LocMemCache.__init__() to run.
BaseCache.__init__(self, {})
# Use a name that is guaranteed to be unique for each RequestCache instance.
# This ensures that it will always be safe to call del _caches[self.name] in
# the destructor, even when multiple threads are doing so at the same time.
self.name = uuid4()
self._cache = _caches.setdefault(self.name, {})
self._expire_info = _expire_info.setdefault(self.name, {})
self._lock = _locks.setdefault(self.name, RWLock())
def __del__(self):
del _caches[self.name]
del _expire_info[self.name]
del _locks[self.name]
class RequestCacheMiddleware(object):
"""
Creates a cache instance that persists only for the duration of the current request.
"""
_request_caches = {}
def process_request(self, request):
# The RequestCache object is keyed on the current thread because each request is
# processed on a single thread, allowing us to retrieve the correct RequestCache
# object in the other functions.
self._request_caches[current_thread()] = RequestCache()
def process_response(self, request, response):
self.delete_cache()
return response
def process_exception(self, request, exception):
self.delete_cache()
@classmethod
def get_cache(cls):
"""
Retrieve the current request's cache.
Returns None if RequestCacheMiddleware is not currently installed via
MIDDLEWARE_CLASSES, or if there is no active request.
"""
return cls._request_caches.get(current_thread())
@classmethod
def clear_cache(cls):
"""
Clear the current request's cache.
"""
cache = cls.get_cache()
if cache:
cache.clear()
@classmethod
def delete_cache(cls):
"""
Delete the current request's cache object to avoid leaking memory.
"""
cache = cls._request_caches.pop(current_thread(), None)
del cache
編集 2016-06-15: 私はこの問題に対する非常に簡単な解決策を発見しました。
from django.core.cache.backends.base import BaseCache
from django.core.cache.backends.locmem import LocMemCache
from django.utils.synch import RWLock
class RequestCache(LocMemCache):
"""
RequestCache is a customized LocMemCache which stores its data cache as an instance attribute, rather than
a global. It's designed to live only as long as the request object that RequestCacheMiddleware attaches it to.
"""
def __init__(self):
# We explicitly do not call super() here, because while we want BaseCache.__init__() to run, we *don't*
# want LocMemCache.__init__() to run, because that would store our caches in its globals.
BaseCache.__init__(self, {})
self._cache = {}
self._expire_info = {}
self._lock = RWLock()
class RequestCacheMiddleware(object):
"""
Creates a fresh cache instance as request.cache. The cache instance lives only as long as request does.
"""
def process_request(self, request):
request.cache = RequestCache()
request.cache
これにより、が存続する限り存続するキャッシュ インスタンスとして使用できrequest
、要求が完了するとガベージ コレクタによって完全にクリーンアップされます。
通常はアクセスできないコンテキストからオブジェクトにアクセスする必要がある場合はrequest
、オンラインで見つけることができる、いわゆる「グローバル リクエスト ミドルウェア」のさまざまな実装の 1 つを使用できます。