Example #1
0
    def get_or_create(self,
                      key,
                      creator,
                      expiration_time=None,
                      should_cache_fn=None):
        """Return a cached value based on the given key.

        If the value does not exist or is considered to be expired
        based on its creation time, the given
        creation function may or may not be used to recreate the value
        and persist the newly generated value in the cache.

        Whether or not the function is used depends on if the
        *dogpile lock* can be acquired or not.  If it can't, it means
        a different thread or process is already running a creation
        function for this key against the cache.  When the dogpile
        lock cannot be acquired, the method will block if no
        previous value is available, until the lock is released and
        a new value available.  If a previous value
        is available, that value is returned immediately without blocking.

        If the :meth:`.invalidate` method has been called, and
        the retrieved value's timestamp is older than the invalidation
        timestamp, the value is unconditionally prevented from
        being returned.  The method will attempt to acquire the dogpile
        lock to generate a new value, or will wait
        until the lock is released to return the new value.

        .. versionchanged:: 0.3.0
          The value is unconditionally regenerated if the creation
          time is older than the last call to :meth:`.invalidate`.

        :param key: Key to be retrieved. While it's typical for a key to be a
         string, it is ultimately passed directly down to the cache backend,
         before being optionally processed by the key_mangler function, so can
         be of any type recognized by the backend or by the key_mangler
         function, if present.

        :param creator: function which creates a new value.

        :param expiration_time: optional expiration time which will overide
         the expiration time already configured on this :class:`.CacheRegion`
         if not None.   To set no expiration, use the value -1.

        :param should_cache_fn: optional callable function which will receive
         the value returned by the "creator", and will then return True or
         False, indicating if the value should actually be cached or not.  If
         it returns False, the value is still returned, but isn't cached.
         E.g.::

            def dont_cache_none(value):
                return value is not None

            value = region.get_or_create("some key",
                                create_value,
                                should_cache_fn=dont_cache_none)

         Above, the function returns the value of create_value() if
         the cache is invalid, however if the return value is None,
         it won't be cached.

         .. versionadded:: 0.4.3

        .. seealso::

            :meth:`.CacheRegion.cache_on_arguments` - applies
            :meth:`.get_or_create` to any function using a decorator.

            :meth:`.CacheRegion.get_or_create_multi` - multiple key/value
             version

        """
        orig_key = key
        if self.key_mangler:
            key = self.key_mangler(key)

        def get_value():
            value = self.backend.get(key)
            if value is NO_VALUE or \
                value.metadata['v'] != value_version or \
                    (
                        self._hard_invalidated and
                        value.metadata["ct"] < self._hard_invalidated):
                raise NeedRegenerationException()
            ct = value.metadata["ct"]
            if self._soft_invalidated:
                if ct < self._soft_invalidated:
                    ct = time.time() - expiration_time - .0001

            return value.payload, ct

        def gen_value():
            created_value = creator()
            value = self._value(created_value)

            if not should_cache_fn or \
                    should_cache_fn(created_value):
                self.backend.set(key, value)

            return value.payload, value.metadata["ct"]

        if expiration_time is None:
            expiration_time = self.expiration_time

        if expiration_time is None and self._soft_invalidated:
            raise exception.DogpileCacheException(
                "Non-None expiration time required "
                "for soft invalidation")

        if expiration_time == -1:
            expiration_time = None

        if self.async_creation_runner:

            def async_creator(mutex):
                return self.async_creation_runner(self, orig_key, creator,
                                                  mutex)
        else:
            async_creator = None

        with Lock(self._mutex(key), gen_value, get_value, expiration_time,
                  async_creator) as value:
            return value
