Commit bf757a2f authored by Michael Manfre's avatar Michael Manfre Committed by Tim Graham
Browse files

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.
parent 8c272473
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
"Base Cache class."
from __future__ import unicode_literals

import time
import warnings

from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
@@ -74,6 +75,18 @@ class BaseCache(object):
        self.version = params.get('VERSION', 1)
        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):
        """Constructs the key used by all other methods. By default it
        uses the key_func to generate a key (which, by default,
+3 −4
Original line number Diff line number Diff line
@@ -92,8 +92,7 @@ class DatabaseCache(BaseDatabaseCache):
        return self._base_set('add', key, value, timeout)

    def _base_set(self, mode, key, value, timeout=DEFAULT_TIMEOUT):
        if timeout == DEFAULT_TIMEOUT:
            timeout = self.default_timeout
        timeout = self.get_backend_timeout(timeout)
        db = router.db_for_write(self.cache_model_class)
        table = connections[db].ops.quote_name(self._table)
        cursor = connections[db].cursor()
@@ -105,9 +104,9 @@ class DatabaseCache(BaseDatabaseCache):
        if timeout is None:
            exp = datetime.max
        elif settings.USE_TZ:
            exp = datetime.utcfromtimestamp(time.time() + timeout)
            exp = datetime.utcfromtimestamp(timeout)
        else:
            exp = datetime.fromtimestamp(time.time() + timeout)
            exp = datetime.fromtimestamp(timeout)
        exp = exp.replace(microsecond=0)
        if num > self._max_entries:
            self._cull(db, cursor, now)
+1 −4
Original line number Diff line number Diff line
@@ -51,9 +51,6 @@ class FileBasedCache(BaseCache):
        fname = self._key_to_file(key)
        dirname = os.path.dirname(fname)

        if timeout == DEFAULT_TIMEOUT:
            timeout = self.default_timeout

        self._cull()

        try:
@@ -61,7 +58,7 @@ class FileBasedCache(BaseCache):
                os.makedirs(dirname)

            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(value, f, pickle.HIGHEST_PROTOCOL)
        except (IOError, OSError):
+1 −4
Original line number Diff line number Diff line
@@ -63,11 +63,8 @@ class LocMemCache(BaseCache):
    def _set(self, key, value, timeout=DEFAULT_TIMEOUT):
        if len(self._cache) >= self._max_entries:
            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._expire_info[key] = expiry
        self._expire_info[key] = self.get_backend_timeout(timeout)

    def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
        key = self.make_key(key, version=version)
+13 −5
Original line number Diff line number Diff line
@@ -7,9 +7,17 @@ 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

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):
        super(BaseMemcachedCache, self).__init__(params)
        if isinstance(server, six.string_types):
@@ -36,7 +44,7 @@ class BaseMemcachedCache(BaseCache):

        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
        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):
        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):
        key = self.make_key(key, version=version)
@@ -79,7 +87,7 @@ class BaseMemcachedCache(BaseCache):

    def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
        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):
        key = self.make_key(key, version=version)
@@ -140,7 +148,7 @@ class BaseMemcachedCache(BaseCache):
        for key, value in data.items():
            key = self.make_key(key, version=version)
            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):
        l = lambda x: self.make_key(x, version=version)
Loading