Added live-object store and simple indexing.
[didex.git] / didex / cache.py
diff --git a/didex/cache.py b/didex/cache.py
new file mode 100644 (file)
index 0000000..76742aa
--- /dev/null
@@ -0,0 +1,153 @@
+import threading, weakref
+
+class entry(object):
+    __slots__ = ["p", "n", "id", "obj", "st", "lk"]
+    def __init__(self, id, c):
+        self.id = id
+        self.obj = None
+        self.st = None
+        self.lk = None
+        self.n = c.mru
+        self.p = None
+        if c.mru is not None:
+            c.mru.p = self
+            c.mru = self
+        else:
+            c.mru = c.lru = self
+        c.n += 1
+
+    def relink(self, c):
+        if c.mru is self:
+            return
+        if self.n is not None:
+            self.n.p = self.p
+        self.p.n = self.n
+        if c.lru is self:
+            c.lru = self.p
+        self.p = None
+        self.n = c.mru
+        c.mru.p = self
+
+    def remove(self, c):
+        if self.n is not None:
+            self.n.p = self.p
+        if self.p is not None:
+            self.p.n = self.n
+        if c.mru is self:
+            c.mru = self.n
+        if c.lru is self:
+            c.lru = self.p
+        c.n -= 1
+
+class cache(object):
+    def __init__(self, *, keep=1000):
+        self.keep = keep
+        self.cur = {}
+        self.mru = self.lru = None
+        self.n = 0
+        self.lk = threading.Lock()
+
+    def _trim(self, n):
+        ent = self.lru
+        for i in range(self.n - n):
+            if ent.st == "l":
+                ent.obj = weakref.ref(ent.obj)
+                ent.st = "w"
+            elif ent.st == "w" and ent.obj() is None:
+                del self.cur[ent.id]
+                ent.remove(self)
+                ent.st = "r"
+            ent = ent.p
+
+    def get(self, id, load=True):
+        while True:
+            with self.lk:
+                ent = self.cur.get(id)
+                if ent is None:
+                    if not load:
+                        raise KeyError(id)
+                    self.cur[id] = ent = entry(id, self)
+                    ent.lk = lk = threading.Lock()
+                    ent.st = "ld"
+                    st = None
+                    self._trim(self.keep)
+                elif ent.st == "l":
+                    ent.relink(self)
+                    return ent.obj
+                elif ent.st == "w":
+                    ret = ent.obj()
+                    if ret is None:
+                        del self.cur[id]
+                        ent.remove(self)
+                        ent.st = "r"
+                        continue
+                    return ret
+                elif ent.st == "ld":
+                    lk = ent.lk
+                    st = "ld"
+                    if lk is None:
+                        continue
+                elif ent.st == "r":
+                    continue
+            with lk:
+                if st is None:
+                    try:
+                        ret = ent.obj = self.load(id)
+                        ent.st = "l"
+                        return ret
+                    except:
+                        with self.lk:
+                            del self.cur[id]
+                            ent.remove(self)
+                            ent.st = "r"
+                        raise
+                    finally:
+                        ent.lk = None
+                elif st == "ld":
+                    continue
+
+    def put(self, id, ob):
+        while True:
+            with self.lk:
+                ent = self.cur.get(id)
+                if ent is None:
+                    self.cur[id] = ent = entry(id, self)
+                    ent.obj = ob
+                    ent.st = "l"
+                    self._trim(self.keep)
+                    return
+                elif ent.st == "l":
+                    ent.obj = ob
+                    return
+                elif ent.st == "w":
+                    ent.obj = ob
+                    return
+                elif ent.st == "r":
+                    continue
+                elif ent.st == "ld":
+                    lk = ent.lk
+                    if lk is None:
+                        continue
+            with lk:
+                continue
+
+    def remove(self, id):
+        while True:
+            with self.lk:
+                ent = self.cur.get(id)
+                if ent is None:
+                    return
+                elif ent.st == "ld":
+                    lk = ent.lk
+                    if lk is None:
+                        continue
+                else:
+                    del self.cur[id]
+                    ent.remove(self)
+                    ent.st = "r"
+                    return
+            with lk:
+                continue
+
+    def load(self, id):
+        raise KeyError()