| 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
|
| +
|
|
|