Example #2
0
    def get_or_create_multi(self,
                            keys,
                            creator,
                            expiration_time=None,
                            should_cache_fn=None):
        """Return a sequence of cached values based on a sequence of keys.

        The behavior for generation of values based on keys corresponds
        to that of :meth:`.Region.get_or_create`, with the exception that
        the ``creator()`` function may be asked to generate any subset of
        the given keys.   The list of keys to be generated is passed to
        ``creator()``, and ``creator()`` should return the generated values
        as a sequence corresponding to the order of the keys.

        The method uses the same approach as :meth:`.Region.get_multi`
        and :meth:`.Region.set_multi` to get and set values from the
        backend.
        
        If you are using a :class:`.CacheBackend` or :class:`.ProxyBackend` 
        that modifies values, take note this function invokes 
        ``.set_multi()`` for newly generated values using the same values it
        returns to the calling function. A correct implementation of 
        ``.set_multi()`` will not modify values in-place on the submitted
        ``mapping`` dict.

        :param keys: Sequence of keys to be retrieved.

        :param creator: function which accepts a sequence of keys and
         returns a sequence of new values.

        :param expiration_time: optional expiration time which will overide
         the expiration time already configured on this :class:`.CacheRegion`
         if not None.   To set no expiration, use the value -1.

        :param should_cache_fn: optional callable function which will receive
         each value returned by the "creator", and will then return True or
         False, indicating if the value should actually be cached or not.  If
         it returns False, the value is still returned, but isn't cached.

        .. versionadded:: 0.5.0

        .. seealso::


            :meth:`.CacheRegion.cache_multi_on_arguments`

            :meth:`.CacheRegion.get_or_create`

        """
        def get_value(key):
            value = values.get(key, NO_VALUE)

            if value is NO_VALUE or \
                value.metadata['v'] != value_version or \
                    (self._hard_invalidated and
                        value.metadata["ct"] < self._hard_invalidated):
                # dogpile.core understands a 0 here as
                # "the value is not available", e.g.
                # _has_value() will return False.
                return value.payload, 0
            else:
                ct = value.metadata["ct"]
                if self._soft_invalidated:
                    if ct < self._soft_invalidated:
                        ct = time.time() - expiration_time - .0001

                return value.payload, ct

        def gen_value():
            raise NotImplementedError()

        def async_creator(key, mutex):
            mutexes[key] = mutex

        if expiration_time is None:
            expiration_time = self.expiration_time

        if expiration_time is None and self._soft_invalidated:
            raise exception.DogpileCacheException(
                "Non-None expiration time required "
                "for soft invalidation")

        if expiration_time == -1:
            expiration_time = None

        mutexes = {}

        sorted_unique_keys = sorted(set(keys))

        if self.key_mangler:
            mangled_keys = [self.key_mangler(k) for k in sorted_unique_keys]
        else:
            mangled_keys = sorted_unique_keys

        orig_to_mangled = dict(zip(sorted_unique_keys, mangled_keys))

        values = dict(zip(mangled_keys, self.backend.get_multi(mangled_keys)))

        for orig_key, mangled_key in orig_to_mangled.items():
            with Lock(self._mutex(mangled_key),
                      gen_value,
                      lambda: get_value(mangled_key),
                      expiration_time,
                      async_creator=lambda mutex: async_creator(
                          orig_key, mutex)):
                pass
        try:
            if mutexes:
                # sort the keys, the idea is to prevent deadlocks.
                # though haven't been able to simulate one anyway.
                keys_to_get = sorted(mutexes)
                new_values = creator(*keys_to_get)

                values_w_created = dict(
                    (orig_to_mangled[k], self._value(v))
                    for k, v in zip(keys_to_get, new_values))

                if not should_cache_fn:
                    self.backend.set_multi(values_w_created)
                else:
                    self.backend.set_multi(
                        dict((k, v) for k, v in values_w_created.items()
                             if should_cache_fn(v[0])))

                values.update(values_w_created)
            return [values[orig_to_mangled[k]].payload for k in keys]
        finally:
            for mutex in mutexes.values():
                mutex.release()
Example #3
0
    def get_or_create(self, key, creator, expiration_time=None):
        """Return a cached value based on the given key.

        If the value does not exist or is considered to be expired
        based on its creation time, the given
        creation function may or may not be used to recreate the value
        and persist the newly generated value in the cache.

        Whether or not the function is used depends on if the
        *dogpile lock* can be acquired or not.  If it can't, it means
        a different thread or process is already running a creation
        function for this key against the cache.  When the dogpile
        lock cannot be acquired, the method will block if no
        previous value is available, until the lock is released and
        a new value available.  If a previous value
        is available, that value is returned immediately without blocking.

        If the :meth:`.invalidate` method has been called, and
        the retrieved value's timestamp is older than the invalidation
        timestamp, the value is unconditionally prevented from
        being returned.  The method will attempt to acquire the dogpile
        lock to generate a new value, or will wait
        until the lock is released to return the new value.

        .. versionchanged:: 0.3.0
          The value is unconditionally regenerated if the creation
          time is older than the last call to :meth:`.invalidate`.

        :param key: Key to be retrieved. While it's typical for a key to be a
         string, it is ultimately passed directly down to the cache backend,
         before being optionally processed by the key_mangler function, so can
         be of any type recognized by the backend or by the key_mangler
         function, if present.

        :param creator: function which creates a new value.
        :param expiration_time: optional expiration time which will overide
         the expiration time already configured on this :class:`.CacheRegion`
         if not None.   To set no expiration, use the value -1.

        See also:

        :meth:`.CacheRegion.cache_on_arguments` - applies :meth:`.get_or_create`
        to any function using a decorator.

        """
        if self.key_mangler:
            key = self.key_mangler(key)

        def get_value():
            value = self.backend.get(key)
            if value is NO_VALUE or \
                value.metadata['v'] != value_version or \
                    (self._invalidated and
                    value.metadata["ct"] < self._invalidated):
                raise NeedRegenerationException()
            return value.payload, value.metadata["ct"]

        def gen_value():
            value = self._value(creator())
            self.backend.set(key, value)
            return value.payload, value.metadata["ct"]

        if expiration_time is None:
            expiration_time = self.expiration_time
        with Lock(self._mutex(key), gen_value, get_value,
                  expiration_time) as value:
            return value