Skip to content

Commit c33e6b7

Browse files
committed
test: give the finaliser backstop tests GraalPy's gc.collect grace (#1963)
- GraalPy's gc.collect forces a full Java GC, sleeps 15 ms and runs only the weak reference callbacks queued by then (GcModuleBuiltins.javaCollect, graal-25.4.4), so on a loaded runner the #1997 finaliser can run after it returns; job 118912680486 failed test_finaliser_alone_releases_an_abandoned_handle with the handle still open - new collect_until keeps collecting up to FINALISER_GRACE, 10 s on GraalPy and none on CPython, so a finaliser that never runs still fails; used there and in test_extractor_abandoned_before_eof_still_releases_its_handle - new stand-in test pins that the helper waits out a late finaliser The file passes on CPython and GraalPy 25.4.4; the tier guard passes. Refs #1963
1 parent 390cde9 commit c33e6b7

1 file changed

Lines changed: 57 additions & 2 deletions

File tree

‎tests/foundation/test_extraction_ownership.py‎

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,17 @@
5151
import importlib.util
5252
import io
5353
import os
54+
import sys
55+
import time
5456
import unittest
57+
from typing import TYPE_CHECKING
5558
from unittest import mock
5659

5760
from tests._support import sample_path
5861

62+
if TYPE_CHECKING:
63+
from typing import Callable
64+
5965
#: Test tier (#1958): depends on garbage collection, weak references or object
6066
#: finalisation.
6167
__tier__ = 'ALL'
@@ -67,6 +73,36 @@
6773
#: Linux; the :func:`builtins.open` measurement below covers the rest.
6874
HAS_PROC_FD = os.path.isdir('/proc/self/fd')
6975

76+
#: Seconds :func:`collect_until` keeps collecting for a finaliser after the first
77+
#: :func:`gc.collect`. CPython runs the finaliser before ``collect`` returns, so it
78+
#: gets no grace. GraalPy's ``collect`` forces a full Java GC, sleeps 15 ms "just
79+
#: trying to give the GC more time" and then runs only the weak reference
80+
#: callbacks queued by then (graalpython ``GcModuleBuiltins.javaCollect``, at
81+
#: graal-25.4.4), so on a loaded runner the finaliser can run after it returns
82+
#: (#1963). A finaliser that never runs still fails once the grace is spent.
83+
FINALISER_GRACE = 10.0 if sys.implementation.name == 'graalpy' else 0.0
84+
85+
86+
def collect_until(done: 'Callable[[], bool]', grace: float = FINALISER_GRACE) -> bool:
87+
"""Collect garbage until ``done()`` holds or ``grace`` seconds have passed.
88+
89+
Args:
90+
done: Zero-argument callable, checked after each collection.
91+
grace: Seconds to keep collecting after the first collection.
92+
93+
Returns:
94+
Whether ``done()`` held before the grace ran out.
95+
96+
"""
97+
deadline = time.monotonic() + grace
98+
while True:
99+
gc.collect()
100+
if done():
101+
return True
102+
if time.monotonic() >= deadline:
103+
return False
104+
time.sleep(0.05)
105+
70106

71107
def fd_count(target: str) -> int:
72108
"""How many of this process's open descriptors resolve to ``target``.
@@ -230,7 +266,7 @@ def test_extractor_abandoned_before_eof_still_releases_its_handle(self) -> None:
230266
'open at this point')
231267

232268
del extractor
233-
gc.collect()
269+
collect_until(lambda: recorder.still_open == 0)
234270

235271
with self.subTest(measurement='builtins.open handles still open'):
236272
self.assertEqual(recorder.still_open, 0,
@@ -398,11 +434,30 @@ def test_finaliser_alone_releases_an_abandoned_handle(self) -> None:
398434
self.assertEqual(recorder.still_open, 1)
399435

400436
del extractor
401-
gc.collect()
437+
collect_until(lambda: recorder.still_open == 0)
402438

403439
self.assertEqual(recorder.still_open, 0,
404440
'without __del__ the handle outlived the extractor')
405441

442+
def test_collect_until_waits_out_a_finaliser_that_runs_late(self) -> None:
443+
"""A finaliser run after :func:`gc.collect` returns, as GraalPy's can be (:issue:`1963`).
444+
445+
The stand-in finaliser runs on the third collection. With no grace the
446+
helper reports it as never run, the shape of the GraalPy leg's failure; with a
447+
grace it waits it out.
448+
449+
"""
450+
collections = []
451+
452+
def late() -> bool:
453+
collections.append(None)
454+
return len(collections) >= 3
455+
456+
self.assertFalse(collect_until(late, grace=0.0))
457+
collections.clear()
458+
self.assertTrue(collect_until(late, grace=5.0))
459+
self.assertEqual(len(collections), 3)
460+
406461
def test_finished_extraction_detaches_its_finaliser(self) -> None:
407462
"""``_cleanup`` and the context manager unregister it, so nothing closes twice."""
408463
import pcapkit

0 commit comments

Comments
 (0)