diff --git a/changelog/14441.improvement.rst b/changelog/14441.improvement.rst new file mode 100644 index 00000000000..d37e6466b06 --- /dev/null +++ b/changelog/14441.improvement.rst @@ -0,0 +1 @@ +Reduced the default number of ``gc.collect()`` passes in the ``unraisableexception`` plugin from 5 to 1 on CPython, where reference counting makes a single pass sufficient. PyPy retains 5 passes due to object resurrection via ``__del__``. This can noticeably speed up test suites that trigger many pytester runs. diff --git a/src/_pytest/unraisableexception.py b/src/_pytest/unraisableexception.py index 0faca36aa00..259f0d8e7f4 100644 --- a/src/_pytest/unraisableexception.py +++ b/src/_pytest/unraisableexception.py @@ -86,9 +86,14 @@ def collect_unraisable(config: Config) -> None: def cleanup( *, config: Config, prev_hook: Callable[[sys.UnraisableHookArgs], object] ) -> None: - # A single collection doesn't necessarily collect everything. - # Constant determined experimentally by the Trio project. - gc_collect_iterations = config.stash.get(gc_collect_iterations_key, 5) + # On PyPy, objects (e.g. coroutines) can survive GC rounds because executing + # their __del__ can resurrect them. The Trio project determined experimentally + # that 5 passes are needed on PyPy to flush everything. On CPython, reference + # counting handles most cleanup immediately, so 1 pass is sufficient. + _default_gc_collect_iterations = 5 if sys.implementation.name == "pypy" else 1 + gc_collect_iterations = config.stash.get( + gc_collect_iterations_key, _default_gc_collect_iterations + ) try: try: gc_collect_harder(gc_collect_iterations)