Development of an internal social media platform with personalised dashboards for students
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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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. new_keys = [self.make_key(x, version=version) for x in keys]
  72. ret = self._cache.get_multi(new_keys)
  73. if ret:
  74. m = dict(zip(new_keys, keys))
  75. return {m[k]: v for k, v in ret.items()}
  76. return ret
  77. def close(self, **kwargs):
  78. # Many clients don't clean up connections properly.
  79. self._cache.disconnect_all()
  80. def incr(self, key, delta=1, version=None):
  81. key = self.make_key(key, version=version)
  82. # memcached doesn't support a negative delta
  83. if delta < 0:
  84. return self._cache.decr(key, -delta)
  85. try:
  86. val = self._cache.incr(key, delta)
  87. # python-memcache responds to incr on nonexistent keys by
  88. # raising a ValueError, pylibmc by raising a pylibmc.NotFound
  89. # and Cmemcache returns None. In all cases,
  90. # we should raise a ValueError though.
  91. except self.LibraryValueNotFoundException:
  92. val = None
  93. if val is None:
  94. raise ValueError("Key '%s' not found" % key)
  95. return val
  96. def decr(self, key, delta=1, version=None):
  97. key = self.make_key(key, version=version)
  98. # memcached doesn't support a negative delta
  99. if delta < 0:
  100. return self._cache.incr(key, -delta)
  101. try:
  102. val = self._cache.decr(key, delta)
  103. # python-memcache responds to incr on nonexistent keys by
  104. # raising a ValueError, pylibmc by raising a pylibmc.NotFound
  105. # and Cmemcache returns None. In all cases,
  106. # we should raise a ValueError though.
  107. except self.LibraryValueNotFoundException:
  108. val = None
  109. if val is None:
  110. raise ValueError("Key '%s' not found" % key)
  111. return val
  112. def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
  113. safe_data = {}
  114. original_keys = {}
  115. for key, value in data.items():
  116. safe_key = self.make_key(key, version=version)
  117. safe_data[safe_key] = value
  118. original_keys[safe_key] = key
  119. failed_keys = self._cache.set_multi(safe_data, self.get_backend_timeout(timeout))
  120. return [original_keys[k] for k in failed_keys]
  121. def delete_many(self, keys, version=None):
  122. self._cache.delete_multi(self.make_key(key, version=version) for key in keys)
  123. def clear(self):
  124. self._cache.flush_all()
  125. class MemcachedCache(BaseMemcachedCache):
  126. "An implementation of a cache binding using python-memcached"
  127. def __init__(self, server, params):
  128. import memcache
  129. super().__init__(server, params, library=memcache, value_not_found_exception=ValueError)
  130. @property
  131. def _cache(self):
  132. if getattr(self, '_client', None) is None:
  133. client_kwargs = {'pickleProtocol': pickle.HIGHEST_PROTOCOL}
  134. client_kwargs.update(self._options)
  135. self._client = self._lib.Client(self._servers, **client_kwargs)
  136. return self._client
  137. def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
  138. key = self.make_key(key, version=version)
  139. return self._cache.touch(key, self.get_backend_timeout(timeout)) != 0
  140. class PyLibMCCache(BaseMemcachedCache):
  141. "An implementation of a cache binding using pylibmc"
  142. def __init__(self, server, params):
  143. import pylibmc
  144. super().__init__(server, params, library=pylibmc, value_not_found_exception=pylibmc.NotFound)
  145. @cached_property
  146. def _cache(self):
  147. return self._lib.Client(self._servers, **self._options)
  148. def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
  149. key = self.make_key(key, version=version)
  150. if timeout == 0:
  151. return self._cache.delete(key)
  152. return self._cache.touch(key, self.get_backend_timeout(timeout))
  153. def close(self, **kwargs):
  154. # libmemcached manages its own connections. Don't call disconnect_all()
  155. # as it resets the failover state and creates unnecessary reconnects.
  156. pass