1"""Mixin helpers.
2
3At least for now, the implementation prefers clarity and performance
4over ease of maintenance, thus providing separate wrappers for all
5valid combinations of parameters lock, condition and info.
6
7"""
8
9# pyright: reportAttributeAccessIssue=false
10
11__all__ = ()
12
13
14class _BasicMixin:
15 def __init__(self, func):
16 self.__func = func
17
18 def __call__(self, /, *args, **kwargs):
19 cache = self.cache
20 key = self.cache_key(*args, **kwargs)
21
22 try:
23 return cache[key]
24 except KeyError:
25 pass # key not found
26 val = self.__func(*args, **kwargs)
27 try:
28 cache[key] = val
29 except ValueError:
30 pass # value too large
31 return val
32
33 def cache_clear(self):
34 self.cache.clear()
35
36
37class _LockedMixin:
38 def __init__(self, func):
39 self.__func = func
40
41 def __call__(self, /, *args, **kwargs):
42 cache = self.cache
43 key = self.cache_key(*args, **kwargs)
44 lock = self.cache_lock
45
46 with lock:
47 try:
48 return cache[key]
49 except KeyError:
50 pass # key not found
51 val = self.__func(*args, **kwargs)
52 with lock:
53 try:
54 # In case of a race condition, i.e. if another thread
55 # stored a value for this key while we were calling
56 # self.__func(), prefer the cached value.
57 return cache.setdefault(key, val)
58 except ValueError:
59 return val # value too large
60
61 def cache_clear(self):
62 with self.cache_lock:
63 self.cache.clear()
64
65
66class _ConditionMixin:
67 def __init__(self, func):
68 self.__func = func
69 self.__pending = set()
70
71 def __call__(self, /, *args, **kwargs):
72 cache = self.cache
73 key = self.cache_key(*args, **kwargs)
74 lock = self.cache_lock
75 cond = self.cache_condition
76
77 with lock:
78 # apparently significantly faster than cond.wait_for(...)
79 while key in self.__pending:
80 cond.wait()
81 try:
82 return cache[key]
83 except KeyError:
84 self.__pending.add(key)
85 try:
86 val = self.__func(*args, **kwargs)
87 with lock:
88 try:
89 cache[key] = val
90 except ValueError:
91 pass # value too large
92 return val
93 finally:
94 with lock:
95 self.__pending.remove(key)
96 cond.notify_all()
97
98 def cache_clear(self):
99 with self.cache_lock:
100 self.cache.clear()
101
102
103class _InfoMixin:
104 def __init__(self, func, info):
105 self.__func = func
106 self.__info = info
107 self.__hits = self.__misses = 0
108
109 def __call__(self, /, *args, **kwargs):
110 cache = self.cache
111 key = self.cache_key(*args, **kwargs)
112
113 try:
114 result = cache[key]
115 self.__hits += 1
116 return result
117 except KeyError:
118 self.__misses += 1
119 val = self.__func(*args, **kwargs)
120 try:
121 cache[key] = val
122 except ValueError:
123 pass # value too large
124 return val
125
126 def cache_clear(self):
127 self.cache.clear()
128 self.__hits = self.__misses = 0
129
130 def cache_info(self):
131 return self.__info(self.cache, self.__hits, self.__misses)
132
133
134class _LockedInfoMixin:
135 def __init__(self, func, info):
136 self.__func = func
137 self.__info = info
138 self.__hits = self.__misses = 0
139
140 def __call__(self, /, *args, **kwargs):
141 cache = self.cache
142 key = self.cache_key(*args, **kwargs)
143 lock = self.cache_lock
144
145 with lock:
146 try:
147 result = cache[key]
148 self.__hits += 1
149 return result
150 except KeyError:
151 self.__misses += 1
152 val = self.__func(*args, **kwargs)
153 with lock:
154 try:
155 # In case of a race condition, i.e. if another thread
156 # stored a value for this key while we were calling
157 # self.__func(), prefer the cached value.
158 return cache.setdefault(key, val)
159 except ValueError:
160 return val # value too large
161
162 def cache_clear(self):
163 with self.cache_lock:
164 self.cache.clear()
165 self.__hits = self.__misses = 0
166
167 def cache_info(self):
168 with self.cache_lock:
169 return self.__info(self.cache, self.__hits, self.__misses)
170
171
172class _ConditionInfoMixin:
173 def __init__(self, func, info):
174 self.__func = func
175 self.__info = info
176 self.__hits = self.__misses = 0
177 self.__pending = set()
178
179 def __call__(self, /, *args, **kwargs):
180 cache = self.cache
181 key = self.cache_key(*args, **kwargs)
182 lock = self.cache_lock
183 cond = self.cache_condition
184
185 with lock:
186 # apparently significantly faster than cond.wait_for(...)
187 while key in self.__pending:
188 cond.wait()
189 try:
190 result = cache[key]
191 self.__hits += 1
192 return result
193 except KeyError:
194 self.__pending.add(key)
195 self.__misses += 1
196 try:
197 val = self.__func(*args, **kwargs)
198 with lock:
199 try:
200 cache[key] = val
201 except ValueError:
202 pass # value too large
203 return val
204 finally:
205 with lock:
206 self.__pending.remove(key)
207 cond.notify_all()
208
209 def cache_clear(self):
210 with self.cache_lock:
211 self.cache.clear()
212 self.__hits = self.__misses = 0
213
214 def cache_info(self):
215 with self.cache_lock:
216 return self.__info(self.cache, self.__hits, self.__misses)