Browse Source

Merge pull request #3481 from manfre/ticket-16358

Fixed #16358 - Made memcache backend delete old value on a failure to set.
Michael Manfre 10 years ago
parent
commit
a305695f28
3 changed files with 25 additions and 1 deletions
  1. 3 1
      django/core/cache/backends/memcached.py
  2. 4 0
      docs/releases/1.8.txt
  3. 18 0
      tests/cache/tests.py

+ 3 - 1
django/core/cache/backends/memcached.py

@@ -86,7 +86,9 @@ class BaseMemcachedCache(six.with_metaclass(BaseMemcachedCacheMethods, 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_backend_timeout(timeout))
+        if not self._cache.set(key, value, self.get_backend_timeout(timeout)):
+            # make sure the key doesn't keep its old value in case of failure to set (memcached's 1MB limit)
+            self._cache.delete(key)
 
     def delete(self, key, version=None):
         key = self.make_key(key, version=version)

+ 4 - 0
docs/releases/1.8.txt

@@ -729,6 +729,10 @@ Miscellaneous
 
   .. _universal newlines: http://www.python.org/dev/peps/pep-0278
 
+* The Memcached cache backends ``MemcachedCache`` and ``PyLibMCCache`` will
+  delete a key if ``set()`` fails. This is necessary to ensure the ``cache_db``
+  session store always fetches the most current session data.
+
 .. _deprecated-features-1.8:
 
 Features deprecated in 1.8

+ 18 - 0
tests/cache/tests.py

@@ -1133,6 +1133,24 @@ class MemcachedCacheTests(BaseCacheTests, TestCase):
         # culling isn't implemented, memcached deals with it.
         pass
 
+    def test_memcached_deletes_key_on_failed_set(self):
+        # By default memcached allows objects up to 1MB. For the cache_db session
+        # backend to always use the current session, memcached needs to delete
+        # the old key if it fails to set.
+        # pylibmc doesn't seem to have SERVER_MAX_VALUE_LENGTH as far as I can
+        # tell from a quick check of its source code. This is falling back to
+        # the default value exposed by python-memcached on my system.
+        max_value_length = getattr(cache._lib, 'SERVER_MAX_VALUE_LENGTH', 1048576)
+
+        cache.set('small_value', 'a')
+        self.assertEqual(cache.get('small_value'), 'a')
+
+        large_value = 'a' * (max_value_length + 1)
+        cache.set('small_value', large_value)
+        # small_value should be deleted, or set if configured to accept larger values
+        value = cache.get('small_value')
+        self.assertTrue(value is None or value == large_value)
+
 
 @override_settings(CACHES=caches_setting_for_tests(
     BACKEND='django.core.cache.backends.filebased.FileBasedCache',