Index: third_party/google-endpoints/dogpile/lock.py |
diff --git a/third_party/google-endpoints/dogpile/lock.py b/third_party/google-endpoints/dogpile/lock.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..29f342d84fc7eac91310b8151b6753d92d5648e5 |
--- /dev/null |
+++ b/third_party/google-endpoints/dogpile/lock.py |
@@ -0,0 +1,158 @@ |
+import time |
+import logging |
+ |
+log = logging.getLogger(__name__) |
+ |
+ |
+class NeedRegenerationException(Exception): |
+ """An exception that when raised in the 'with' block, |
+ forces the 'has_value' flag to False and incurs a |
+ regeneration of the value. |
+ |
+ """ |
+ |
+NOT_REGENERATED = object() |
+ |
+ |
+class Lock(object): |
+ """Dogpile lock class. |
+ |
+ Provides an interface around an arbitrary mutex |
+ that allows one thread/process to be elected as |
+ the creator of a new value, while other threads/processes |
+ continue to return the previous version |
+ of that value. |
+ |
+ :param mutex: A mutex object that provides ``acquire()`` |
+ and ``release()`` methods. |
+ :param creator: Callable which returns a tuple of the form |
+ (new_value, creation_time). "new_value" should be a newly |
+ generated value representing completed state. "creation_time" |
+ should be a floating point time value which is relative |
+ to Python's ``time.time()`` call, representing the time |
+ at which the value was created. This time value should |
+ be associated with the created value. |
+ :param value_and_created_fn: Callable which returns |
+ a tuple of the form (existing_value, creation_time). This |
+ basically should return what the last local call to the ``creator()`` |
+ callable has returned, i.e. the value and the creation time, |
+ which would be assumed here to be from a cache. If the |
+ value is not available, the :class:`.NeedRegenerationException` |
+ exception should be thrown. |
+ :param expiretime: Expiration time in seconds. Set to |
+ ``None`` for never expires. This timestamp is compared |
+ to the creation_time result and ``time.time()`` to determine if |
+ the value returned by value_and_created_fn is "expired". |
+ :param async_creator: A callable. If specified, this callable will be |
+ passed the mutex as an argument and is responsible for releasing the mutex |
+ after it finishes some asynchronous value creation. The intent is for |
+ this to be used to defer invocation of the creator callable until some |
+ later time. |
+ |
+ """ |
+ |
+ def __init__( |
+ self, |
+ mutex, |
+ creator, |
+ value_and_created_fn, |
+ expiretime, |
+ async_creator=None, |
+ ): |
+ self.mutex = mutex |
+ self.creator = creator |
+ self.value_and_created_fn = value_and_created_fn |
+ self.expiretime = expiretime |
+ self.async_creator = async_creator |
+ |
+ def _is_expired(self, createdtime): |
+ """Return true if the expiration time is reached, or no |
+ value is available.""" |
+ |
+ return not self._has_value(createdtime) or \ |
+ ( |
+ self.expiretime is not None and |
+ time.time() - createdtime > self.expiretime |
+ ) |
+ |
+ def _has_value(self, createdtime): |
+ """Return true if the creation function has proceeded |
+ at least once.""" |
+ return createdtime > 0 |
+ |
+ def _enter(self): |
+ value_fn = self.value_and_created_fn |
+ |
+ try: |
+ value = value_fn() |
+ value, createdtime = value |
+ except NeedRegenerationException: |
+ log.debug("NeedRegenerationException") |
+ value = NOT_REGENERATED |
+ createdtime = -1 |
+ |
+ generated = self._enter_create(createdtime) |
+ |
+ if generated is not NOT_REGENERATED: |
+ generated, createdtime = generated |
+ return generated |
+ elif value is NOT_REGENERATED: |
+ try: |
+ value, createdtime = value_fn() |
+ return value |
+ except NeedRegenerationException: |
+ raise Exception("Generation function should " |
+ "have just been called by a concurrent " |
+ "thread.") |
+ else: |
+ return value |
+ |
+ def _enter_create(self, createdtime): |
+ |
+ if not self._is_expired(createdtime): |
+ return NOT_REGENERATED |
+ |
+ async = False |
+ |
+ if self._has_value(createdtime): |
+ if not self.mutex.acquire(False): |
+ log.debug("creation function in progress " |
+ "elsewhere, returning") |
+ return NOT_REGENERATED |
+ else: |
+ log.debug("no value, waiting for create lock") |
+ self.mutex.acquire() |
+ |
+ try: |
+ log.debug("value creation lock %r acquired" % self.mutex) |
+ |
+ # see if someone created the value already |
+ try: |
+ value, createdtime = self.value_and_created_fn() |
+ except NeedRegenerationException: |
+ pass |
+ else: |
+ if not self._is_expired(createdtime): |
+ log.debug("value already present") |
+ return value, createdtime |
+ elif self.async_creator: |
+ log.debug("Passing creation lock to async runner") |
+ self.async_creator(self.mutex) |
+ async = True |
+ return value, createdtime |
+ |
+ log.debug("Calling creation function") |
+ created = self.creator() |
+ return created |
+ finally: |
+ if not async: |
+ self.mutex.release() |
+ log.debug("Released creation lock") |
+ |
+ |
+ def __enter__(self): |
+ return self._enter() |
+ |
+ def __exit__(self, type, value, traceback): |
+ pass |
+ |