1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 from weakref import WeakValueDictionary
17 from itertools import ifilterfalse
18 from twisted.python import log
19 from twisted.internet import defer
20 from collections import deque
21 from collections import defaultdict
22
23
25 """
26 A least-recently-used cache, with a fixed maximum size.
27
28 See buildbot manual for more information.
29 """
30
31 __slots__ = ('max_size max_queue miss_fn queue cache weakrefs '
32 'refcount hits refhits misses'.split())
33 sentinel = object()
34 QUEUE_SIZE_FACTOR = 10
35
36 - def __init__(self, miss_fn, max_size=50):
45
46 - def put(self, key, value):
52
53 - def get(self, key, **miss_fn_kwargs):
54 try:
55 return self._get_hit(key)
56 except KeyError:
57 pass
58
59 self.misses += 1
60
61 result = self.miss_fn(key, **miss_fn_kwargs)
62 if result is not None:
63 self.cache[key] = result
64 self.weakrefs[key] = result
65 self._ref_key(key)
66 self._purge()
67
68 return result
69
72
80
82 global inv_failed
83
84
85 cache_keys = set(self.cache.keys())
86 queue_keys = set(self.queue)
87 if queue_keys - cache_keys:
88 log.msg("INV: uncached keys in queue:", queue_keys - cache_keys)
89 inv_failed = True
90 if cache_keys - queue_keys:
91 log.msg("INV: unqueued keys in cache:", cache_keys - queue_keys)
92 inv_failed = True
93
94
95
96 exp_refcount = dict()
97 for k in self.queue:
98 exp_refcount[k] = exp_refcount.get(k, 0) + 1
99 if exp_refcount != self.refcount:
100 log.msg("INV: refcounts differ:")
101 log.msg(" expected:", sorted(exp_refcount.items()))
102 log.msg(" got:", sorted(self.refcount.items()))
103 inv_failed = True
104
125
127 """Try to do a value lookup from the existing cache entries."""
128 try:
129 result = self.cache[key]
130 self.hits += 1
131 self._ref_key(key)
132 return result
133 except KeyError:
134 pass
135
136 result = self.weakrefs[key]
137 self.refhits += 1
138 self.cache[key] = result
139 self._ref_key(key)
140 return result
141
164
165
167 """
168 An LRU cache with asynchronous locking to ensure that in the common case of
169 multiple concurrent requests for the same key, only one fetch is performed.
170 """
171
172 __slots__ = ['concurrent']
173
174 - def __init__(self, miss_fn, max_size=50):
177
178 - def get(self, key, **miss_fn_kwargs):
179 try:
180 result = self._get_hit(key)
181 return defer.succeed(result)
182 except KeyError:
183 pass
184
185 concurrent = self.concurrent
186 conc = concurrent.get(key)
187 if conc:
188 self.hits += 1
189 d = defer.Deferred()
190 conc.append(d)
191 return d
192
193
194 self.misses += 1
195
196
197 d = defer.Deferred()
198 assert key not in concurrent
199 concurrent[key] = [ d ]
200
201 miss_d = self.miss_fn(key, **miss_fn_kwargs)
202
203 def handle_result(result):
204 if result is not None:
205 self.cache[key] = result
206 self.weakrefs[key] = result
207
208
209
210 self._ref_key(key)
211
212 self._purge()
213
214
215 dlist = concurrent.pop(key)
216 for d in dlist:
217 d.callback(result)
218
219 def handle_failure(f):
220
221 dlist = concurrent.pop(key)
222 for d in dlist:
223 d.errback(f)
224
225 miss_d.addCallbacks(handle_result, handle_failure)
226 miss_d.addErrback(log.err)
227
228 return d
229
230
231
232 inv_failed = False
233