2006-02-24 14:07:01 +08:00
|
|
|
"Memcached cache backend"
|
|
|
|
|
2013-02-24 20:36:04 +08:00
|
|
|
import pickle
|
2016-09-01 01:15:22 +08:00
|
|
|
import re
|
2015-01-28 20:35:27 +08:00
|
|
|
import time
|
2010-02-11 20:06:26 +08:00
|
|
|
|
2015-01-28 20:35:27 +08:00
|
|
|
from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
|
2013-10-19 06:49:24 +08:00
|
|
|
from django.utils.functional import cached_property
|
2012-07-20 20:22:00 +08:00
|
|
|
|
2013-09-24 00:40:19 +08:00
|
|
|
|
2015-01-18 08:32:54 +08:00
|
|
|
class BaseMemcachedCache(BaseCache):
|
2010-12-21 23:19:19 +08:00
|
|
|
def __init__(self, server, params, library, value_not_found_exception):
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(params)
|
2016-12-29 23:27:49 +08:00
|
|
|
if isinstance(server, str):
|
2016-09-01 01:15:22 +08:00
|
|
|
self._servers = re.split('[;,]', server)
|
2010-12-21 23:19:19 +08:00
|
|
|
else:
|
|
|
|
self._servers = server
|
|
|
|
|
|
|
|
# The exception type to catch from the underlying library for a key
|
|
|
|
# that was not found. This is a ValueError for python-memcache,
|
|
|
|
# pylibmc.NotFound for pylibmc, and cmemcache will return None without
|
|
|
|
# raising an exception.
|
|
|
|
self.LibraryValueNotFoundException = value_not_found_exception
|
|
|
|
|
|
|
|
self._lib = library
|
2016-08-31 20:12:40 +08:00
|
|
|
self._options = params.get('OPTIONS') or {}
|
2010-12-21 23:19:19 +08:00
|
|
|
|
|
|
|
@property
|
|
|
|
def _cache(self):
|
|
|
|
"""
|
2017-01-26 03:02:33 +08:00
|
|
|
Implement transparent thread-safe access to a memcached client.
|
2010-12-21 23:19:19 +08:00
|
|
|
"""
|
2011-02-19 15:40:09 +08:00
|
|
|
if getattr(self, '_client', None) is None:
|
2016-08-31 20:12:40 +08:00
|
|
|
self._client = self._lib.Client(self._servers, **self._options)
|
2011-02-19 15:40:09 +08:00
|
|
|
|
|
|
|
return self._client
|
2006-02-24 14:07:01 +08:00
|
|
|
|
2013-09-24 00:40:19 +08:00
|
|
|
def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT):
|
2010-02-11 20:06:26 +08:00
|
|
|
"""
|
|
|
|
Memcached deals with long (> 30 days) timeouts in a special
|
|
|
|
way. Call this function to obtain a safe value for your timeout.
|
|
|
|
"""
|
2013-05-18 18:54:59 +08:00
|
|
|
if timeout == DEFAULT_TIMEOUT:
|
2014-06-16 16:46:34 +08:00
|
|
|
timeout = self.default_timeout
|
2013-05-18 18:54:59 +08:00
|
|
|
|
|
|
|
if timeout is None:
|
|
|
|
# Using 0 in memcache sets a non-expiring timeout.
|
|
|
|
return 0
|
|
|
|
elif int(timeout) == 0:
|
|
|
|
# Other cache backends treat 0 as set-and-expire. To achieve this
|
|
|
|
# in memcache backends, a negative timeout must be passed.
|
|
|
|
timeout = -1
|
|
|
|
|
2013-11-03 05:02:56 +08:00
|
|
|
if timeout > 2592000: # 60*60*24*30, 30 days
|
2016-06-16 09:20:23 +08:00
|
|
|
# See https://github.com/memcached/memcached/wiki/Programming#expiration
|
2015-07-06 21:48:06 +08:00
|
|
|
# "Expiration times can be set from 0, meaning "never expire", to
|
|
|
|
# 30 days. Any time higher than 30 days is interpreted as a Unix
|
|
|
|
# timestamp date. If you want to expire an object on January 1st of
|
|
|
|
# next year, this is how you do that."
|
2010-02-11 20:06:26 +08:00
|
|
|
#
|
|
|
|
# This means that we have to switch to absolute timestamps.
|
|
|
|
timeout += int(time.time())
|
2011-07-29 17:39:23 +08:00
|
|
|
return int(timeout)
|
2010-02-11 20:06:26 +08:00
|
|
|
|
2013-05-18 18:54:59 +08:00
|
|
|
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
2010-11-19 23:39:35 +08:00
|
|
|
key = self.make_key(key, version=version)
|
2013-09-24 00:40:19 +08:00
|
|
|
return self._cache.add(key, value, self.get_backend_timeout(timeout))
|
2007-10-20 23:16:34 +08:00
|
|
|
|
2010-11-19 23:39:35 +08:00
|
|
|
def get(self, key, default=None, version=None):
|
|
|
|
key = self.make_key(key, version=version)
|
|
|
|
val = self._cache.get(key)
|
2006-02-24 14:07:01 +08:00
|
|
|
if val is None:
|
|
|
|
return default
|
2010-03-02 04:11:24 +08:00
|
|
|
return val
|
2006-02-24 14:07:01 +08:00
|
|
|
|
2013-05-18 18:54:59 +08:00
|
|
|
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
2010-11-19 23:39:35 +08:00
|
|
|
key = self.make_key(key, version=version)
|
2014-11-07 12:47:04 +08:00
|
|
|
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)
|
2010-11-19 23:39:35 +08:00
|
|
|
|
|
|
|
def delete(self, key, version=None):
|
|
|
|
key = self.make_key(key, version=version)
|
|
|
|
self._cache.delete(key)
|
|
|
|
|
|
|
|
def get_many(self, keys, version=None):
|
2018-07-07 02:03:58 +08:00
|
|
|
key_map = {self.make_key(key, version=version): key for key in keys}
|
|
|
|
ret = self._cache.get_multi(key_map.keys())
|
|
|
|
return {key_map[k]: v for k, v in ret.items()}
|
2008-08-17 07:35:58 +08:00
|
|
|
|
|
|
|
def close(self, **kwargs):
|
2016-09-02 16:33:59 +08:00
|
|
|
# Many clients don't clean up connections properly.
|
2008-08-17 07:35:58 +08:00
|
|
|
self._cache.disconnect_all()
|
|
|
|
|
2010-11-19 23:39:35 +08:00
|
|
|
def incr(self, key, delta=1, version=None):
|
|
|
|
key = self.make_key(key, version=version)
|
2012-11-06 19:19:14 +08:00
|
|
|
# memcached doesn't support a negative delta
|
|
|
|
if delta < 0:
|
|
|
|
return self._cache.decr(key, -delta)
|
2009-12-14 04:35:06 +08:00
|
|
|
try:
|
|
|
|
val = self._cache.incr(key, delta)
|
|
|
|
|
2017-02-03 09:43:21 +08:00
|
|
|
# python-memcache responds to incr on nonexistent keys by
|
2010-12-21 23:19:19 +08:00
|
|
|
# raising a ValueError, pylibmc by raising a pylibmc.NotFound
|
|
|
|
# and Cmemcache returns None. In all cases,
|
|
|
|
# we should raise a ValueError though.
|
|
|
|
except self.LibraryValueNotFoundException:
|
2009-12-14 04:35:06 +08:00
|
|
|
val = None
|
|
|
|
if val is None:
|
|
|
|
raise ValueError("Key '%s' not found" % key)
|
|
|
|
return val
|
2009-03-11 21:27:03 +08:00
|
|
|
|
2010-11-19 23:39:35 +08:00
|
|
|
def decr(self, key, delta=1, version=None):
|
|
|
|
key = self.make_key(key, version=version)
|
2012-11-06 19:19:14 +08:00
|
|
|
# memcached doesn't support a negative delta
|
|
|
|
if delta < 0:
|
|
|
|
return self._cache.incr(key, -delta)
|
2009-12-14 04:35:06 +08:00
|
|
|
try:
|
|
|
|
val = self._cache.decr(key, delta)
|
|
|
|
|
2017-02-03 09:43:21 +08:00
|
|
|
# python-memcache responds to incr on nonexistent keys by
|
2010-12-21 23:19:19 +08:00
|
|
|
# raising a ValueError, pylibmc by raising a pylibmc.NotFound
|
|
|
|
# and Cmemcache returns None. In all cases,
|
|
|
|
# we should raise a ValueError though.
|
|
|
|
except self.LibraryValueNotFoundException:
|
2009-12-14 04:35:06 +08:00
|
|
|
val = None
|
|
|
|
if val is None:
|
|
|
|
raise ValueError("Key '%s' not found" % key)
|
|
|
|
return val
|
2010-01-27 16:21:35 +08:00
|
|
|
|
2013-05-18 18:54:59 +08:00
|
|
|
def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
|
2016-11-06 19:57:38 +08:00
|
|
|
safe_data = {}
|
|
|
|
original_keys = {}
|
|
|
|
for key, value in data.items():
|
|
|
|
safe_key = self.make_key(key, version=version)
|
|
|
|
safe_data[safe_key] = value
|
|
|
|
original_keys[safe_key] = key
|
|
|
|
failed_keys = self._cache.set_multi(safe_data, self.get_backend_timeout(timeout))
|
|
|
|
return [original_keys[k] for k in failed_keys]
|
2010-01-27 16:21:35 +08:00
|
|
|
|
2010-11-19 23:39:35 +08:00
|
|
|
def delete_many(self, keys, version=None):
|
2016-01-24 00:47:07 +08:00
|
|
|
self._cache.delete_multi(self.make_key(key, version=version) for key in keys)
|
2010-01-27 16:21:35 +08:00
|
|
|
|
|
|
|
def clear(self):
|
|
|
|
self._cache.flush_all()
|
2010-12-21 23:19:19 +08:00
|
|
|
|
2013-11-03 04:12:09 +08:00
|
|
|
|
2010-12-21 23:19:19 +08:00
|
|
|
class MemcachedCache(BaseMemcachedCache):
|
|
|
|
"An implementation of a cache binding using python-memcached"
|
|
|
|
def __init__(self, server, params):
|
|
|
|
import memcache
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(server, params, library=memcache, value_not_found_exception=ValueError)
|
2010-12-21 23:19:19 +08:00
|
|
|
|
2013-02-24 20:36:04 +08:00
|
|
|
@property
|
|
|
|
def _cache(self):
|
|
|
|
if getattr(self, '_client', None) is None:
|
2017-06-02 07:08:59 +08:00
|
|
|
client_kwargs = {'pickleProtocol': pickle.HIGHEST_PROTOCOL}
|
2016-08-31 20:12:40 +08:00
|
|
|
client_kwargs.update(self._options)
|
|
|
|
self._client = self._lib.Client(self._servers, **client_kwargs)
|
2013-02-24 20:36:04 +08:00
|
|
|
return self._client
|
|
|
|
|
2018-04-28 05:48:35 +08:00
|
|
|
def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
|
|
|
|
key = self.make_key(key, version=version)
|
|
|
|
return self._cache.touch(key, self.get_backend_timeout(timeout)) != 0
|
|
|
|
|
2013-11-03 04:12:09 +08:00
|
|
|
|
2010-12-21 23:19:19 +08:00
|
|
|
class PyLibMCCache(BaseMemcachedCache):
|
|
|
|
"An implementation of a cache binding using pylibmc"
|
|
|
|
def __init__(self, server, params):
|
|
|
|
import pylibmc
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(server, params, library=pylibmc, value_not_found_exception=pylibmc.NotFound)
|
2010-12-21 23:19:19 +08:00
|
|
|
|
2013-10-19 06:49:24 +08:00
|
|
|
@cached_property
|
2010-12-21 23:19:19 +08:00
|
|
|
def _cache(self):
|
2016-08-31 20:12:40 +08:00
|
|
|
return self._lib.Client(self._servers, **self._options)
|
2016-09-02 16:33:59 +08:00
|
|
|
|
2018-04-28 05:48:35 +08:00
|
|
|
def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
|
|
|
|
key = self.make_key(key, version=version)
|
|
|
|
if timeout == 0:
|
|
|
|
return self._cache.delete(key)
|
|
|
|
return self._cache.touch(key, self.get_backend_timeout(timeout))
|
|
|
|
|
2016-09-02 16:33:59 +08:00
|
|
|
def close(self, **kwargs):
|
|
|
|
# libmemcached manages its own connections. Don't call disconnect_all()
|
|
|
|
# as it resets the failover state and creates unnecessary reconnects.
|
|
|
|
pass
|