mirror of
				https://github.com/django/django.git
				synced 2025-11-03 21:25:09 +00:00 
			
		
		
		
	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
					
				
					 7 changed files with 41 additions and 17 deletions
				
			
		
							
								
								
									
										13
									
								
								django/core/cache/backends/base.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								django/core/cache/backends/base.py
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
"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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										7
									
								
								django/core/cache/backends/db.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								django/core/cache/backends/db.py
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										5
									
								
								django/core/cache/backends/filebased.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								django/core/cache/backends/filebased.py
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -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):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										5
									
								
								django/core/cache/backends/locmem.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								django/core/cache/backends/locmem.py
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										18
									
								
								django/core/cache/backends/memcached.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								django/core/cache/backends/memcached.py
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -457,6 +457,10 @@ these changes.
 | 
			
		|||
 | 
			
		||||
* ``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
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -552,3 +552,9 @@ The :class:`django.db.models.IPAddressField` and
 | 
			
		|||
:class:`django.db.models.GenericIPAddressField` and
 | 
			
		||||
: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…
	
	Add table
		Add a link
		
	
		Reference in a new issue