Ticket #11503: patch.diff

File patch.diff, 9.1 KB (added by andrewfong, 6 years ago)

Exposes a flush method that clears the cache for each backend + tests. Also, since culling and flushing are related (e.g. when the cull frequency is 0), I refactored some of the culling code to use the flush method instead. Accordingly, I've added tests to make sure culling is still working correctly.

  • django/core/cache/backends/base.py

     
    8383        """
    8484        return self.incr(key, -delta)
    8585
     86    def flush(self):
     87        """
     88        Empties the cache
     89        """
     90        raise NotImplementedError
     91
    8692    def __contains__(self, key):
    8793        """
    8894        Returns True if the key is in the cache and has not expired.
  • django/core/cache/backends/dummy.py

     
    2323
    2424    def has_key(self, *args, **kwargs):
    2525        return False
     26
     27    def flush(self):
     28        pass
  • django/core/cache/backends/locmem.py

     
    108108        finally:
    109109            self._lock.writer_leaves()
    110110
     111    def flush(self):
     112        self._cache.clear()
     113        self._expire_info.clear()
     114
    111115    def _cull(self):
    112116        if self._cull_frequency == 0:
    113             self._cache.clear()
    114             self._expire_info.clear()
     117            self.flush()
    115118        else:
    116119            doomed = [k for (i, k) in enumerate(self._cache) if i % self._cull_frequency == 0]
    117120            for k in doomed:
  • django/core/cache/backends/filebased.py

     
    103103        except (IOError, OSError, EOFError, pickle.PickleError):
    104104            return False
    105105
     106    def flush(self):
     107        try:
     108            filelist = os.listdir(self._dir)
     109        except (IOError, OSError):
     110            return
     111        for topdir in filelist:
     112            topdir = os.path.join(self._dir, topdir)
     113            try:
     114                for root, _, files in os.walk(topdir):
     115                    for f in files:
     116                        self._delete(os.path.join(root, f))
     117            except (IOError, OSError):
     118                pass
     119
    106120    def _cull(self):
    107121        if int(self._num_entries) < self._max_entries:
    108122            return
     
    113127            return
    114128
    115129        if self._cull_frequency == 0:
    116             doomed = filelist
     130            doomed = [os.path.join(self._dir, k) for k in filelist]
    117131        else:
    118132            doomed = [os.path.join(self._dir, k) for (i, k) in enumerate(filelist) if i % self._cull_frequency == 0]
    119133
  • django/core/cache/backends/db.py

     
    5252        num = cursor.fetchone()[0]
    5353        now = datetime.now().replace(microsecond=0)
    5454        exp = datetime.fromtimestamp(time.time() + timeout).replace(microsecond=0)
    55         if num > self._max_entries:
     55        if num >= self._max_entries:
    5656            self._cull(cursor, now)
    5757        encoded = base64.encodestring(pickle.dumps(value, 2)).strip()
    5858        cursor.execute("SELECT cache_key, expires FROM %s WHERE cache_key = %%s" % self._table, [key])
     
    8282        cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s and expires > %%s" % self._table, [key, now])
    8383        return cursor.fetchone() is not None
    8484
     85    def flush(self):
     86        cursor = connection.cursor()
     87        self._flush(cursor)
     88
     89    def _flush(self, cursor):
     90        cursor.execute("DELETE FROM %s" % self._table)
     91
    8592    def _cull(self, cursor, now):
    8693        if self._cull_frequency == 0:
    87             cursor.execute("DELETE FROM %s" % self._table)
     94            self._flush(cursor)
    8895        else:
    8996            cursor.execute("DELETE FROM %s WHERE expires < %%s" % self._table, [str(now)])
    9097            cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
    9198            num = cursor.fetchone()[0]
    92             if num > self._max_entries:
     99            if num >= self._max_entries:
    93100                cursor.execute("SELECT cache_key FROM %s ORDER BY cache_key LIMIT 1 OFFSET %%s" % self._table, [num / self._cull_frequency])
    94101                cursor.execute("DELETE FROM %s WHERE cache_key < %%s" % self._table, [cursor.fetchone()[0]])
  • django/core/cache/backends/memcached.py

     
    4444
    4545    def close(self, **kwargs):
    4646        self._cache.disconnect_all()
    47 
     47   
     48    def flush(self):
     49        self._cache.flush_all()     
     50   
    4851    def incr(self, key, delta=1):
    4952        return self._cache.incr(key, delta)
    5053
  • tests/regressiontests/cache/tests.py

     
    8989        self.assertRaises(ValueError, self.cache.decr, 'answer')
    9090        self.assertRaises(ValueError, self.cache.decr, 'does_not_exist')
    9191
     92    def test_flush(self):
     93        "Flushing dummy cache does nothing but should not throw an exception"
     94        self.cache.flush()
     95
    9296    def test_data_types(self):
    9397        "All data types are ignored equally by the dummy cache"
    9498        stuff = {
     
    197201        self.assertEqual(self.cache.get('answer'), 32)
    198202        self.assertRaises(ValueError, self.cache.decr, 'does_not_exist')
    199203
     204    def test_flush(self):
     205        # Flushing removes everything from the cache
     206        self.cache.set("cheese1", "Venezuelan Beaver")
     207        self.cache.set("cheese2", "Czech Sheep's Milk")
     208        self.cache.flush()
     209        self.assertEqual(self.cache.get('cheese1'), None)
     210        self.assertEqual(self.cache.get('cheese2'), None)
     211   
     212    def test_culling(self):
     213        # After max_entries, remove 1/cull_frequency entries
     214        cache = self._get_culling_cache(4,2)
     215        cache.set("cheese1", "Venezuelan Beaver")
     216        cache.set("cheese2", "Czech Sheep's Milk")
     217        cache.set("cheese3", "Ilchester")
     218        cache.set("cheese4", "Cheddar")
     219        cache.set("cheese5", "Limburger")
     220
     221        count = 0
     222        if cache.get('cheese1'): count += 1
     223        if cache.get('cheese2'): count += 1
     224        if cache.get('cheese3'): count += 1
     225        if cache.get('cheese4'): count += 1
     226        if cache.get('cheese5'): count += 1
     227
     228        self.assertEqual(3, count)
     229
     230    def test_cull_zero(self):
     231        # Cull_frequency of 0 remove all entries
     232        cache = self._get_culling_cache(2,0)
     233        cache.set("cheese1", "Venezuelan Beaver")
     234        cache.set("cheese2", "Czech Sheep's Milk")
     235        cache.set("cheese3", "Ilchester")
     236        self.assertEqual(cache.get('cheese1'), None)
     237        self.assertEqual(cache.get('cheese2'), None)
     238        self.assertEqual(cache.get('cheese3'), 'Ilchester')
     239
     240    def _get_culling_cache(self, max_entries, cull_frequency):
     241        "Returns a cache with given max_entries and cull_frequency"
     242        raise NotImplementedError
     243
    200244    def test_data_types(self):
    201245        # Many different data types can be cached
    202246        stuff = {
     
    246290        cursor = connection.cursor()
    247291        cursor.execute('DROP TABLE test_cache_table');
    248292
     293    def _get_culling_cache(self, max_entries, cull_frequency):
     294        return get_cache('db://test_cache_table?max_entries=%s&cull_frequency=%s' %
     295                         (max_entries, cull_frequency))
     296
    249297class LocMemCacheTests(unittest.TestCase, BaseCacheTests):
    250298    def setUp(self):
    251299        self.cache = get_cache('locmem://')
    252300
     301    def _get_culling_cache(self, max_entries, cull_frequency):
     302        return get_cache('locmem://?max_entries=%s&cull_frequency=%s' %
     303                         (max_entries, cull_frequency))
     304
    253305# memcached backend isn't guaranteed to be available.
    254306# To check the memcached backend, the test settings file will
    255307# need to contain a CACHE_BACKEND setting that points at
     
    259311        def setUp(self):
    260312            self.cache = get_cache(settings.CACHE_BACKEND)
    261313
     314        def test_culling(self):
     315            # No culling tests -- memcached does its own thing
     316            pass
     317
     318        def test_cull_zero(self):
     319            # No culling tests -- memcached does its own thing
     320            pass
     321
    262322class FileBasedCacheTests(unittest.TestCase, BaseCacheTests):
    263323    """
    264324    Specific test cases for the file-based cache.
     
    270330    def tearDown(self):
    271331        shutil.rmtree(self.dirname)
    272332
     333    def _get_culling_cache(self, max_entries, cull_frequency):
     334        return get_cache('file://%s?max_entries=%s&cull_frequency=%s' %
     335                         (self.dirname, max_entries, cull_frequency))
     336   
    273337    def test_hashing(self):
    274338        """Test that keys are hashed into subdirectories correctly"""
    275339        self.cache.set("foo", "bar")
Back to Top