@@ -197,7 +197,9 @@ def test_gc_hooks_do_not_cause_unraisable_recursionerror():
197
197
# constant. Regardless, if the test passes just once that's sufficient proof that
198
198
# it's not the GC (or accounting of it) that is at fault.
199
199
200
- NUM_CYCLES = 10_000
200
+ # The number of cycles sufficient to reliably trigger a GC cycle, experimentally
201
+ # found to be a few hundred on CPython. Multiply by 10 for safety margin.
202
+ NUM_CYCLES = 5_000
201
203
202
204
def probe_depth ():
203
205
try :
@@ -213,11 +215,7 @@ def at_depth(depth, fn):
213
215
return at_depth (depth - 1 , fn )
214
216
215
217
def gen_cycles ():
216
- # We may be at the recursion limit, no free stack frames. Generate lots
217
- # of reference cycles, to trigger GC (if enabled). Beware: there may not
218
- # even be room for raising new exceptions here, anything will end up as
219
- # a RecursionError.
220
- for _ in range (NUM_CYCLES ):
218
+ for i in range (NUM_CYCLES ):
221
219
a = [None ]
222
220
b = [a ]
223
221
a [0 ] = b
@@ -228,37 +226,37 @@ def gen_cycles_at_depth(depth, *, gc_disable):
228
226
gc .disable ()
229
227
at_depth (depth , gen_cycles )
230
228
dead_objects = gc .collect ()
231
- if dead_objects is not None : # == None on PyPy
229
+ if dead_objects is not None : # is None on PyPy
232
230
if gc_disable :
233
231
assert dead_objects >= 2 * NUM_CYCLES
234
232
else :
235
- assert dead_objects < 2 * NUM_CYCLES # collection was triggered
233
+ # collection was triggered
234
+ assert dead_objects < 2 * NUM_CYCLES
236
235
finally :
237
236
gc .enable ()
238
237
238
+ # Warmup to de-flake PyPy (the first run has much lower effective limits)
239
+ probe_depth ()
240
+
239
241
@given (st .booleans ())
240
242
def inner_test (_ ):
241
243
max_depth = probe_depth ()
242
- max_depth = probe_depth () # Executing probe twice de-flakes PyPy
243
244
245
+ # Lower the limit to where we can successfully generate cycles
246
+ # when no gc is performed
244
247
while True :
245
- # Lower the limit to where we can successfully generate cycles
246
248
try :
247
249
gen_cycles_at_depth (max_depth , gc_disable = True )
248
250
except RecursionError :
249
251
max_depth -= 1
250
252
else :
251
253
break
254
+ # Note that PyPy is a bit weird, in that it raises RecursionError at
255
+ # (maxdepth - n) for small positive n, but not at exactly (maxdepth).
256
+ # In general, it is really finicky to get the details right in this
257
+ # test, so be careful.
252
258
253
- # Verify limits w/o any gc interfering
254
-
255
- # gen_cycles_at_depth(max_depth - 1, gc_disable=True) # RecursionError on PyPy (!)
256
- gen_cycles_at_depth (max_depth , gc_disable = True )
257
- with pytest .raises (RecursionError ):
258
- gen_cycles_at_depth (max_depth + 1 , gc_disable = True )
259
-
260
- # Check that the limit is unchanged with gc enabled
261
-
259
+ # Now check that the limit is unchanged with gc enabled
262
260
gen_cycles_at_depth (max_depth , gc_disable = False )
263
261
with pytest .raises (RecursionError ):
264
262
gen_cycles_at_depth (max_depth + 1 , gc_disable = False )
0 commit comments