0001"""
0002This implements the instance caching in SQLObject. Caching is
0003relatively aggressive. All objects are retained so long as they are
0004in memory, by keeping weak references to objects. We also keep other
0005objects in a cache that doesn't allow them to be garbage collected
0006(unless caching is turned off).
0007"""
0008
0009import threading
0010from weakref import ref
0011from time import time as now
0012
0013class CacheFactory(object):
0014
0015 """
0016 CacheFactory caches object creation. Each object should be
0017 referenced by a single hashable ID (note tuples of hashable
0018 values are also hashable).
0019
0020 """
0021
0022 def __init__(self, cullFrequency=100, cullFraction=2,
0023 cache=True):
0024 """
0025 Every cullFrequency times that an item is retrieved from
0026 this cache, the cull method is called.
0027
0028 The cull method then expires an arbitrary fraction of
0029 the cached objects. The idea is at no time will the cache
0030 be entirely emptied, placing a potentially high load at that
0031 moment, but everything object will have its time to go
0032 eventually. The fraction is given as an integer, and one
0033 in that many objects are expired (i.e., the default is 1/2
0034 of objects are expired).
0035
0036 By setting cache to False, items won't be cached.
0037
0038 However, in all cases a weak reference is kept to created
0039 objects, and if the object hasn't been garbage collected
0040 it will be returned.
0041 """
0042
0043 self.cullFrequency = cullFrequency
0044 self.cullCount = 0
0045 self.cullOffset = 0
0046 self.cullFraction = cullFraction
0047 self.doCache = cache
0048
0049 if self.doCache:
0050 self.cache = {}
0051 self.expiredCache = {}
0052 self.lock = threading.Lock()
0053
0054 def tryGet(self, id):
0055 """
0056 This returns None, or the object in cache.
0057 """
0058 value = self.expiredCache.get(id)
0059 if value:
0060
0061 return value()
0062 if not self.doCache:
0063 return None
0064 return self.cache.get(id)
0065
0066 def get(self, id):
0067 """
0068 This method can cause deadlocks! tryGet is safer
0069
0070 This returns the object found in cache, or None. If None,
0071 then the cache will remain locked! This is so that the
0072 calling function can create the object in a threadsafe manner
0073 before releasing the lock. You should use this like (note
0074 that ``cache`` is actually a CacheSet object in this
0075 example)::
0076
0077 obj = cache.get(some_id, my_class)
0078 if obj is None:
0079 try:
0080 obj = create_object(some_id)
0081 cache.put(some_id, my_class, obj)
0082 finally:
0083 cache.finishPut(cls)
0084
0085 This method checks both the main cache (which retains
0086 references) and the 'expired' cache, which retains only weak
0087 references.
0088 """
0089
0090 if self.doCache:
0091 if self.cullCount > self.cullFrequency:
0092
0093
0094
0095
0096 self.cullCount = 0
0097 self.cull()
0098 else:
0099 self.cullCount = self.cullCount + 1
0100
0101 try:
0102 return self.cache[id]
0103 except KeyError:
0104 pass
0105 self.lock.acquire()
0106 try:
0107 val = self.cache[id]
0108 except KeyError:
0109 pass
0110 else:
0111 self.lock.release()
0112 return val
0113 try:
0114 val = self.expiredCache[id]()
0115 except KeyError:
0116 return None
0117 else:
0118 del self.expiredCache[id]
0119 if val is None:
0120 return None
0121 self.cache[id] = val
0122 self.lock.release()
0123 return val
0124
0125 else:
0126 try:
0127 val = self.expiredCache[id]()
0128 if val is not None:
0129 return val
0130 except KeyError:
0131 pass
0132 self.lock.acquire()
0133 try:
0134 val = self.expiredCache[id]()
0135 except KeyError:
0136 return None
0137 else:
0138 if val is None:
0139 del self.expiredCache[id]
0140 return None
0141 self.lock.release()
0142 return val
0143
0144 def put(self, id, obj):
0145 """
0146 Puts an object into the cache. Should only be called after
0147 .get(), so that duplicate objects don't end up in the cache.
0148 """
0149 if self.doCache:
0150 self.cache[id] = obj
0151 else:
0152 self.expiredCache[id] = ref(obj)
0153
0154 def finishPut(self):
0155 """
0156 Releases the lock that is retained when .get() is called and
0157 returns None.
0158 """
0159 self.lock.release()
0160
0161 def created(self, id, obj):
0162 """
0163 Inserts and object into the cache. Should be used when no one
0164 else knows about the object yet, so there cannot be any object
0165 already in the cache. After a database INSERT is an example
0166 of this situation.
0167 """
0168 if self.doCache:
0169 if self.cullCount > self.cullFrequency:
0170
0171
0172
0173
0174 self.cullCount = 0
0175 self.cull()
0176 else:
0177 self.cullCount = self.cullCount + 1
0178 self.cache[id] = obj
0179 else:
0180 self.expiredCache[id] = ref(obj)
0181
0182 def cull(self):
0183 """
0184 Runs through the cache and expires objects. E.g., if
0185 ``cullFraction`` is 3, then every third object is moved to
0186 the 'expired' (aka weakref) cache.
0187 """
0188 self.lock.acquire()
0189 try:
0190 keys = self.cache.keys()
0191 for i in xrange(self.cullOffset, len(keys), self.cullFraction):
0192 id = keys[i]
0193 self.expiredCache[id] = ref(self.cache[id])
0194 del self.cache[id]
0195
0196
0197
0198 self.cullOffset = (self.cullOffset + 1) % self.cullFraction
0199 finally:
0200 self.lock.release()
0201
0202 def clear(self):
0203 """
0204 Removes everything from the cache. Warning! This can cause
0205 duplicate objects in memory.
0206 """
0207 if self.doCache:
0208 self.cache.clear()
0209 self.expiredCache.clear()
0210
0211 def expire(self, id):
0212 """
0213 Expires a single object. Typically called after a delete.
0214 Doesn't even keep a weakref. (@@: bad name?)
0215 """
0216 if not self.doCache:
0217 return
0218 self.lock.acquire()
0219 try:
0220 if self.cache.has_key(id):
0221 del self.cache[id]
0222 if self.expiredCache.has_key(id):
0223 del self.expiredCache[id]
0224 finally:
0225 self.lock.release()
0226
0227 def expireAll(self):
0228 """
0229 Expires all objects, moving them all into the expired/weakref
0230 cache.
0231 """
0232 if not self.doCache:
0233 return
0234 self.lock.acquire()
0235 try:
0236 for key, value in self.cache.items():
0237 self.expiredCache[key] = ref(value)
0238 self.cache = {}
0239 finally:
0240 self.lock.release()
0241
0242 def allIDs(self):
0243 """
0244 Returns the IDs of all objects in the cache.
0245 """
0246 if self.doCache:
0247 all = self.cache.keys()
0248 else:
0249 all = []
0250 for id, value in self.expiredCache.items():
0251 if value():
0252 all.append(id)
0253 return all
0254
0255 def getAll(self):
0256 """
0257 Return all the objects in the cache.
0258 """
0259 if self.doCache:
0260 all = self.cache.values()
0261 else:
0262 all = []
0263 for value in self.expiredCache.values():
0264 if value():
0265 all.append(value())
0266 return all
0267
0268class CacheSet(object):
0269
0270 """
0271 A CacheSet is used to collect and maintain a series of caches. In
0272 SQLObject, there is one CacheSet per connection, and one Cache
0273 in the CacheSet for each class, since IDs are not unique across
0274 classes. It contains methods similar to Cache, but that take
0275 a ``cls`` argument.
0276 """
0277
0278 def __init__(self, *args, **kw):
0279 self.caches = {}
0280 self.args = args
0281 self.kw = kw
0282
0283 def get(self, id, cls):
0284 try:
0285 return self.caches[cls.__name__].get(id)
0286 except KeyError:
0287 self.caches[cls.__name__] = CacheFactory(*self.args, **self.kw)
0288 return self.caches[cls.__name__].get(id)
0289
0290 def put(self, id, cls, obj):
0291 self.caches[cls.__name__].put(id, obj)
0292
0293 def finishPut(self, cls):
0294 self.caches[cls.__name__].finishPut()
0295
0296 def created(self, id, cls, obj):
0297 try:
0298 self.caches[cls.__name__].created(id, obj)
0299 except KeyError:
0300 self.caches[cls.__name__] = CacheFactory(*self.args, **self.kw)
0301 self.caches[cls.__name__].created(id, obj)
0302
0303 def expire(self, id, cls):
0304 try:
0305 self.caches[cls.__name__].expire(id)
0306 except KeyError:
0307 pass
0308
0309 def clear(self, cls=None):
0310 if cls is None:
0311 for cache in self.caches.values():
0312 cache.clear()
0313 elif self.caches.has_key(cls.__name__):
0314 self.caches[cls.__name__].clear()
0315
0316 def tryGet(self, id, cls):
0317 return self.tryGetByName(id, cls.__name__)
0318
0319 def tryGetByName(self, id, clsname):
0320 try:
0321 return self.caches[clsname].tryGet(id)
0322 except KeyError:
0323 return None
0324
0325 def allIDs(self, cls):
0326 try:
0327 self.caches[cls.__name__].allIDs()
0328 except KeyError:
0329 return []
0330
0331 def allSubCaches(self):
0332 return self.caches.values()
0333
0334 def allSubCachesByClassNames(self):
0335 return self.caches
0336
0337 def weakrefAll(self, cls=None):
0338 """
0339 Move all objects in the cls (or if not given, then in all
0340 classes) to the weakref dictionary, where they can be
0341 collected.
0342 """
0343 if cls is None:
0344 for cache in self.caches.values():
0345 cache.expireAll()
0346 elif self.caches.has_key(cls.__name__):
0347 self.caches[cls.__name__].expireAll()
0348
0349 def getAll(self, cls=None):
0350 """
0351 Returns all instances in the cache for the given class or all
0352 classes.
0353 """
0354 if cls is None:
0355 results = []
0356 for cache in self.caches.values():
0357 results.extend(cache.getAll())
0358 return results
0359 elif cls.__name__ in self.caches:
0360 return self.caches[cls.__name__].getAll()
0361 else:
0362 return []