Permalink
Browse files

Fixed #21012 -- Thread-local caches, like databases.

  • Loading branch information...
1 parent 3ca0815 commit ee7eb0f73e6d4699edcf5d357dce715224525cf6 @funkybob funkybob committed with aaugustin Oct 18, 2013
@@ -1,6 +1,6 @@
from django.conf import settings
from django.contrib.sessions.backends.base import SessionBase, CreateError
-from django.core.cache import get_cache
+from django.core.cache import caches
from django.utils.six.moves import xrange
KEY_PREFIX = "django.contrib.sessions.cache"
@@ -11,7 +11,7 @@ class SessionStore(SessionBase):
A cache-based session store.
"""
def __init__(self, session_key=None):
- self._cache = get_cache(settings.SESSION_CACHE_ALIAS)
+ self._cache = caches[settings.SESSION_CACHE_ALIAS]
super(SessionStore, self).__init__(session_key)
@property
@@ -6,7 +6,7 @@
from django.conf import settings
from django.contrib.sessions.backends.db import SessionStore as DBStore
-from django.core.cache import get_cache
+from django.core.cache import caches
from django.core.exceptions import SuspiciousOperation
from django.utils import timezone
from django.utils.encoding import force_text
@@ -20,7 +20,7 @@ class SessionStore(DBStore):
"""
def __init__(self, session_key=None):
- self._cache = get_cache(settings.SESSION_CACHE_ALIAS)
+ self._cache = caches[settings.SESSION_CACHE_ALIAS]
super(SessionStore, self).__init__(session_key)
@property
@@ -15,7 +15,7 @@
from django.contrib.sessions.backends.signed_cookies import SessionStore as CookieSession
from django.contrib.sessions.models import Session
from django.contrib.sessions.middleware import SessionMiddleware
-from django.core.cache import get_cache
+from django.core.cache import caches
from django.core.cache.backends.base import InvalidCacheBackendError
from django.core import management
from django.core.exceptions import ImproperlyConfigured
@@ -140,7 +140,7 @@ def test_clear(self):
self.assertTrue(self.session.modified)
def test_save(self):
- if (hasattr(self.session, '_cache') and'DummyCache' in
+ if (hasattr(self.session, '_cache') and 'DummyCache' in
settings.CACHES[settings.SESSION_CACHE_ALIAS]['BACKEND']):
raise unittest.SkipTest("Session saving tests require a real cache backend")
self.session.save()
@@ -481,23 +481,24 @@ def test_load_overlong_key(self):
def test_default_cache(self):
self.session.save()
- self.assertNotEqual(get_cache('default').get(self.session.cache_key), None)
+ self.assertNotEqual(caches['default'].get(self.session.cache_key), None)
@override_settings(CACHES={
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
},
'sessions': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
+ 'LOCATION': 'session',
},
}, SESSION_CACHE_ALIAS='sessions')
def test_non_default_cache(self):
# Re-initalize the session backend to make use of overridden settings.
self.session = self.backend()
self.session.save()
- self.assertEqual(get_cache('default').get(self.session.cache_key), None)
- self.assertNotEqual(get_cache('sessions').get(self.session.cache_key), None)
+ self.assertEqual(caches['default'].get(self.session.cache_key), None)
+ self.assertNotEqual(caches['sessions'].get(self.session.cache_key), None)
class SessionMiddlewareTests(unittest.TestCase):
@@ -7,7 +7,7 @@
import re
from django.conf import settings
-from django.core.cache import (get_cache, InvalidCacheBackendError,
+from django.core.cache import (caches, InvalidCacheBackendError,
cache as default_cache)
from django.core.exceptions import ImproperlyConfigured
from django.core.files.base import ContentFile
@@ -56,7 +56,7 @@ class CachedFilesMixin(object):
def __init__(self, *args, **kwargs):
super(CachedFilesMixin, self).__init__(*args, **kwargs)
try:
- self.cache = get_cache('staticfiles')
+ self.cache = caches['staticfiles']
except InvalidCacheBackendError:
# Use the default backend
self.cache = default_cache
@@ -14,6 +14,9 @@
See docs/topics/cache.txt for information on the public API.
"""
+from threading import local
+import warnings
+
from django.conf import settings
from django.core import signals
from django.core.cache.backends.base import (
@@ -23,8 +26,8 @@
__all__ = [
- 'get_cache', 'cache', 'DEFAULT_CACHE_ALIAS', 'InvalidCacheBackendError',
- 'CacheKeyWarning', 'BaseCache',
+ 'create_cache', 'get_cache', 'cache', 'DEFAULT_CACHE_ALIAS',
+ 'InvalidCacheBackendError', 'CacheKeyWarning', 'BaseCache',
]
DEFAULT_CACHE_ALIAS = 'default'
@@ -35,48 +38,116 @@
def get_cache(backend, **kwargs):
"""
- Function to load a cache backend dynamically. This is flexible by design
- to allow different use cases:
+ Function to retrieve a configure cache, or create a new one.
+
+ This wrapper is for backward compatibility.
+
+ Use either create_cache or caches directly.
+
+ """
+ warnings.warn("'get_cache' is deprecated. Use either caches or create_cache.",
+ PendingDeprecationWarning, stacklevel=2)
+
+ # If it's just an alias with no options, use the new API
+ if backend in settings.CACHES and not kwargs:
+ return caches[backend]
+
+ return create_cache(backend, **kwargs)
- To load a backend that is pre-defined in the settings::
- cache = get_cache('default')
+def create_cache(backend, **params):
+ """
+ Function to create a cache backend dynamically. This is flexible by design
+ to allow different use cases:
- To load a backend with its dotted import path,
- including arbitrary options::
+ To load a backend with its dotted import path, including options::
- cache = get_cache('django.core.cache.backends.memcached.MemcachedCache', **{
- 'LOCATION': '127.0.0.1:11211', 'TIMEOUT': 30,
+ cache = get_cache('django.core.cache.backends.memcached.MemcachedCache',
+ LOCATION='127.0.0.1:11211', TIMEOUT=30,
})
+ To create a new instance of a cache in settings.CACHES, pass the alias::
+
+ cache = create_cache('default')
+
+ You can also pass extra parameters to override those in settings.CACHES::
+
+ cache = create_cache('default', LOCATION='bar')
+
"""
+
+ # We can name a cache from settings.CACHES and update its params
+ try:
+ conf = settings.CACHES[backend]
+ except KeyError:
+ pass
+ else:
+ params = conf.copy()
+ params.update(params)
+ backend = params.pop('BACKEND')
+
try:
- # Try to get the CACHES entry for the given backend name first
- try:
- conf = settings.CACHES[backend]
- except KeyError:
- try:
- # Trying to import the given backend, in case it's a dotted path
- import_by_path(backend)
- except ImproperlyConfigured as e:
- raise InvalidCacheBackendError("Could not find backend '%s': %s" % (
- backend, e))
- location = kwargs.pop('LOCATION', '')
- params = kwargs
- else:
- params = conf.copy()
- params.update(kwargs)
- backend = params.pop('BACKEND')
- location = params.pop('LOCATION', '')
backend_cls = import_by_path(backend)
- except (AttributeError, ImportError, ImproperlyConfigured) as e:
- raise InvalidCacheBackendError(
- "Could not find backend '%s': %s" % (backend, e))
+ except ImproperlyConfigured as e:
+ raise InvalidCacheBackendError("Could not find backend '%s': %s" % (
+ backend, e
+ ))
+ location = params.pop('LOCATION', '')
cache = backend_cls(location, params)
# Some caches -- python-memcached in particular -- need to do a cleanup at the
# end of a request cycle. If not implemented in a particular backend
# cache.close is a no-op
signals.request_finished.connect(cache.close)
return cache
-cache = get_cache(DEFAULT_CACHE_ALIAS)
+
+class CacheHandler(object):
+ """
+ A Cache Handler to manage access to Cache instances.
+
+ Ensures only one instance of each alias exists per thread.
+ """
+ def __init__(self):
+ self._caches = local()
+
+ def __getitem__(self, alias):
+ try:
+ return getattr(self._caches, alias)
+ except AttributeError:
+ pass
+
+ if alias not in settings.CACHES:
+ raise InvalidCacheBackendError(
+ "Could not find config for '%s' in settings.CACHES" % alias
+ )
+
+ cache = create_cache(alias)
+ setattr(self._caches, alias, cache)
+
+ return cache
+
+caches = CacheHandler()
+
+class DefaultCacheProxy(object):
+ """
+ Proxy access to the default Cache object's attributes.
+
+ This allows the legacy `cache` object to be thread-safe using the new
+ ``caches`` API.
+ """
+ def __getattr__(self, name):
+ return getattr(caches[DEFAULT_CACHE_ALIAS], name)
+
+ def __setattr__(self, name, value):
+ return setattr(caches[DEFAULT_CACHE_ALIAS], name, value)
+
+ def __delattr__(self, name):
+ return delattr(caches[DEFAULT_CACHE_ALIAS], name)
+
+ def __eq__(self, other):
+ return caches[DEFAULT_CACHE_ALIAS] == other
+
+ def __ne__(self, other):
+ return caches[DEFAULT_CACHE_ALIAS] != other
+
+cache = DefaultCacheProxy()
@@ -2,13 +2,13 @@
import time
import pickle
-from threading import local
from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
from django.utils import six
from django.utils.deprecation import RenameMethodsBase
from django.utils.encoding import force_str
+from django.utils.functional import cached_property
class BaseMemcachedCacheMethods(RenameMethodsBase):
@@ -177,24 +177,14 @@ class PyLibMCCache(BaseMemcachedCache):
"An implementation of a cache binding using pylibmc"
def __init__(self, server, params):
import pylibmc
- self._local = local()
super(PyLibMCCache, self).__init__(server, params,
library=pylibmc,
value_not_found_exception=pylibmc.NotFound)
- @property
+ @cached_property
def _cache(self):
- # PylibMC uses cache options as the 'behaviors' attribute.
- # It also needs to use threadlocals, because some versions of
- # PylibMC don't play well with the GIL.
- client = getattr(self._local, 'client', None)
- if client:
- return client
-
client = self._lib.Client(self._servers)
if self._options:
client.behaviors = self._options
- self._local.client = client
-
return client
@@ -1,7 +1,7 @@
from optparse import make_option
from django.conf import settings
-from django.core.cache import get_cache
+from django.core.cache import create_cache
from django.core.cache.backends.db import BaseDatabaseCache
from django.core.management.base import BaseCommand, CommandError
from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS
@@ -30,7 +30,7 @@ def handle(self, *tablenames, **options):
self.create_table(db, tablename)
else:
for cache_alias in settings.CACHES:
- cache = get_cache(cache_alias)
+ cache = create_cache(cache_alias)
if isinstance(cache, BaseDatabaseCache):
self.create_table(db, cache._table)
Oops, something went wrong. Retry.

0 comments on commit ee7eb0f

Please sign in to comment.