1717
1818@pytest .fixture
1919def loop ():
20- loop = asyncio .new_event_loop ()
21- asyncio .set_event_loop (loop )
22- yield loop
23- loop .close ()
20+ # Save current loop to restore after the test
21+ try :
22+ old_loop = asyncio .get_running_loop ()
23+ except RuntimeError :
24+ old_loop = None
25+ new_loop = asyncio .new_event_loop ()
26+ asyncio .set_event_loop (new_loop )
27+ yield new_loop
28+ new_loop .close ()
29+ if old_loop is not None :
30+ asyncio .set_event_loop (old_loop )
2431
2532
2633@pytest .fixture
@@ -38,54 +45,81 @@ def run_the_loop(fn, *args, **kwargs):
3845
3946
4047# Bounded cache (LRU)
41- @alru_cache (maxsize = 128 )
42- async def cached_func (x ):
48+ async def _cached_func (x ):
4349 return x
4450
4551
46- @alru_cache (maxsize = 16 , ttl = 0.01 )
47- async def cached_func_ttl (x ):
52+ def create_cached_func ():
53+ return alru_cache (maxsize = 128 )(_cached_func )
54+
55+
56+ async def _cached_func_ttl (x ):
4857 return x
4958
5059
60+ def create_cached_func_ttl ():
61+ return alru_cache (maxsize = 16 , ttl = 0.01 )(_cached_func_ttl )
62+
63+
5164# Unbounded cache (no maxsize)
52- @alru_cache ()
53- async def cached_func_unbounded (x ):
65+ async def _cached_func_unbounded (x ):
5466 return x
5567
5668
57- @alru_cache (ttl = 0.01 )
58- async def cached_func_unbounded_ttl (x ):
69+ def create_cached_func_unbounded ():
70+ return alru_cache ()(_cached_func_unbounded )
71+
72+
73+ async def _cached_func_unbounded_ttl (x ):
5974 return x
6075
6176
62- class Methods :
63- @alru_cache (maxsize = 128 )
64- async def cached_meth (self , x ):
65- return x
77+ def create_cached_func_unbounded_ttl ():
78+ return alru_cache (ttl = 0.01 )(_cached_func_unbounded_ttl )
79+
6680
67- @alru_cache (maxsize = 16 , ttl = 0.01 )
68- async def cached_meth_ttl (self , x ):
69- return x
7081
71- @alru_cache ()
72- async def cached_meth_unbounded (self , x ):
73- return x
82+ def create_cached_meth ():
83+ class MethodsInstance :
84+ @alru_cache (maxsize = 128 )
85+ async def cached_meth (self , x ):
86+ return x
87+ return MethodsInstance ().cached_meth
7488
75- @alru_cache (ttl = 0.01 )
76- async def cached_meth_unbounded_ttl (self , x ):
77- return x
89+
90+ def create_cached_meth_ttl ():
91+ class MethodsInstance :
92+ @alru_cache (maxsize = 16 , ttl = 0.01 )
93+ async def cached_meth_ttl (self , x ):
94+ return x
95+ return MethodsInstance ().cached_meth_ttl
96+
97+
98+ def create_cached_meth_unbounded ():
99+ class MethodsInstance :
100+ @alru_cache ()
101+ async def cached_meth_unbounded (self , x ):
102+ return x
103+ return MethodsInstance ().cached_meth_unbounded
104+
105+
106+ def create_cached_meth_unbounded_ttl ():
107+ class MethodsInstance :
108+ @alru_cache (ttl = 0.01 )
109+ async def cached_meth_unbounded_ttl (self , x ):
110+ return x
111+ return MethodsInstance ().cached_meth_unbounded_ttl
78112
79113
80114async def uncached_func (x ):
81115 return x
82116
83117
84118funcs_no_ttl = [
85- cached_func ,
86- cached_func_unbounded ,
87- Methods . cached_meth ,
88- Methods . cached_meth_unbounded ,
119+ create_cached_func ,
120+ create_cached_func_unbounded ,
121+ create_cached_meth ,
122+ create_cached_meth_unbounded ,
89123]
90124no_ttl_ids = [
91125 "func-bounded" ,
@@ -95,10 +129,10 @@ async def uncached_func(x):
95129]
96130
97131funcs_ttl = [
98- cached_func_ttl ,
99- cached_func_unbounded_ttl ,
100- Methods . cached_meth_ttl ,
101- Methods . cached_meth_unbounded_ttl ,
132+ create_cached_func_ttl ,
133+ create_cached_func_unbounded_ttl ,
134+ create_cached_meth_ttl ,
135+ create_cached_meth_unbounded_ttl ,
102136]
103137ttl_ids = [
104138 "func-bounded-ttl" ,
@@ -111,13 +145,13 @@ async def uncached_func(x):
111145all_ids = [* no_ttl_ids , * ttl_ids ]
112146
113147
114- @pytest .mark .parametrize ("func " , all_funcs , ids = all_ids )
148+ @pytest .mark .parametrize ("factory " , all_funcs , ids = all_ids )
115149def test_cache_hit_benchmark (
116150 benchmark : BenchmarkFixture ,
117151 run_loop : Callable [..., Any ],
118- func : _LRUCacheWrapper [Any ],
152+ factory : Callable [[], _LRUCacheWrapper [Any ] ],
119153) -> None :
120- # Populate cache
154+ func = factory ()
121155 keys = list (range (10 ))
122156 for key in keys :
123157 run_loop (func , key )
@@ -130,14 +164,15 @@ async def run() -> None:
130164 benchmark (run_loop , run )
131165
132166
133- @pytest .mark .parametrize ("func " , all_funcs , ids = all_ids )
167+ @pytest .mark .parametrize ("factory " , all_funcs , ids = all_ids )
134168def test_cache_miss_benchmark (
135169 benchmark : BenchmarkFixture ,
136170 run_loop : Callable [..., Any ],
137- func : _LRUCacheWrapper [Any ],
171+ factory : Callable [[], _LRUCacheWrapper [Any ] ],
138172) -> None :
139- unique_objects = [object () for _ in range (128 )]
140- func .cache_clear ()
173+ func = factory ()
174+ # Use 2048 objects (16x maxsize=128) to force evictions and measure actual misses
175+ unique_objects = [object () for _ in range (2048 )]
141176
142177 async def run () -> None :
143178 for obj in unique_objects :
@@ -146,37 +181,39 @@ async def run() -> None:
146181 benchmark (run_loop , run )
147182
148183
149- @pytest .mark .parametrize ("func " , all_funcs , ids = all_ids )
184+ @pytest .mark .parametrize ("factory " , all_funcs , ids = all_ids )
150185def test_cache_clear_benchmark (
151186 benchmark : BenchmarkFixture ,
152187 run_loop : Callable [..., Any ],
153- func : _LRUCacheWrapper [Any ],
188+ factory : Callable [[], _LRUCacheWrapper [Any ] ],
154189) -> None :
190+ func = factory ()
155191 for i in range (100 ):
156192 run_loop (func , i )
157193
158194 benchmark (func .cache_clear )
159195
160196
161- @pytest .mark .parametrize ("func_ttl " , funcs_ttl , ids = ttl_ids )
197+ @pytest .mark .parametrize ("factory " , funcs_ttl , ids = ttl_ids )
162198def test_cache_ttl_expiry_benchmark (
163199 benchmark : BenchmarkFixture ,
164200 run_loop : Callable [..., Any ],
165- func_ttl : _LRUCacheWrapper [Any ],
201+ factory : Callable [[], _LRUCacheWrapper [Any ] ],
166202) -> None :
203+ func_ttl = factory ()
167204 run_loop (func_ttl , 99 )
168205 run_loop (asyncio .sleep , 0.02 )
169206
170207 benchmark (run_loop , func_ttl , 99 )
171208
172209
173- @pytest .mark .parametrize ("func " , all_funcs , ids = all_ids )
210+ @pytest .mark .parametrize ("factory " , all_funcs , ids = all_ids )
174211def test_cache_invalidate_benchmark (
175212 benchmark : BenchmarkFixture ,
176213 run_loop : Callable [..., Any ],
177- func : _LRUCacheWrapper [Any ],
214+ factory : Callable [[], _LRUCacheWrapper [Any ] ],
178215) -> None :
179- # Populate cache
216+ func = factory ()
180217 keys = list (range (123 , 321 ))
181218 for i in keys :
182219 run_loop (func , i )
@@ -189,13 +226,13 @@ def run() -> None:
189226 invalidate (i )
190227
191228
192- @pytest .mark .parametrize ("func " , all_funcs , ids = all_ids )
229+ @pytest .mark .parametrize ("factory " , all_funcs , ids = all_ids )
193230def test_cache_info_benchmark (
194231 benchmark : BenchmarkFixture ,
195232 run_loop : Callable [..., Any ],
196- func : _LRUCacheWrapper [Any ],
233+ factory : Callable [[], _LRUCacheWrapper [Any ] ],
197234) -> None :
198- # Populate cache
235+ func = factory ()
199236 keys = list (range (1000 ))
200237 for i in keys :
201238 run_loop (func , i )
@@ -208,37 +245,37 @@ def run() -> None:
208245 cache_info ()
209246
210247
211- @pytest .mark .parametrize ("func " , all_funcs , ids = all_ids )
248+ @pytest .mark .parametrize ("factory " , all_funcs , ids = all_ids )
212249def test_concurrent_cache_hit_benchmark (
213250 benchmark : BenchmarkFixture ,
214251 run_loop : Callable [..., Any ],
215- func : _LRUCacheWrapper [Any ],
252+ factory : Callable [[], _LRUCacheWrapper [Any ] ],
216253) -> None :
217- # Populate cache
254+ func = factory ()
218255 keys = list (range (600 , 700 ))
219256 for key in keys :
220257 run_loop (func , key )
221258
222259 async def gather_coros ():
223260 gather = asyncio .gather
224261 for _ in range (10 ):
225- return await gather (* map (func , keys ))
262+ _ = await gather (* map (func , keys ))
226263
227264 benchmark (run_loop , gather_coros )
228265
229266
230267def test_cache_fill_eviction_benchmark (
231268 benchmark : BenchmarkFixture , run_loop : Callable [..., Any ]
232269) -> None :
233- # Populate cache
270+ func = create_cached_func ()
234271 for i in range (- 128 , 0 ):
235- run_loop (cached_func , i )
272+ run_loop (func , i )
236273
237274 keys = list (range (5000 ))
238275
239276 async def fill ():
240277 for k in keys :
241- await cached_func (k )
278+ await func (k )
242279
243280 benchmark (run_loop , fill )
244281
@@ -252,20 +289,20 @@ async def fill():
252289# The relevant internal methods do not exist on _LRUCacheWrapperInstanceMethod,
253290# so we can skip methods for this part of the benchmark suite.
254291# We also skip wrappers with ttl because it raises KeyError.
255- only_funcs_no_ttl = all_funcs [:2 ]
256- func_ids_no_ttl = all_ids [:2 ]
292+ only_funcs_no_ttl = funcs_no_ttl [:2 ]
293+ func_ids_no_ttl = no_ttl_ids [:2 ]
257294
258295
259- @pytest .mark .parametrize ("func " , only_funcs_no_ttl , ids = func_ids_no_ttl )
296+ @pytest .mark .parametrize ("factory " , only_funcs_no_ttl , ids = func_ids_no_ttl )
260297def test_internal_cache_hit_microbenchmark (
261298 benchmark : BenchmarkFixture ,
262299 run_loop : Callable [..., Any ],
263- func : _LRUCacheWrapper [Any ],
300+ factory : Callable [[], _LRUCacheWrapper [Any ] ],
264301) -> None :
265302 """Directly benchmark _cache_hit (internal, sync) using parameterized funcs."""
303+ func = factory ()
266304 cache_hit = func ._cache_hit
267305
268- # Populate cache
269306 keys = list (range (128 ))
270307 for i in keys :
271308 run_loop (func , i )
@@ -276,11 +313,12 @@ def run() -> None:
276313 cache_hit (i )
277314
278315
279- @pytest .mark .parametrize ("func " , only_funcs_no_ttl , ids = func_ids_no_ttl )
316+ @pytest .mark .parametrize ("factory " , only_funcs_no_ttl , ids = func_ids_no_ttl )
280317def test_internal_cache_miss_microbenchmark (
281- benchmark : BenchmarkFixture , func : _LRUCacheWrapper [Any ]
318+ benchmark : BenchmarkFixture , factory : Callable [[], _LRUCacheWrapper [Any ] ]
282319) -> None :
283320 """Directly benchmark _cache_miss (internal, sync) using parameterized funcs."""
321+ func = factory ()
284322 cache_miss = func ._cache_miss
285323
286324 @benchmark
@@ -289,17 +327,17 @@ def run() -> None:
289327 cache_miss (i )
290328
291329
292- @pytest .mark .parametrize ("func " , only_funcs_no_ttl , ids = func_ids_no_ttl )
330+ @pytest .mark .parametrize ("factory " , only_funcs_no_ttl , ids = func_ids_no_ttl )
293331@pytest .mark .parametrize ("task_state" , ["finished" , "cancelled" , "exception" ])
294332def test_internal_task_done_callback_microbenchmark (
295333 benchmark : BenchmarkFixture ,
296334 loop : asyncio .BaseEventLoop ,
297- func : _LRUCacheWrapper [Any ],
335+ factory : Callable [[], _LRUCacheWrapper [Any ] ],
298336 task_state : str ,
299337) -> None :
300338 """Directly benchmark _task_done_callback (internal, sync) using parameterized funcs and task states."""
339+ func = factory ()
301340
302- # Create a dummy coroutine and task
303341 async def dummy_coro ():
304342 if task_state == "exception" :
305343 raise ValueError ("test exception" )
0 commit comments