#!/usr/bin/env python import threading import time from collections import deque class ObjectCache: """ Cache objects identified by id. Objects are removed from cache based on the last accessed algorithm. """ # How much larger than object cache should time stamp deq be # allowed to grow. DEFAULT_TIME_STAMP_DEQ_SIZE_FACTOR = 2 # Cache info expiration time. DEFAULT_OBJECT_LIFETIME = 60 # seconds def __init__(self, cacheSize, objectLifetime=DEFAULT_OBJECT_LIFETIME, objectClass=None): self.lock = threading.RLock() self.objectMap = {} # id/object map self.timestampDeq = deque() # timestamp deq self.cacheSize = cacheSize self.objectLifetime = objectLifetime self.deqSize = ObjectCache.DEFAULT_TIME_STAMP_DEQ_SIZE_FACTOR*cacheSize self.objectClass = objectClass def setCacheSize(self, cacheSize): self.cacheSize = cacheSize def setObjectLifetime(self, objectLifetime): self.objectLifetime = objectLifetime def __purgeOne(self): # Get rid of one cached item based on the last accessed algorithm. while True: deqEntry = self.timestampDeq.popleft() oldId = deqEntry[0] cachedEntry = self.objectMap.get(oldId) if cachedEntry is not None: # Timestamp entry is valid. if cachedEntry == deqEntry: # Found an old item, get rid of it from the cache. del self.objectMap[oldId] break # Done. return def __purgeTimestampDeq(self): # Get rid of stale entries. timestampDeq = deque() while len(self.timestampDeq): deqEntry = self.timestampDeq.popleft() id = deqEntry[0] cachedEntry = self.objectMap.get(id) if cachedEntry is not None: # Timestamp entry is valid. if cachedEntry == deqEntry: # Found current item, keep it. timestampDeq.append(deqEntry) # Done. self.timestampDeq = timestampDeq return def put(self, id, item, objectLifetime=None): updateTime = time.time() expirationTime = updateTime + self.objectLifetime if objectLifetime is not None: expirationTime = updateTime + objectLifetime entry = (id, item, updateTime, expirationTime) self.lock.acquire() try: self.objectMap[id] = entry self.timestampDeq.append(entry) if len(self.objectMap) > self.cacheSize: self.__purgeOne() if len(self.timestampDeq) > self.deqSize: self.__purgeTimestampDeq() finally: self.lock.release() def get(self, id): item = None itemTuple = self.objectMap.get(id) if itemTuple is not None: id, item, updateTime, expirationTime = itemTuple return item def getAll(self): # Item tuple: id, item, updateTime, expirationTime = itemTuple return map(lambda itemTuple:itemTuple[1], self.objectMap.values()) def getItemTuple(self, id): itemTuple = self.objectMap.get(id) if itemTuple is None: itemTuple = (id, None, None, None) return itemTuple def remove(self, id): self.lock.acquire() try: item = self.objectMap.get(id) if item is not None: del self.objectMap[id] return item finally: self.lock.release() def isEmpty(self): return len(self.objectMap) == 0 def size(self): return len(self.objectMap) def __str__(self): return '%s' % self.timestampDeq ####################################################################### # Testing. if __name__ == '__main__': c = ObjectCache(3) class Item: def __init__(self, id): self.id = id def getId(self): return self.id def __str__(self): return '%s' % self.id class Item2: def __init__(self, name): self.name = name def getName(self): return self.name def __str__(self): return '%s' % self.name for i in range(0,5): item = Item(i) c.put(i, item) print 'Added item: ', item print 'Cache: ', c time.sleep(1) for j in range(0,3): item = Item(2) c.put(2, item) print 'Updated item: ', item print 'Cache: ', c time.sleep(1) item = c.remove(2) print 'Deleted item 2: ', item print 'Cache: ', c time.sleep(1) item = c.get(2) print 'Got item 2: ', item print 'Cache: ', c print time.sleep(1) print c = ObjectCache(3) c.put('sv', Item2('sv')) print c i = c.get('sv') print i print 'Done'