Index: third_party/google-endpoints/dogpile/cache/backends/memcached.py |
diff --git a/third_party/google-endpoints/dogpile/cache/backends/memcached.py b/third_party/google-endpoints/dogpile/cache/backends/memcached.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6758a998024ea22fa28ce38521954292061d0d55 |
--- /dev/null |
+++ b/third_party/google-endpoints/dogpile/cache/backends/memcached.py |
@@ -0,0 +1,364 @@ |
+""" |
+Memcached Backends |
+------------------ |
+ |
+Provides backends for talking to `memcached <http://memcached.org>`_. |
+ |
+""" |
+ |
+from ..api import CacheBackend, NO_VALUE |
+from ...util import compat |
+from ... import util |
+import random |
+import time |
+ |
+__all__ = 'GenericMemcachedBackend', 'MemcachedBackend',\ |
+ 'PylibmcBackend', 'BMemcachedBackend', 'MemcachedLock' |
+ |
+ |
+class MemcachedLock(object): |
+ """Simple distributed lock using memcached. |
+ |
+ This is an adaptation of the lock featured at |
+ http://amix.dk/blog/post/19386 |
+ |
+ """ |
+ |
+ def __init__(self, client_fn, key, timeout=0): |
+ self.client_fn = client_fn |
+ self.key = "_lock" + key |
+ self.timeout = timeout |
+ |
+ def acquire(self, wait=True): |
+ client = self.client_fn() |
+ i = 0 |
+ while True: |
+ if client.add(self.key, 1, self.timeout): |
+ return True |
+ elif not wait: |
+ return False |
+ else: |
+ sleep_time = (((i + 1) * random.random()) + 2 ** i) / 2.5 |
+ time.sleep(sleep_time) |
+ if i < 15: |
+ i += 1 |
+ |
+ def release(self): |
+ client = self.client_fn() |
+ client.delete(self.key) |
+ |
+ |
+class GenericMemcachedBackend(CacheBackend): |
+ """Base class for memcached backends. |
+ |
+ This base class accepts a number of paramters |
+ common to all backends. |
+ |
+ :param url: the string URL to connect to. Can be a single |
+ string or a list of strings. This is the only argument |
+ that's required. |
+ :param distributed_lock: boolean, when True, will use a |
+ memcached-lock as the dogpile lock (see :class:`.MemcachedLock`). |
+ Use this when multiple |
+ processes will be talking to the same memcached instance. |
+ When left at False, dogpile will coordinate on a regular |
+ threading mutex. |
+ :param lock_timeout: integer, number of seconds after acquiring a lock that |
+ memcached should expire it. This argument is only valid when |
+ ``distributed_lock`` is ``True``. |
+ |
+ .. versionadded:: 0.5.7 |
+ |
+ :param memcached_expire_time: integer, when present will |
+ be passed as the ``time`` parameter to ``pylibmc.Client.set``. |
+ This is used to set the memcached expiry time for a value. |
+ |
+ .. note:: |
+ |
+ This parameter is **different** from Dogpile's own |
+ ``expiration_time``, which is the number of seconds after |
+ which Dogpile will consider the value to be expired. |
+ When Dogpile considers a value to be expired, |
+ it **continues to use the value** until generation |
+ of a new value is complete, when using |
+ :meth:`.CacheRegion.get_or_create`. |
+ Therefore, if you are setting ``memcached_expire_time``, you'll |
+ want to make sure it is greater than ``expiration_time`` |
+ by at least enough seconds for new values to be generated, |
+ else the value won't be available during a regeneration, |
+ forcing all threads to wait for a regeneration each time |
+ a value expires. |
+ |
+ The :class:`.GenericMemachedBackend` uses a ``threading.local()`` |
+ object to store individual client objects per thread, |
+ as most modern memcached clients do not appear to be inherently |
+ threadsafe. |
+ |
+ In particular, ``threading.local()`` has the advantage over pylibmc's |
+ built-in thread pool in that it automatically discards objects |
+ associated with a particular thread when that thread ends. |
+ |
+ """ |
+ |
+ set_arguments = {} |
+ """Additional arguments which will be passed |
+ to the :meth:`set` method.""" |
+ |
+ def __init__(self, arguments): |
+ self._imports() |
+ # using a plain threading.local here. threading.local |
+ # automatically deletes the __dict__ when a thread ends, |
+ # so the idea is that this is superior to pylibmc's |
+ # own ThreadMappedPool which doesn't handle this |
+ # automatically. |
+ self.url = util.to_list(arguments['url']) |
+ self.distributed_lock = arguments.get('distributed_lock', False) |
+ self.lock_timeout = arguments.get('lock_timeout', 0) |
+ self.memcached_expire_time = arguments.get( |
+ 'memcached_expire_time', 0) |
+ |
+ def has_lock_timeout(self): |
+ return self.lock_timeout != 0 |
+ |
+ def _imports(self): |
+ """client library imports go here.""" |
+ raise NotImplementedError() |
+ |
+ def _create_client(self): |
+ """Creation of a Client instance goes here.""" |
+ raise NotImplementedError() |
+ |
+ @util.memoized_property |
+ def _clients(self): |
+ backend = self |
+ |
+ class ClientPool(compat.threading.local): |
+ def __init__(self): |
+ self.memcached = backend._create_client() |
+ |
+ return ClientPool() |
+ |
+ @property |
+ def client(self): |
+ """Return the memcached client. |
+ |
+ This uses a threading.local by |
+ default as it appears most modern |
+ memcached libs aren't inherently |
+ threadsafe. |
+ |
+ """ |
+ return self._clients.memcached |
+ |
+ def get_mutex(self, key): |
+ if self.distributed_lock: |
+ return MemcachedLock(lambda: self.client, key, |
+ timeout=self.lock_timeout) |
+ else: |
+ return None |
+ |
+ def get(self, key): |
+ value = self.client.get(key) |
+ if value is None: |
+ return NO_VALUE |
+ else: |
+ return value |
+ |
+ def get_multi(self, keys): |
+ values = self.client.get_multi(keys) |
+ return [ |
+ NO_VALUE if key not in values |
+ else values[key] for key in keys |
+ ] |
+ |
+ def set(self, key, value): |
+ self.client.set( |
+ key, |
+ value, |
+ **self.set_arguments |
+ ) |
+ |
+ def set_multi(self, mapping): |
+ self.client.set_multi( |
+ mapping, |
+ **self.set_arguments |
+ ) |
+ |
+ def delete(self, key): |
+ self.client.delete(key) |
+ |
+ def delete_multi(self, keys): |
+ self.client.delete_multi(keys) |
+ |
+ |
+class MemcacheArgs(object): |
+ """Mixin which provides support for the 'time' argument to set(), |
+ 'min_compress_len' to other methods. |
+ |
+ """ |
+ def __init__(self, arguments): |
+ self.min_compress_len = arguments.get('min_compress_len', 0) |
+ |
+ self.set_arguments = {} |
+ if "memcached_expire_time" in arguments: |
+ self.set_arguments["time"] = arguments["memcached_expire_time"] |
+ if "min_compress_len" in arguments: |
+ self.set_arguments["min_compress_len"] = \ |
+ arguments["min_compress_len"] |
+ super(MemcacheArgs, self).__init__(arguments) |
+ |
+pylibmc = None |
+ |
+ |
+class PylibmcBackend(MemcacheArgs, GenericMemcachedBackend): |
+ """A backend for the |
+ `pylibmc <http://sendapatch.se/projects/pylibmc/index.html>`_ |
+ memcached client. |
+ |
+ A configuration illustrating several of the optional |
+ arguments described in the pylibmc documentation:: |
+ |
+ from dogpile.cache import make_region |
+ |
+ region = make_region().configure( |
+ 'dogpile.cache.pylibmc', |
+ expiration_time = 3600, |
+ arguments = { |
+ 'url':["127.0.0.1"], |
+ 'binary':True, |
+ 'behaviors':{"tcp_nodelay": True,"ketama":True} |
+ } |
+ ) |
+ |
+ Arguments accepted here include those of |
+ :class:`.GenericMemcachedBackend`, as well as |
+ those below. |
+ |
+ :param binary: sets the ``binary`` flag understood by |
+ ``pylibmc.Client``. |
+ :param behaviors: a dictionary which will be passed to |
+ ``pylibmc.Client`` as the ``behaviors`` parameter. |
+ :param min_compress_len: Integer, will be passed as the |
+ ``min_compress_len`` parameter to the ``pylibmc.Client.set`` |
+ method. |
+ |
+ """ |
+ |
+ def __init__(self, arguments): |
+ self.binary = arguments.get('binary', False) |
+ self.behaviors = arguments.get('behaviors', {}) |
+ super(PylibmcBackend, self).__init__(arguments) |
+ |
+ def _imports(self): |
+ global pylibmc |
+ import pylibmc # noqa |
+ |
+ def _create_client(self): |
+ return pylibmc.Client( |
+ self.url, |
+ binary=self.binary, |
+ behaviors=self.behaviors |
+ ) |
+ |
+memcache = None |
+ |
+ |
+class MemcachedBackend(MemcacheArgs, GenericMemcachedBackend): |
+ """A backend using the standard |
+ `Python-memcached <http://www.tummy.com/Community/software/\ |
+ python-memcached/>`_ |
+ library. |
+ |
+ Example:: |
+ |
+ from dogpile.cache import make_region |
+ |
+ region = make_region().configure( |
+ 'dogpile.cache.memcached', |
+ expiration_time = 3600, |
+ arguments = { |
+ 'url':"127.0.0.1:11211" |
+ } |
+ ) |
+ |
+ """ |
+ def _imports(self): |
+ global memcache |
+ import memcache # noqa |
+ |
+ def _create_client(self): |
+ return memcache.Client(self.url) |
+ |
+ |
+bmemcached = None |
+ |
+ |
+class BMemcachedBackend(GenericMemcachedBackend): |
+ """A backend for the |
+ `python-binary-memcached <https://github.com/jaysonsantos/\ |
+ python-binary-memcached>`_ |
+ memcached client. |
+ |
+ This is a pure Python memcached client which |
+ includes the ability to authenticate with a memcached |
+ server using SASL. |
+ |
+ A typical configuration using username/password:: |
+ |
+ from dogpile.cache import make_region |
+ |
+ region = make_region().configure( |
+ 'dogpile.cache.bmemcached', |
+ expiration_time = 3600, |
+ arguments = { |
+ 'url':["127.0.0.1"], |
+ 'username':'scott', |
+ 'password':'tiger' |
+ } |
+ ) |
+ |
+ Arguments which can be passed to the ``arguments`` |
+ dictionary include: |
+ |
+ :param username: optional username, will be used for |
+ SASL authentication. |
+ :param password: optional password, will be used for |
+ SASL authentication. |
+ |
+ """ |
+ def __init__(self, arguments): |
+ self.username = arguments.get('username', None) |
+ self.password = arguments.get('password', None) |
+ super(BMemcachedBackend, self).__init__(arguments) |
+ |
+ def _imports(self): |
+ global bmemcached |
+ import bmemcached |
+ |
+ class RepairBMemcachedAPI(bmemcached.Client): |
+ """Repairs BMemcached's non-standard method |
+ signatures, which was fixed in BMemcached |
+ ef206ed4473fec3b639e. |
+ |
+ """ |
+ |
+ def add(self, key, value, timeout=0): |
+ try: |
+ return super(RepairBMemcachedAPI, self).add( |
+ key, value, timeout) |
+ except ValueError: |
+ return False |
+ |
+ self.Client = RepairBMemcachedAPI |
+ |
+ def _create_client(self): |
+ return self.Client( |
+ self.url, |
+ username=self.username, |
+ password=self.password |
+ ) |
+ |
+ def delete_multi(self, keys): |
+ """python-binary-memcached api does not implements delete_multi""" |
+ for key in keys: |
+ self.delete(key) |