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            # it's actually a weakref:
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                # Two threads could hit the cull in a row, but
0093                # that's not so bad.  At least by setting cullCount
0094                # back to zero right away we avoid this.  The cull
0095                # method has a lock, so it's threadsafe.
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                # Two threads could hit the cull in a row, but
0171                # that's not so bad.  At least by setting cullCount
0172                # back to zero right away we avoid this.  The cull
0173                # method has a lock, so it's threadsafe.
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        """Runs through the cache and expires objects
0184
0185        E.g., if ``cullFraction`` is 3, then every third object is moved to
0186        the 'expired' (aka weakref) cache.
0187
0188        """
0189        self.lock.acquire()
0190        try:
0191            #remove dead references from the expired cache
0192            keys = self.expiredCache.keys()
0193            for key in keys:
0194                if self.expiredCache[key]() is None:
0195                    self.expiredCache.pop(key, None)
0196
0197            keys = self.cache.keys()
0198            for i in xrange(self.cullOffset, len(keys), self.cullFraction):
0199                id = keys[i]
0200                # create a weakref, then remove from the cache
0201                obj = ref(self.cache[id])
0202                del self.cache[id]
0203
0204                #the object may have been gc'd when removed from the cache
0205                #above, no need to place in expiredCache
0206                if obj() is not None:
0207                    self.expiredCache[id] = obj
0208            # This offset tries to balance out which objects we
0209            # expire, so no object will just hang out in the cache
0210            # forever.
0211            self.cullOffset = (self.cullOffset + 1) % self.cullFraction
0212        finally:
0213            self.lock.release()
0214
0215    def clear(self):
0216        """
0217        Removes everything from the cache.  Warning!  This can cause
0218        duplicate objects in memory.
0219        """
0220        if self.doCache:
0221            self.cache.clear()
0222        self.expiredCache.clear()
0223
0224    def expire(self, id):
0225        """
0226        Expires a single object.  Typically called after a delete.
0227        Doesn't even keep a weakref.  (@@: bad name?)
0228        """
0229        if not self.doCache:
0230            return
0231        self.lock.acquire()
0232        try:
0233            if id in self.cache:
0234                del self.cache[id]
0235            if id in self.expiredCache:
0236                del self.expiredCache[id]
0237        finally:
0238            self.lock.release()
0239
0240    def expireAll(self):
0241        """
0242        Expires all objects, moving them all into the expired/weakref
0243        cache.
0244        """
0245        if not self.doCache:
0246            return
0247        self.lock.acquire()
0248        try:
0249            for key, value in self.cache.items():
0250                self.expiredCache[key] = ref(value)
0251            self.cache = {}
0252        finally:
0253            self.lock.release()
0254
0255    def allIDs(self):
0256        """
0257        Returns the IDs of all objects in the cache.
0258        """
0259        if self.doCache:
0260            all = self.cache.keys()
0261        else:
0262            all = []
0263        for id, value in self.expiredCache.items():
0264            if value():
0265                all.append(id)
0266        return all
0267
0268    def getAll(self):
0269        """
0270        Return all the objects in the cache.
0271        """
0272        if self.doCache:
0273            all = self.cache.values()
0274        else:
0275            all = []
0276        for value in self.expiredCache.values():
0277            if value():
0278                all.append(value())
0279        return all
0280
0281class CacheSet(object):
0282
0283    """
0284    A CacheSet is used to collect and maintain a series of caches.  In
0285    SQLObject, there is one CacheSet per connection, and one Cache
0286    in the CacheSet for each class, since IDs are not unique across
0287    classes.  It contains methods similar to Cache, but that take
0288    a ``cls`` argument.
0289    """
0290
0291    def __init__(self, *args, **kw):
0292        self.caches = {}
0293        self.args = args
0294        self.kw = kw
0295
0296    def get(self, id, cls):
0297        try:
0298            return self.caches[cls.__name__].get(id)
0299        except KeyError:
0300            self.caches[cls.__name__] = CacheFactory(*self.args, **self.kw)
0301            return self.caches[cls.__name__].get(id)
0302
0303    def put(self, id, cls, obj):
0304        self.caches[cls.__name__].put(id, obj)
0305
0306    def finishPut(self, cls):
0307        self.caches[cls.__name__].finishPut()
0308
0309    def created(self, id, cls, obj):
0310        try:
0311            self.caches[cls.__name__].created(id, obj)
0312        except KeyError:
0313            self.caches[cls.__name__] = CacheFactory(*self.args, **self.kw)
0314            self.caches[cls.__name__].created(id, obj)
0315
0316    def expire(self, id, cls):
0317        try:
0318            self.caches[cls.__name__].expire(id)
0319        except KeyError:
0320            pass
0321
0322    def clear(self, cls=None):
0323        if cls is None:
0324            for cache in self.caches.values():
0325                cache.clear()
0326        elif cls.__name__ in self.caches:
0327            self.caches[cls.__name__].clear()
0328
0329    def tryGet(self, id, cls):
0330        return self.tryGetByName(id, cls.__name__)
0331
0332    def tryGetByName(self, id, clsname):
0333        try:
0334            return self.caches[clsname].tryGet(id)
0335        except KeyError:
0336            return None
0337
0338    def allIDs(self, cls):
0339        try:
0340            self.caches[cls.__name__].allIDs()
0341        except KeyError:
0342            return []
0343
0344    def allSubCaches(self):
0345        return self.caches.values()
0346
0347    def allSubCachesByClassNames(self):
0348        return self.caches
0349
0350    def weakrefAll(self, cls=None):
0351        """
0352        Move all objects in the cls (or if not given, then in all
0353        classes) to the weakref dictionary, where they can be
0354        collected.
0355        """
0356        if cls is None:
0357            for cache in self.caches.values():
0358                cache.expireAll()
0359        elif cls.__name__ in self.caches:
0360            self.caches[cls.__name__].expireAll()
0361
0362    def getAll(self, cls=None):
0363        """
0364        Returns all instances in the cache for the given class or all
0365        classes.
0366        """
0367        if cls is None:
0368            results = []
0369            for cache in self.caches.values():
0370                results.extend(cache.getAll())
0371            return results
0372        elif cls.__name__ in self.caches:
0373            return self.caches[cls.__name__].getAll()
0374        else:
0375            return []