1
1
from cachetools import Cache
2
2
from collections import OrderedDict
3
+ from readerwriterlock import rwlock
3
4
4
- class ARC (Cache ):
5
- """
6
- Adaptive Replacement Cache (ARC) implementation with on_evict callback.
7
- Balances recency and frequency via two active lists (T1, T2) and two ghost lists (B1, B2).
8
- Calls on_evict([key]) whenever an item is evicted from the active cache.
9
- """
5
+ _sentinel = object ()
10
6
11
- def __init__ (self , maxsize , getsizeof = None , on_evict = None ):
12
- """
13
- Args:
14
- maxsize (int): Maximum cache size.
15
- getsizeof (callable, optional): Sizing function for items.
16
- on_evict (callable, optional): Callback called as on_evict([key]) when a key is evicted.
17
- """
7
+ class ARC (Cache ):
8
+ def __init__ (self , maxsize , getsizeof = None ):
18
9
super ().__init__ (maxsize , getsizeof )
19
10
self .t1 = OrderedDict ()
20
11
self .t2 = OrderedDict ()
21
12
self .b1 = OrderedDict ()
22
13
self .b2 = OrderedDict ()
23
- self .p = 0 # Adaptive target for T1 size.
24
- self .on_evict = on_evict
14
+ self .p = 0
15
+ self ._rw_lock = rwlock . RWLockWrite ()
25
16
26
17
def __len__ (self ):
27
18
return len (self .t1 ) + len (self .t2 )
@@ -30,96 +21,80 @@ def __contains__(self, key):
30
21
return key in self .t1 or key in self .t2
31
22
32
23
def _evict_internal (self ):
33
- """
34
- Evicts items from T1 or T2 if cache is over capacity, and prunes ghost lists.
35
- Calls on_evict for each evicted key.
36
- """
37
- # Evict from T1 or T2 if active cache > maxsize
38
24
while len (self .t1 ) + len (self .t2 ) > self .maxsize :
39
25
if len (self .t1 ) > self .p or (len (self .t1 ) == 0 and len (self .t2 ) > 0 ):
40
26
key , value = self .t1 .popitem (last = False )
41
27
self .b1 [key ] = value
42
- if self .on_evict :
43
- self .on_evict ([key ])
44
28
else :
45
29
key , value = self .t2 .popitem (last = False )
46
30
self .b2 [key ] = value
47
- if self .on_evict :
48
- self .on_evict ([key ])
49
- # Prune ghost lists to their max lengths
50
31
while len (self .b1 ) > (self .maxsize - self .p ):
51
32
self .b1 .popitem (last = False )
52
33
while len (self .b2 ) > self .p :
53
34
self .b2 .popitem (last = False )
54
35
55
36
def __setitem__ (self , key , value ):
56
- # Remove from all lists before re-inserting
57
- for l in (self .t1 , self .t2 , self .b1 , self .b2 ):
58
- l .pop (key , None )
59
- self .t1 [key ] = value
60
- self .t1 .move_to_end (key )
61
- self ._evict_internal ()
37
+ with self . _rw_lock . gen_wlock ():
38
+ for l in (self .t1 , self .t2 , self .b1 , self .b2 ):
39
+ l .pop (key , None )
40
+ self .t1 [key ] = value
41
+ self .t1 .move_to_end (key )
42
+ self ._evict_internal ()
62
43
63
44
def __getitem__ (self , key ):
64
- # Case 1: Hit in T1 → promote to T2
65
- if key in self .t1 :
66
- value = self .t1 .pop (key )
67
- self .t2 [key ] = value
68
- self .t2 .move_to_end (key )
69
- self .p = max (0 , self .p - 1 )
70
- self ._evict_internal ()
71
- return value
72
- # Case 2: Hit in T2 → refresh in T2
73
- if key in self .t2 :
74
- value = self .t2 .pop (key )
75
- self .t2 [key ] = value
76
- self .t2 .move_to_end (key )
77
- self .p = min (self .maxsize , self .p + 1 )
78
- self ._evict_internal ()
79
- return value
80
- # Case 3: Hit in B1 (ghost) → fetch and promote to T2
81
- if key in self .b1 :
82
- self .b1 .pop (key )
83
- self .p = min (self .maxsize , self .p + 1 )
84
- self ._evict_internal ()
85
- value = super ().__missing__ (key )
86
- self .t2 [key ] = value
87
- self .t2 .move_to_end (key )
88
- return value
89
- # Case 4: Hit in B2 (ghost) → fetch and promote to T2
90
- if key in self .b2 :
91
- self .b2 .pop (key )
92
- self .p = max (0 , self .p - 1 )
93
- self ._evict_internal ()
94
- value = super ().__missing__ (key )
95
- self .t2 [key ] = value
96
- self .t2 .move_to_end (key )
97
- return value
98
- # Case 5: Cold miss → handled by Cache base class (calls __setitem__ after __missing__)
99
- return super ().__getitem__ (key )
45
+ with self ._rw_lock .gen_wlock ():
46
+ if key in self .t1 :
47
+ value = self .t1 .pop (key )
48
+ self .t2 [key ] = value
49
+ self .t2 .move_to_end (key )
50
+ self .p = max (0 , self .p - 1 )
51
+ self ._evict_internal ()
52
+ return value
53
+ if key in self .t2 :
54
+ value = self .t2 .pop (key )
55
+ self .t2 [key ] = value
56
+ self .t2 .move_to_end (key )
57
+ self .p = min (self .maxsize , self .p + 1 )
58
+ self ._evict_internal ()
59
+ return value
60
+ if key in self .b1 :
61
+ self .b1 .pop (key )
62
+ self .p = min (self .maxsize , self .p + 1 )
63
+ self ._evict_internal ()
64
+ value = super ().__missing__ (key )
65
+ self .t2 [key ] = value
66
+ self .t2 .move_to_end (key )
67
+ return value
68
+ if key in self .b2 :
69
+ self .b2 .pop (key )
70
+ self .p = max (0 , self .p - 1 )
71
+ self ._evict_internal ()
72
+ value = super ().__missing__ (key )
73
+ self .t2 [key ] = value
74
+ self .t2 .move_to_end (key )
75
+ return value
76
+ return super ().__getitem__ (key )
100
77
101
78
def __missing__ (self , key ):
102
- """
103
- Override this in a subclass, or rely on direct assignment (cache[key] = value).
104
- """
105
79
raise KeyError (key )
106
80
107
- def pop (self , key , default = None ):
108
- """
109
- Remove key from all lists.
110
- """
111
- for l in ( self . t1 , self . t2 , self . b1 , self . b2 ):
112
- if key in l :
113
- return l . pop (key )
114
- return default
81
+ def pop (self , key , default = _sentinel ):
82
+ with self . _rw_lock . gen_wlock ():
83
+ for l in ( self . t1 , self . t2 , self . b1 , self . b2 ):
84
+ if key in l :
85
+ return l . pop ( key )
86
+ if default is _sentinel :
87
+ raise KeyError (key )
88
+ return default
115
89
116
90
def clear (self ):
117
- self .t1 .clear ()
118
- self .t2 .clear ()
119
- self .b1 .clear ()
120
- self .b2 .clear ()
121
- self .p = 0
122
- super ().clear ()
91
+ with self ._rw_lock .gen_wlock ():
92
+ self .t1 .clear ()
93
+ self .t2 .clear ()
94
+ self .b1 .clear ()
95
+ self .b2 .clear ()
96
+ self .p = 0
97
+ super ().clear ()
123
98
124
99
def __iter__ (self ):
125
100
yield from self .t1
0 commit comments