[IMP] core: rebuild LRU on OrderedDict

That's pretty much the entire point of OrderedDict's existence.

Also removed the iteration methods: they're not used and they're not
thread-safe, since they're written as generators they "return"
instantaneously when called immediately unlocking, then the iteration
itself is unprotected, so the entire thing is useless.

They could be reimplemented with explicit locking (a `with self._lock`
internally) if there's ever a use case for it, but that doesn't seem
to be the case at the moment.

closes odoo/odoo#46061

Signed-off-by: Xavier Morel (xmo) <xmo@odoo.com>
This commit is contained in:
Xavier Morel
2020-04-03 13:16:46 +00:00
parent e167f4aeae
commit 1b30a78beb
+15 -84
View File
@@ -1,31 +1,21 @@
# -*- coding: utf-8 -*-
# taken from http://code.activestate.com/recipes/252524-length-limited-o1-lru-cache-implementation/
import collections
import threading
from .func import synchronized
__all__ = ['LRU']
class LRUNode(object):
__slots__ = ['prev', 'next', 'me']
def __init__(self, prev, me):
self.prev = prev
self.me = me
self.next = None
class LRU(object):
"""
Implementation of a length-limited O(1) LRU queue.
Built for and used by PyPE:
http://pype.sourceforge.net
Copyright 2003 Josiah Carlson.
Implementation of a length-limited O(1) LRU map.
Original Copyright 2003 Josiah Carlson, later rebuilt on OrderedDict.
"""
def __init__(self, count, pairs=[]):
def __init__(self, count, pairs=()):
self._lock = threading.RLock()
self.count = max(count, 1)
self.d = {}
self.first = None
self.last = None
self.d = collections.OrderedDict()
for key, value in pairs:
self[key] = value
@@ -41,88 +31,29 @@ class LRU(object):
@synchronized()
def __getitem__(self, obj):
a = self.d[obj].me
self[a[0]] = a[1]
return a[1]
a = self.d[obj]
self.d.move_to_end(obj, last=False)
return a
@synchronized()
def __setitem__(self, obj, val):
if obj in self.d:
del self[obj]
nobj = LRUNode(self.last, (obj, val))
if self.first is None:
self.first = nobj
if self.last:
self.last.next = nobj
self.last = nobj
self.d[obj] = nobj
if len(self.d) > self.count:
if self.first == self.last:
self.first = None
self.last = None
return
a = self.first
a.next.prev = None
self.first = a.next
a.next = None
del self.d[a.me[0]]
del a
self.d[obj] = val
self.d.move_to_end(obj, last=False)
while len(self.d) > self.count:
self.d.popitem(last=True)
@synchronized()
def __delitem__(self, obj):
nobj = self.d[obj]
if nobj.prev:
nobj.prev.next = nobj.next
else:
self.first = nobj.next
if nobj.next:
nobj.next.prev = nobj.prev
else:
self.last = nobj.prev
del self.d[obj]
@synchronized()
def __iter__(self):
cur = self.first
while cur is not None:
cur2 = cur.next
yield cur.me[1]
cur = cur2
@synchronized()
def __len__(self):
return len(self.d)
# FIXME: should this have a P2 and a P3 version or something?
@synchronized()
def iteritems(self):
cur = self.first
while cur is not None:
cur2 = cur.next
yield cur.me
cur = cur2
items = iteritems
@synchronized()
def iterkeys(self):
return iter(self.d)
@synchronized()
def itervalues(self):
return iter(self.d.values())
@synchronized()
def keys(self):
return list(self.d)
@synchronized()
def pop(self,key):
v=self[key]
del self[key]
return v
return self.d.pop(key)
@synchronized()
def clear(self):
self.d = {}
self.first = None
self.last = None
self.d.clear()