Trees Indices Help
Trac
Package trac :: Module cache

Source Code for Module trac.cache

 1 # -*- coding: utf-8 -*- 
 2 # 
 3 # Copyright (C) 2009 Edgewall Software 
 4 # All rights reserved. 
 5 # 
 6 # This software is licensed as described in the file COPYING, which 
 7 # you should have received as part of this distribution. The terms 
 8 # are also available at http://trac.edgewall.com/license.html. 
 9 # 
 10 # This software consists of voluntary contributions made by many 
 11 # individuals. For the exact contribution history, see the revision 
 12 # history and logs, available at http://trac.edgewall.org/. 
 13 
 14 from trac .core  import Component  
 15 from trac .util .concurrency  import ThreadLocal , threading 
 16 
 17 __all__ = ["CacheManager", "cached"] 
18 19 20 - class CachedProperty (object):
21 """Cached property descriptor""" 22
23 - def __init__ (self, retriever, id_attr=None):
24 self.retriever = retriever 25 self.__doc__ = retriever.__doc__ 26 self.id_attr = id_attr 27 self.id = None
28
29 - def __get__ (self, instance, owner):
30 if instance is None: 31 return self 32 if self.id_attr is not None: 33 id = getattr(instance, self.id_attr) 34 else: 35 id = self.id 36 if id is None: 37 id = self.id = self.make_id(owner) 38 return CacheManager (instance.env ).get (id , self.retriever, instance)
39
40 - def __delete__ (self, instance):
41 if self.id_attr is not None: 42 id = getattr(instance, self.id_attr) 43 else: 44 id = self.id 45 if id is None: 46 id = self.id = self.make_id(instance.__class__) 47 CacheManager (instance.env ).invalidate (id )
48
49 - def make_id (self, cls):
50 attr = self.retriever.__name__ 51 for base in cls.mro(): 52 if base.__dict__.get (attr) is self: 53 cls = base 54 break 55 return '%s.%s.%s' % (cls.__module__, cls.__name__, attr)
56
57 58 - def cached (fn_or_id=None):
59 """Method decorator creating a cached attribute from a data retrieval 60 method. 61 62 Accessing the cached attribute gives back the cached value. The data 63 retrieval method is called as needed by the CacheManager. Invalidating 64 the cache for this value is done by `del`eting the attribute. 65 66 The data retrieval method is called with a single argument `db` containing 67 a reference to a database connection. All data retrieval should be done 68 through this connection. 69 70 Note that the cache validity is maintained using a table in the database. 71 Cache invalidation is performed within a transaction block, and can be 72 nested within another transaction block. 73 74 The id used to identify the attribute in the database is constructed from 75 the names of the containing module, class and retriever method. If the 76 decorator is used in non-signleton (typically non-`Component`) objects, 77 an optional string specifying the name of the attribute containing the id 78 must be passed to the decorator call as follows: 79 {{{ 80 def __init__(self, env, name): 81 self.env = env 82 self._metadata_id = 'custom_id.' + name 83 84 @cached('_metadata_id') 85 def metadata(db): 86 ... 87 }}} 88 89 This decorator requires that the object on which it is used has an `env` 90 attribute containing the application `Environment`. 91 """ 92 if not hasattr(fn_or_id, '__call__'): 93 def decorator(fn): 94 return CachedProperty(fn, fn_or_id)
95 return decorator 96 else: 97 return CachedProperty(fn_or_id)
98
99 100 - class CacheManager (Component):
101 """Cache manager.""" 102 103 required = True 104
105 - def __init__ (self):
106 self._cache = {} 107 self._local = ThreadLocal (meta=None, cache =None) 108 self._lock = threading.RLock()
109 110 # Public interface 111
112 - def reset_metadata (self):
113 """Reset per-request cache metadata.""" 114 self._local.meta = self._local.cache = None
115
116 - def get (self, id, retriever, instance):
117 """Get cached or fresh data for the given id.""" 118 # Get cache metadata 119 local_meta = self._local.meta 120 local_cache = self._local.cache 121 if local_meta is None: 122 # First cache usage in this request, retrieve cache metadata 123 # from the database and make a thread-local copy of the cache 124 db = self.env .get_read_db () 125 cursor = db .cursor () 126 cursor .execute ("SELECT id,generation FROM cache") 127 self._local.meta = local_meta = dict(cursor ) 128 self._local.cache = local_cache = self._cache.copy() 129 else: 130 db = None 131 132 db_generation = local_meta.get (id , -1) 133 134 # Try the thread-local copy first 135 try: 136 (data , generation) = local_cache[id ] 137 if generation == db_generation: 138 return data 139 except KeyError: 140 pass 141 142 if db is None: 143 db = self.env .get_read_db () 144 self._lock.acquire() 145 try: 146 # Get data from the process cache 147 try: 148 (data , generation) = local_cache[id ] = self._cache[id ] 149 if generation == db_generation: 150 return data 151 except KeyError: 152 generation = None # Force retrieval from the database 153 154 # Check if the process cache has the newest version, as it may 155 # have been updated after the metadata retrieval 156 cursor = db .cursor () 157 cursor .execute ("SELECT generation FROM cache WHERE id=%s", (id ,)) 158 row = cursor .fetchone () 159 db_generation = not row and -1 or row[0] 160 if db_generation == generation: 161 return data 162 163 # Retrieve data from the database 164 data = retriever(instance, db ) 165 local_cache[id ] = self._cache[id ] = (data , db_generation) 166 local_meta[id ] = db_generation 167 return data 168 finally: 169 self._lock.release()
170
171 - def invalidate (self, id):
172 """Invalidate cached data for the given id.""" 173 db = self.env .get_read_db () # prevent deadlock 174 self._lock.acquire() 175 try: 176 # Invalidate in other processes 177 178 # The row corresponding to the cache may not exist in the table 179 # yet. 180 # - If the row exists, the UPDATE increments the generation, the 181 # SELECT returns a row and we're done. 182 # - If the row doesn't exist, the UPDATE does nothing, but starts 183 # a transaction. The SELECT then returns nothing, and we can 184 # safely INSERT a new row. 185 @self.env .with_transaction () 186 def do_invalidate(db): 187 cursor = db .cursor () 188 cursor .execute (""" 189 UPDATE cache SET generation=generation+1 WHERE id=%s 190 """, (id ,)) 191 cursor .execute ("SELECT generation FROM cache WHERE id=%s", 192 (id ,)) 193 if not cursor .fetchone (): 194 cursor .execute ("INSERT INTO cache VALUES (%s, %s)", 195 (id , 0))
196 197 # Invalidate in this process 198 self._cache.pop(id , None) 199 200 # Invalidate in this thread 201 try: 202 del self._local.cache [id ] 203 except (KeyError, TypeError): 204 pass 205 finally: 206 self._lock.release()
207
Trees Indices Help
Trac
Generated by Epydoc 3.0.1 on Mon Feb 13 23:37:28 2023 http://epydoc.sourceforge.net

AltStyle によって変換されたページ (->オリジナル) /