Source

dogpile.core / dogpile / core / dogpile.py

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.

    .. versionadded:: 0.4.0
        The :class:`.Lock` class was added as a single-use object
        representing the dogpile API without dependence on
        any shared state between multiple instances.

    :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 background_runner: A callable.  If specified, this callable will be
    passed the mutex and creator callable as arguments.  Responsibility for
    releasing the mutex is delegated to this callable.  The intent is for this
    to be used to defer invocation of the creator callable until some later
    time: to run it in the background.

    """

    def __init__(self,
            mutex,
            creator,
            value_and_created_fn,
            expiretime,
            background_runner=None,
        ):
        self.mutex = mutex
        self.creator = creator
        self.value_and_created_fn = value_and_created_fn
        self.expiretime = expiretime
        self.background_runner = background_runner

    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

        backgrounded = 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.background_runner:
                    log.debug("Passing creation lock to background runner")
                    self.background_runner(self.mutex, self.creator)
                    backgrounded = True
                    return value, createdtime

            log.debug("Calling creation function")
            created = self.creator()
            return created
        finally:
            if not backgrounded:
                self.mutex.release()
                log.debug("Released creation lock")


    def __enter__(self):
        return self._enter()

    def __exit__(self, type, value, traceback):
        pass
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.