99import threading
1010
1111from collections import OrderedDict
12+ from thirdparty import six
1213from thirdparty .six .moves import collections_abc as _collections
1314
15+ class NoLock (object ):
16+ """
17+ No-op stand-in for a lock, used where the guarded operations are already atomic on their own
18+
19+ >>> with NoLock():
20+ ... True
21+ True
22+ """
23+
24+ def __enter__ (self ):
25+ return self
26+
27+ def __exit__ (self , * args ):
28+ return False
29+
30+ def cacheLock (reentrant = False ):
31+ """
32+ Returns a lock for guarding the (OrderedDict based) caches. Only Python 2 needs a real one, as its
33+ OrderedDict is implemented in Python (#5619), while on Python 3 each operation is atomic. Moreover,
34+ since Python 3.14 a Ctrl-C landing right after a lock got acquired by the 'with' statement leaves it
35+ acquired forever (i.e. '__exit__' never gets called), hanging every thread that touches the cache
36+
37+ >>> isinstance(cacheLock(), NoLock) == six.PY3
38+ True
39+ """
40+
41+ if six .PY2 :
42+ return threading .RLock () if reentrant else threading .Lock ()
43+ else :
44+ return NoLock ()
45+
1446class AttribDict (dict ):
1547 """
1648 This class defines the dictionary with added capability to access members as attributes
@@ -133,7 +165,7 @@ class LRUDict(object):
133165 def __init__ (self , capacity ):
134166 self .capacity = capacity
135167 self .cache = OrderedDict ()
136- self .__lock = threading . Lock ()
168+ self .__lock = cacheLock ()
137169
138170 def __len__ (self ):
139171 return len (self .cache )
@@ -159,7 +191,10 @@ def __setitem__(self, key, value):
159191 self .cache .pop (key )
160192 except KeyError :
161193 if len (self .cache ) >= self .capacity :
162- self .cache .popitem (last = False )
194+ try :
195+ self .cache .popitem (last = False )
196+ except KeyError : # Note: concurrently emptied by another thread (unlocked on Python 3)
197+ pass
163198 self .cache [key ] = value
164199
165200 def set (self , key , value ):
0 commit comments