You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

memcached.py 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. "Memcached cache backend"
  2. import pickle
  3. import re
  4. import time
  5. from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
  6. from django.utils.functional import cached_property
  7. class BaseMemcachedCache(BaseCache):
  8. def __init__(self, server, params, library, value_not_found_exception):
  9. super().__init__(params)
  10. if isinstance(server, str):
  11. self._servers = re.split('[;,]', server)
  12. else:
  13. self._servers = server
  14. # The exception type to catch from the underlying library for a key
  15. # that was not found. This is a ValueError for python-memcache,
  16. # pylibmc.NotFound for pylibmc, and cmemcache will return None without
  17. # raising an exception.
  18. self.LibraryValueNotFoundException = value_not_found_exception
  19. self._lib = library
  20. self._options = params.get('OPTIONS') or {}
  21. @property
  22. def _cache(self):
  23. """
  24. Implement transparent thread-safe access to a memcached client.
  25. """
  26. if getattr(self, '_client', None) is None:
  27. self._client = self._lib.Client(self._servers, **self._options)
  28. return self._client
  29. def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT):
  30. """
  31. Memcached deals with long (> 30 days) timeouts in a special
  32. way. Call this function to obtain a safe value for your timeout.
  33. """
  34. if timeout == DEFAULT_TIMEOUT:
  35. timeout = self.default_timeout
  36. if timeout is None:
  37. # Using 0 in memcache sets a non-expiring timeout.
  38. return 0
  39. elif int(timeout) == 0:
  40. # Other cache backends treat 0 as set-and-expire. To achieve this
  41. # in memcache backends, a negative timeout must be passed.
  42. timeout = -1
  43. if timeout > 2592000: # 60*60*24*30, 30 days
  44. # See https://github.com/memcached/memcached/wiki/Programming#expiration
  45. # "Expiration times can be set from 0, meaning "never expire", to
  46. # 30 days. Any time higher than 30 days is interpreted as a Unix
  47. # timestamp date. If you want to expire an object on January 1st of
  48. # next year, this is how you do that."
  49. #
  50. # This means that we have to switch to absolute timestamps.
  51. timeout += int(time.time())
  52. return int(timeout)
  53. def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
  54. key = self.make_key(key, version=version)
  55. return self._cache.add(key, value, self.get_backend_timeout(timeout))
  56. def get(self, key, default=None, version=None):
  57. key = self.make_key(key, version=version)
  58. val = self._cache.get(key)
  59. if val is None:
  60. return default
  61. return val
  62. def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
  63. key = self.make_key(key, version=version)
  64. if not self._cache.set(key, value, self.get_backend_timeout(timeout)):
  65. # make sure the key doesn't keep its old value in case of failure to set (memcached's 1MB limit)
  66. self._cache.delete(key)
  67. def delete(self, key, version=None):
  68. key = self.make_key(key, version=version)
  69. self._cache.delete(key)
  70. def get_many(self, keys, version=None):
  71. key_map = {self.make_key(key, version=version): key for key in keys}
  72. ret = self._cache.get_multi(key_map.keys())
  73. return {key_map[k]: v for k, v in ret.items()}
  74. def close(self, **kwargs):
  75. # Many clients don't clean up connections properly.
  76. self._cache.disconnect_all()
  77. def incr(self, key, delta=1, version=None):
  78. key = self.make_key(key, version=version)
  79. # memcached doesn't support a negative delta
  80. if delta < 0:
  81. return self._cache.decr(key, -delta)
  82. try:
  83. val = self._cache.incr(key, delta)
  84. # python-memcache responds to incr on nonexistent keys by
  85. # raising a ValueError, pylibmc by raising a pylibmc.NotFound
  86. # and Cmemcache returns None. In all cases,
  87. # we should raise a ValueError though.
  88. except self.LibraryValueNotFoundException:
  89. val = None
  90. if val is None:
  91. raise ValueError("Key '%s' not found" % key)
  92. return val
  93. def decr(self, key, delta=1, version=None):
  94. key = self.make_key(key, version=version)
  95. # memcached doesn't support a negative delta
  96. if delta < 0:
  97. return self._cache.incr(key, -delta)
  98. try:
  99. val = self._cache.decr(key, delta)
  100. # python-memcache responds to incr on nonexistent keys by
  101. # raising a ValueError, pylibmc by raising a pylibmc.NotFound
  102. # and Cmemcache returns None. In all cases,
  103. # we should raise a ValueError though.
  104. except self.LibraryValueNotFoundException:
  105. val = None
  106. if val is None:
  107. raise ValueError("Key '%s' not found" % key)
  108. return val
  109. def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
  110. safe_data = {}
  111. original_keys = {}
  112. for key, value in data.items():
  113. safe_key = self.make_key(key, version=version)
  114. safe_data[safe_key] = value
  115. original_keys[safe_key] = key
  116. failed_keys = self._cache.set_multi(safe_data, self.get_backend_timeout(timeout))
  117. return [original_keys[k] for k in failed_keys]
  118. def delete_many(self, keys, version=None):
  119. self._cache.delete_multi(self.make_key(key, version=version) for key in keys)
  120. def clear(self):
  121. self._cache.flush_all()
  122. class MemcachedCache(BaseMemcachedCache):
  123. "An implementation of a cache binding using python-memcached"
  124. def __init__(self, server, params):
  125. import memcache
  126. super().__init__(server, params, library=memcache, value_not_found_exception=ValueError)
  127. @property
  128. def _cache(self):
  129. if getattr(self, '_client', None) is None:
  130. client_kwargs = {'pickleProtocol': pickle.HIGHEST_PROTOCOL}
  131. client_kwargs.update(self._options)
  132. self._client = self._lib.Client(self._servers, **client_kwargs)
  133. return self._client
  134. def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
  135. key = self.make_key(key, version=version)
  136. return self._cache.touch(key, self.get_backend_timeout(timeout)) != 0
  137. class PyLibMCCache(BaseMemcachedCache):
  138. "An implementation of a cache binding using pylibmc"
  139. def __init__(self, server, params):
  140. import pylibmc
  141. super().__init__(server, params, library=pylibmc, value_not_found_exception=pylibmc.NotFound)
  142. @cached_property
  143. def _cache(self):
  144. return self._lib.Client(self._servers, **self._options)
  145. def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
  146. key = self.make_key(key, version=version)
  147. if timeout == 0:
  148. return self._cache.delete(key)
  149. return self._cache.touch(key, self.get_backend_timeout(timeout))
  150. def close(self, **kwargs):
  151. # libmemcached manages its own connections. Don't call disconnect_all()
  152. # as it resets the failover state and creates unnecessary reconnects.
  153. pass