Test harness for leakage detection: does `threading_setup` duplicates `saved_test_environment`?

CPython has two test helpers that manage multithreading resource leaks:

When some test leaks threads, we get a report from saved_test_environment only:

Warning – threading._dangling was modified by test_deliberate_leakage
Warning – Before: {<weakref at 0x000002A431AA95D0; to ‘_MainThread’ at 0x000002A43163B710>}
Warning – After: {<weakref at 0x000002A431AAB1A0; to ‘Thread’ at 0x000002A431A93590>, <weakref at 0x000002A431AAAFC0; to ‘_MainThread’ at 0x000002A43163B710>}
test_deliberate_leakage ran no tests

A reproducer (Lib/test/test_deliberate_leakage.py ran with python.bat -m test -v test_deliberate_leakage):

from concurrent.futures import ThreadPoolExecutor
from test.support.threading_helper import threading_cleanup, threading_setup
from threading import Barrier
thread_info = threading_setup()
leaked_pool = ThreadPoolExecutor()

What is the reason behind such duplication? Do we need threading_setup altogether?

For context, threading_setup was added in 2020, saved_test_environment was added in 2016.

Bump (to give this topic the second chance).

threading_setup and threading_cleanup were added not in 2020, but in 2006.

What is the problem? threading_setup and threading_cleanup are tools which help to eliminate thread leaks, and the code in test.libregtest.save_env is a detector which detects leaks. If any leaked threads are found, it should be fixed, maybe with using threading_setup and threading_cleanup, or better using more narrow tool, like start_threads.

What is the reason behind such duplication?

The scope of threading_setup + threading_cleanup is usually a single test function.

The scope libregrtest.save_end is a whole test module.

A more narrow scope helps a lot to debug tests leaking threads. Moreover, it’s likely that if a test module runs 100 tests and a test runs earlier “leaks” a thread, the thread completes in the background while following tests are executed.

It’s important to call threading_cleanup as soon as possible.

Finally, the implementation is different. threading_cleanup uses the low-level C module _thread by calling its _thread._count() function. save_end is less reliable, it only checks for high-level threading._dangling (Python module).

For both, it’s complementary, it’s not redundant.

By the way, tests failing with “dangling threads” are annoying, hard to debug, hard to reproduce, so help is welcomed to debug and fix them :slight_smile: My notes on unstable tests: Unstable tests — Unofficial Python Development (Victor's notes) documentation

1 Like