Commit c0337f7
committed
Fix: pytest never exits after the summary, wedged by abandoned non-daemon threads
Every CI job printed its pytest summary and then sat there until the
fifteen-minute cap killed it -- 5m41s of dead time on ubuntu/3.11, and the
same on all eight matrix jobs.
Root cause: tests/comprehensive/test_edge_cases_real.py::test_deadlock_prevention
builds a deliberate AB-BA lock inversion in two *non-daemon* threads, joins
them with timeout=5, and abandons them when (as designed) they deadlock.
Py_FinalizeEx calls threading._shutdown(), which joins every surviving
non-daemon thread with no timeout, so the interpreter could never finalize.
The test is also re-run by reflection from test_comprehensive_edge_case_suite,
so four threads were wedged, not two.
Evidence, from a faulthandler dump armed at pytest_unconfigure:
AT-UNCONFIGURE non-daemon alive: 4
ALIVE Thread-6 target=...potential_deadlock.<locals>.worker1
ALIVE Thread-7 target=...potential_deadlock.<locals>.worker2
ALIVE Thread-8 target=...potential_deadlock.<locals>.worker1
ALIVE Thread-9 target=...potential_deadlock.<locals>.worker2
Thread 0x33da2b000: test_edge_cases_real.py line 635 in worker2
Thread 0x33ca1f000: test_edge_cases_real.py line 629 in worker1
Thread 0x33ba13000: test_edge_cases_real.py line 635 in worker2
Thread 0x33aa07000: test_edge_cases_real.py line 629 in worker1
Thread 0x1fbd91d80: threading.py line 1477 in _shutdown
Marking those two threads daemon changes nothing the test observes -- the
deadlock still happens, the joins still time out, is_alive() is still True and
the assertions are untouched -- but abandoning them no longer wedges the
process.
Same defect, second site: tests/real_world/conftest.py::_within claimed to
abandon its worker, but a ThreadPoolExecutor's workers are non-daemon and
concurrent.futures joins all of them, untimed, at interpreter exit even after
shutdown(wait=False). Abandoning a seventy-second DNS lookup only moved the
wait to process exit. Replaced with an actual daemon thread, preserving the
existing semantics (OSError -> None, timeout -> None, anything else re-raised).
Isolating measurement, tests/comprehensive/test_edge_cases_real.py alone:
before: summary at 59.98s, still not exited 90s later
after: summary at 62.60s, exited 0.66s later
Full CI selection:
before: [345.05] 1764 passed ... -- never exited, killed after 6 minutes
after: [343.58] 1764 passed ... / [345.64] EXITED rc=01 parent 1af2c02 commit c0337f7
2 files changed
Lines changed: 40 additions & 15 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
635 | 635 | | |
636 | 636 | | |
637 | 637 | | |
638 | | - | |
639 | | - | |
640 | | - | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
641 | 651 | | |
642 | 652 | | |
643 | 653 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
10 | 9 | | |
11 | 10 | | |
| 11 | + | |
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
35 | | - | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
46 | 61 | | |
47 | 62 | | |
48 | 63 | | |
| |||
0 commit comments