mirror of https://github.com/django/django.git
Fixed #21147 -- Avoided time.time precision issue with cache backends.
The precision of time.time() is OS specific and it is possible for the resolution to be low enough to allow reading a cache key previously set with a timeout of 0.
This commit is contained in:
parent
8c27247397
commit
bf757a2f4d
|
@ -1,6 +1,7 @@
|
||||||
"Base Cache class."
|
"Base Cache class."
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import time
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
|
from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
|
||||||
|
@ -74,6 +75,18 @@ class BaseCache(object):
|
||||||
self.version = params.get('VERSION', 1)
|
self.version = params.get('VERSION', 1)
|
||||||
self.key_func = get_key_func(params.get('KEY_FUNCTION', None))
|
self.key_func = get_key_func(params.get('KEY_FUNCTION', None))
|
||||||
|
|
||||||
|
def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT):
|
||||||
|
"""
|
||||||
|
Returns the timeout value usable by this backend based upon the provided
|
||||||
|
timeout.
|
||||||
|
"""
|
||||||
|
if timeout == DEFAULT_TIMEOUT:
|
||||||
|
timeout = self.default_timeout
|
||||||
|
elif timeout == 0:
|
||||||
|
# ticket 21147 - avoid time.time() related precision issues
|
||||||
|
timeout = -1
|
||||||
|
return None if timeout is None else time.time() + timeout
|
||||||
|
|
||||||
def make_key(self, key, version=None):
|
def make_key(self, key, version=None):
|
||||||
"""Constructs the key used by all other methods. By default it
|
"""Constructs the key used by all other methods. By default it
|
||||||
uses the key_func to generate a key (which, by default,
|
uses the key_func to generate a key (which, by default,
|
||||||
|
|
|
@ -92,8 +92,7 @@ class DatabaseCache(BaseDatabaseCache):
|
||||||
return self._base_set('add', key, value, timeout)
|
return self._base_set('add', key, value, timeout)
|
||||||
|
|
||||||
def _base_set(self, mode, key, value, timeout=DEFAULT_TIMEOUT):
|
def _base_set(self, mode, key, value, timeout=DEFAULT_TIMEOUT):
|
||||||
if timeout == DEFAULT_TIMEOUT:
|
timeout = self.get_backend_timeout(timeout)
|
||||||
timeout = self.default_timeout
|
|
||||||
db = router.db_for_write(self.cache_model_class)
|
db = router.db_for_write(self.cache_model_class)
|
||||||
table = connections[db].ops.quote_name(self._table)
|
table = connections[db].ops.quote_name(self._table)
|
||||||
cursor = connections[db].cursor()
|
cursor = connections[db].cursor()
|
||||||
|
@ -105,9 +104,9 @@ class DatabaseCache(BaseDatabaseCache):
|
||||||
if timeout is None:
|
if timeout is None:
|
||||||
exp = datetime.max
|
exp = datetime.max
|
||||||
elif settings.USE_TZ:
|
elif settings.USE_TZ:
|
||||||
exp = datetime.utcfromtimestamp(time.time() + timeout)
|
exp = datetime.utcfromtimestamp(timeout)
|
||||||
else:
|
else:
|
||||||
exp = datetime.fromtimestamp(time.time() + timeout)
|
exp = datetime.fromtimestamp(timeout)
|
||||||
exp = exp.replace(microsecond=0)
|
exp = exp.replace(microsecond=0)
|
||||||
if num > self._max_entries:
|
if num > self._max_entries:
|
||||||
self._cull(db, cursor, now)
|
self._cull(db, cursor, now)
|
||||||
|
|
|
@ -51,9 +51,6 @@ class FileBasedCache(BaseCache):
|
||||||
fname = self._key_to_file(key)
|
fname = self._key_to_file(key)
|
||||||
dirname = os.path.dirname(fname)
|
dirname = os.path.dirname(fname)
|
||||||
|
|
||||||
if timeout == DEFAULT_TIMEOUT:
|
|
||||||
timeout = self.default_timeout
|
|
||||||
|
|
||||||
self._cull()
|
self._cull()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -61,7 +58,7 @@ class FileBasedCache(BaseCache):
|
||||||
os.makedirs(dirname)
|
os.makedirs(dirname)
|
||||||
|
|
||||||
with open(fname, 'wb') as f:
|
with open(fname, 'wb') as f:
|
||||||
expiry = None if timeout is None else time.time() + timeout
|
expiry = self.get_backend_timeout(timeout)
|
||||||
pickle.dump(expiry, f, pickle.HIGHEST_PROTOCOL)
|
pickle.dump(expiry, f, pickle.HIGHEST_PROTOCOL)
|
||||||
pickle.dump(value, f, pickle.HIGHEST_PROTOCOL)
|
pickle.dump(value, f, pickle.HIGHEST_PROTOCOL)
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
|
|
|
@ -63,11 +63,8 @@ class LocMemCache(BaseCache):
|
||||||
def _set(self, key, value, timeout=DEFAULT_TIMEOUT):
|
def _set(self, key, value, timeout=DEFAULT_TIMEOUT):
|
||||||
if len(self._cache) >= self._max_entries:
|
if len(self._cache) >= self._max_entries:
|
||||||
self._cull()
|
self._cull()
|
||||||
if timeout == DEFAULT_TIMEOUT:
|
|
||||||
timeout = self.default_timeout
|
|
||||||
expiry = None if timeout is None else time.time() + timeout
|
|
||||||
self._cache[key] = value
|
self._cache[key] = value
|
||||||
self._expire_info[key] = expiry
|
self._expire_info[key] = self.get_backend_timeout(timeout)
|
||||||
|
|
||||||
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
|
|
|
@ -7,9 +7,17 @@ from threading import local
|
||||||
from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
|
from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
|
||||||
|
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
from django.utils.deprecation import RenameMethodsBase
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
class BaseMemcachedCache(BaseCache):
|
|
||||||
|
class BaseMemcachedCacheMethods(RenameMethodsBase):
|
||||||
|
renamed_methods = (
|
||||||
|
('_get_memcache_timeout', 'get_backend_timeout', PendingDeprecationWarning),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseMemcachedCache(six.with_metaclass(BaseMemcachedCacheMethods, BaseCache)):
|
||||||
def __init__(self, server, params, library, value_not_found_exception):
|
def __init__(self, server, params, library, value_not_found_exception):
|
||||||
super(BaseMemcachedCache, self).__init__(params)
|
super(BaseMemcachedCache, self).__init__(params)
|
||||||
if isinstance(server, six.string_types):
|
if isinstance(server, six.string_types):
|
||||||
|
@ -36,7 +44,7 @@ class BaseMemcachedCache(BaseCache):
|
||||||
|
|
||||||
return self._client
|
return self._client
|
||||||
|
|
||||||
def _get_memcache_timeout(self, timeout=DEFAULT_TIMEOUT):
|
def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT):
|
||||||
"""
|
"""
|
||||||
Memcached deals with long (> 30 days) timeouts in a special
|
Memcached deals with long (> 30 days) timeouts in a special
|
||||||
way. Call this function to obtain a safe value for your timeout.
|
way. Call this function to obtain a safe value for your timeout.
|
||||||
|
@ -68,7 +76,7 @@ class BaseMemcachedCache(BaseCache):
|
||||||
|
|
||||||
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
return self._cache.add(key, value, self._get_memcache_timeout(timeout))
|
return self._cache.add(key, value, self.get_backend_timeout(timeout))
|
||||||
|
|
||||||
def get(self, key, default=None, version=None):
|
def get(self, key, default=None, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
|
@ -79,7 +87,7 @@ class BaseMemcachedCache(BaseCache):
|
||||||
|
|
||||||
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
self._cache.set(key, value, self._get_memcache_timeout(timeout))
|
self._cache.set(key, value, self.get_backend_timeout(timeout))
|
||||||
|
|
||||||
def delete(self, key, version=None):
|
def delete(self, key, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
|
@ -140,7 +148,7 @@ class BaseMemcachedCache(BaseCache):
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
safe_data[key] = value
|
safe_data[key] = value
|
||||||
self._cache.set_multi(safe_data, self._get_memcache_timeout(timeout))
|
self._cache.set_multi(safe_data, self.get_backend_timeout(timeout))
|
||||||
|
|
||||||
def delete_many(self, keys, version=None):
|
def delete_many(self, keys, version=None):
|
||||||
l = lambda x: self.make_key(x, version=version)
|
l = lambda x: self.make_key(x, version=version)
|
||||||
|
|
|
@ -457,6 +457,10 @@ these changes.
|
||||||
|
|
||||||
* ``ModelAdmin.get_formsets`` will be removed.
|
* ``ModelAdmin.get_formsets`` will be removed.
|
||||||
|
|
||||||
|
* Remove the backward compatible shims introduced to rename the
|
||||||
|
``BaseMemcachedCache._get_memcache_timeout()`` method to
|
||||||
|
``get_backend_timeout()``.
|
||||||
|
|
||||||
2.0
|
2.0
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -552,3 +552,9 @@ The :class:`django.db.models.IPAddressField` and
|
||||||
:class:`django.db.models.GenericIPAddressField` and
|
:class:`django.db.models.GenericIPAddressField` and
|
||||||
:class:`django.forms.GenericIPAddressField`.
|
:class:`django.forms.GenericIPAddressField`.
|
||||||
|
|
||||||
|
``BaseMemcachedCache._get_memcache_timeout`` method
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ``BaseMemcachedCache._get_memcache_timeout()`` method has been renamed to
|
||||||
|
``get_backend_timeout()``. Despite being a private API, it will go through the
|
||||||
|
normal deprecation.
|
||||||
|
|
Loading…
Reference in New Issue