dogpile.core / dogpile / core / dogpile.py

import time
import logging
import threading

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".


    """

    def __init__(self,
            mutex,
            creator,
            value_and_created_fn,
            expiretime,
            threaded_creation=False,
        ):
        self.mutex = mutex
        self.creator = creator
        self.value_and_created_fn = value_and_created_fn
        self.expiretime = expiretime
        self.threaded_creation = threaded_creation

    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

        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.threaded_creation:
                    # If configured for threaded creation, give responsibility
                    # for releasing the mutex to a new thread which will
                    # invoke the creator callable on its own time.
                    def worker():
                        try:
                            self.creator()
                        finally:
                            self.mutex.release()
                            log.debug("Released creation lock")

                    log.debug("spinning off value creation thread")
                    thread = threading.Thread(target=worker)
                    thread.start()

                    # Return the stale value while the worker works.
                    return value, createdtime

            log.debug("Calling creation function")
            created = self.creator()
            return created
        finally:
            if not self.threaded_creation:
                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.