trac.cache – Control of cached data coherency

Trac is a server application which may involve multiple concurrent processes. The coherency of the data presented to the clients is ensured by the underlying database and its transaction handling. However, a server process will not systematically retrieve data from the database, as various in-memory caches are used for performance reasons. We could ensure the integrity of those caches in a single process in presence of multiple threads by the appropriate use of locking and by updating the caches as needed, but we also need a mechanism for invalidating the caches in the other processes.

The purpose of this module is to provide a cached decorator which can annotate a data retriever method of a class for turning it into an attribute working like a cache. This means that an access to this attribute will only call the underlying retriever method once on first access, or only once after the cache has been invalidated, even if this invalidation happened in another process.

Public API

trac.cache.cached(fn_or_attr=None)

Method decorator creating a cached attribute from a data retrieval method.

Accessing the cached attribute gives back the cached value. The data retrieval method is transparently called by the CacheManager on first use after the program start or after the cache has been invalidated. Invalidating the cache for this value is done by deleting the attribute.

Note that the cache validity is maintained using the cache table in the database. Cache invalidation is performed within a transaction block, and can be nested within another transaction block.

When the decorator is used in a class for which instances behave as singletons within the scope of a given Environment (typically Component classes), the key used to identify the attribute in the database is constructed from the names of the containing module, class and retriever method:

class WikiSystem(Component):
    @cached
    def pages(self):
        return {name for name, in self.env.db_query(
                    "SELECT DISTINCT name FROM wiki")}

Otherwise, when the decorator is used in non-“singleton” objects, a string specifying the name of an attribute containing a string unique to the instance must be passed to the decorator. This value will be appended to the key constructed from module, class and method name:

class SomeClass(object):
    def __init__(self, env, name):
        self.env = env
        self.name = name
        self._metadata_id = name

    @cached('_metadata_id')
    def metadata(self):
        ...

Note that in this case the key attribute is overwritten with a hash of the key on first access, so it should not be used for any other purpose.

In either case, this decorator requires that the object on which it is used has an env attribute containing the application Environment.

Changed in version 1.0: The data retrieval method used to be called with a single argument db containing a reference to a database connection. This is the same connection that can be retrieved via the normal db_query or db_transaction, so this is no longer needed and is not supported.

Internal API

class trac.cache.CacheManager

Bases: trac.core.Component

Cache manager.

get(id, retriever, instance)

Get cached or fresh data for the given id.

invalidate(id)

Invalidate cached data for the given id.

reset_metadata()

Reset per-request cache metadata.

The following classes are the descriptors created by the cached decorator:

class trac.cache.CachedSingletonProperty(retriever)

Bases: trac.cache.CachedPropertyBase

Cached property descriptor for classes behaving as singletons in the scope of one Environment instance.

This means there will be no more than one cache to monitor in the database for this kind of cache. Therefore, using only “static” information for the key is enough. For the same reason it is also safe to store the corresponding id as a property of the descriptor instance.

class trac.cache.CachedProperty(retriever, key_attr)

Bases: trac.cache.CachedPropertyBase

Cached property descriptor for classes having potentially multiple instances associated to a single Environment instance.

As we’ll have potentially many different caches to monitor for this kind of cache, the key needs to be augmented by a string unique to each instance of the owner class. As the resulting id will be different for each instance of the owner class, we can’t store it as a property of the descriptor class, so we store it back in the attribute used for augmenting the key (key_attr).

Both classes inherit from a common base:

class trac.cache.CachedPropertyBase(retriever)

Bases: property

Base class for cached property descriptors.

Since 1.0.2:inherits from property.
trac.cache.key_to_id()