Skip to content

Commit 79ed70e

Browse files
committed
Revert "gh-128639: Don't assume one thread in subinterpreter finalization (gh-128640)"
This reverts commit 9859791.
1 parent 8d490b3 commit 79ed70e

File tree

6 files changed

+38
-99
lines changed

6 files changed

+38
-99
lines changed

Lib/test/test_interpreters/test_api.py

Lines changed: 2 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -647,59 +647,6 @@ def test_created_with_capi(self):
647647
self.interp_exists(interpid))
648648

649649

650-
def test_remaining_threads(self):
651-
r_interp, w_interp = self.pipe()
652-
653-
FINISHED = b'F'
654-
655-
# It's unlikely, but technically speaking, it's possible
656-
# that the thread could've finished before interp.close() is
657-
# reached, so this test might not properly exercise the case.
658-
# However, it's quite unlikely and I'm too lazy to deal with it.
659-
interp = interpreters.create()
660-
interp.exec(f"""if True:
661-
import os
662-
import threading
663-
import time
664-
665-
def task():
666-
time.sleep(1)
667-
os.write({w_interp}, {FINISHED!r})
668-
669-
threads = [threading.Thread(target=task) for _ in range(3)]
670-
for t in threads:
671-
t.start()
672-
""")
673-
interp.close()
674-
675-
self.assertEqual(os.read(r_interp, 1), FINISHED)
676-
677-
def test_remaining_daemon_threads(self):
678-
interp = _interpreters.create(
679-
types.SimpleNamespace(
680-
use_main_obmalloc=False,
681-
allow_fork=False,
682-
allow_exec=False,
683-
allow_threads=True,
684-
allow_daemon_threads=True,
685-
check_multi_interp_extensions=True,
686-
gil='own',
687-
)
688-
)
689-
_interpreters.exec(interp, f"""if True:
690-
import threading
691-
import time
692-
693-
def task():
694-
time.sleep(100)
695-
696-
threads = [threading.Thread(target=task, daemon=True) for _ in range(3)]
697-
for t in threads:
698-
t.start()
699-
""")
700-
_interpreters.destroy(interp)
701-
702-
703650
class TestInterpreterPrepareMain(TestBase):
704651

705652
def test_empty(self):
@@ -808,10 +755,7 @@ def script():
808755
spam.eggs()
809756
810757
interp = interpreters.create()
811-
try:
812-
interp.exec(script)
813-
finally:
814-
interp.close()
758+
interp.exec(script)
815759
""")
816760

817761
stdout, stderr = self.assert_python_failure(scriptfile)
@@ -820,7 +764,7 @@ def script():
820764
# File "{interpreters.__file__}", line 179, in exec
821765
self.assertEqual(stderr, dedent(f"""\
822766
Traceback (most recent call last):
823-
File "{scriptfile}", line 10, in <module>
767+
File "{scriptfile}", line 9, in <module>
824768
interp.exec(script)
825769
~~~~~~~~~~~^^^^^^^^
826770
{interpmod_line.strip()}

Lib/test/test_interpreters/test_lifecycle.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ def test_sys_path_0(self):
132132
'sub': sys.path[0],
133133
}}, indent=4), flush=True)
134134
""")
135-
interp.close()
136135
'''
137136
# <tmp>/
138137
# pkg/
@@ -173,10 +172,7 @@ def test_gh_109793(self):
173172
argv = [sys.executable, '-c', '''if True:
174173
from test.support import interpreters
175174
interp = interpreters.create()
176-
try:
177-
raise Exception
178-
finally:
179-
interp.close()
175+
raise Exception
180176
''']
181177
proc = subprocess.run(argv, capture_output=True, text=True)
182178
self.assertIn('Traceback', proc.stderr)

Lib/test/test_threading.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1689,7 +1689,10 @@ def f():
16891689
16901690
_testcapi.run_in_subinterp(%r)
16911691
""" % (subinterp_code,)
1692-
assert_python_ok("-c", script)
1692+
with test.support.SuppressCrashReport():
1693+
rc, out, err = assert_python_failure("-c", script)
1694+
self.assertIn("Fatal Python error: Py_EndInterpreter: "
1695+
"not the last thread", err.decode())
16931696

16941697
def _check_allowed(self, before_start='', *,
16951698
allowed=True,

Misc/NEWS.d/next/Core_and_Builtins/2025-01-08-12-52-47.gh-issue-128640.9nbh9z.rst

Lines changed: 0 additions & 1 deletion
This file was deleted.

Programs/_testembed.c

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,12 +1395,9 @@ static int test_audit_subinterpreter(void)
13951395
PySys_AddAuditHook(_audit_subinterpreter_hook, NULL);
13961396
_testembed_initialize();
13971397

1398-
PyThreadState *tstate = PyThreadState_Get();
1399-
for (int i = 0; i < 3; ++i)
1400-
{
1401-
Py_EndInterpreter(Py_NewInterpreter());
1402-
PyThreadState_Swap(tstate);
1403-
}
1398+
Py_NewInterpreter();
1399+
Py_NewInterpreter();
1400+
Py_NewInterpreter();
14041401

14051402
Py_Finalize();
14061403

Python/pylifecycle.c

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1992,7 +1992,6 @@ resolve_final_tstate(_PyRuntimeState *runtime)
19921992
}
19931993
else {
19941994
/* Fall back to the current tstate. It's better than nothing. */
1995-
// XXX No it's not
19961995
main_tstate = tstate;
19971996
}
19981997
}
@@ -2038,16 +2037,6 @@ _Py_Finalize(_PyRuntimeState *runtime)
20382037

20392038
_PyAtExit_Call(tstate->interp);
20402039

2041-
/* Clean up any lingering subinterpreters.
2042-
2043-
Two preconditions need to be met here:
2044-
2045-
- This has to happen before _PyRuntimeState_SetFinalizing is
2046-
called, or else threads might get prematurely blocked.
2047-
- The world must not be stopped, as finalizers can run.
2048-
*/
2049-
finalize_subinterpreters();
2050-
20512040
assert(_PyThreadState_GET() == tstate);
20522041

20532042
/* Copy the core config, PyInterpreterState_Delete() free
@@ -2135,6 +2124,9 @@ _Py_Finalize(_PyRuntimeState *runtime)
21352124
_PyImport_FiniExternal(tstate->interp);
21362125
finalize_modules(tstate);
21372126

2127+
/* Clean up any lingering subinterpreters. */
2128+
finalize_subinterpreters();
2129+
21382130
/* Print debug stats if any */
21392131
_PyEval_Fini();
21402132

@@ -2418,8 +2410,9 @@ Py_NewInterpreter(void)
24182410
return tstate;
24192411
}
24202412

2421-
/* Delete an interpreter. This requires that the given thread state
2422-
is current, and that the thread has no remaining frames.
2413+
/* Delete an interpreter and its last thread. This requires that the
2414+
given thread state is current, that the thread has no remaining
2415+
frames, and that it is its interpreter's only remaining thread.
24232416
It is a fatal error to violate these constraints.
24242417
24252418
(Py_FinalizeEx() doesn't have these constraints -- it zaps
@@ -2449,15 +2442,14 @@ Py_EndInterpreter(PyThreadState *tstate)
24492442
_Py_FinishPendingCalls(tstate);
24502443

24512444
_PyAtExit_Call(tstate->interp);
2452-
_PyRuntimeState *runtime = interp->runtime;
2453-
_PyEval_StopTheWorldAll(runtime);
2454-
PyThreadState *list = _PyThreadState_RemoveExcept(tstate);
2445+
2446+
if (tstate != interp->threads.head || tstate->next != NULL) {
2447+
Py_FatalError("not the last thread");
2448+
}
24552449

24562450
/* Remaining daemon threads will automatically exit
24572451
when they attempt to take the GIL (ex: PyEval_RestoreThread()). */
24582452
_PyInterpreterState_SetFinalizing(interp, tstate);
2459-
_PyEval_StartTheWorldAll(runtime);
2460-
_PyThreadState_DeleteList(list, /*is_after_fork=*/0);
24612453

24622454
// XXX Call something like _PyImport_Disable() here?
24632455

@@ -2488,8 +2480,6 @@ finalize_subinterpreters(void)
24882480
PyInterpreterState *main_interp = _PyInterpreterState_Main();
24892481
assert(final_tstate->interp == main_interp);
24902482
_PyRuntimeState *runtime = main_interp->runtime;
2491-
assert(!runtime->stoptheworld.world_stopped);
2492-
assert(_PyRuntimeState_GetFinalizing(runtime) == NULL);
24932483
struct pyinterpreters *interpreters = &runtime->interpreters;
24942484

24952485
/* Get the first interpreter in the list. */
@@ -2518,17 +2508,27 @@ finalize_subinterpreters(void)
25182508

25192509
/* Clean up all remaining subinterpreters. */
25202510
while (interp != NULL) {
2521-
/* Make a tstate for finalization. */
2522-
PyThreadState *tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_FINI);
2523-
if (tstate == NULL) {
2524-
// XXX Some graceful way to always get a thread state?
2525-
Py_FatalError("thread state allocation failed");
2511+
assert(!_PyInterpreterState_IsRunningMain(interp));
2512+
2513+
/* Find the tstate to use for fini. We assume the interpreter
2514+
will have at most one tstate at this point. */
2515+
PyThreadState *tstate = interp->threads.head;
2516+
if (tstate != NULL) {
2517+
/* Ideally we would be able to use tstate as-is, and rely
2518+
on it being in a ready state: no exception set, not
2519+
running anything (tstate->current_frame), matching the
2520+
current thread ID (tstate->thread_id). To play it safe,
2521+
we always delete it and use a fresh tstate instead. */
2522+
assert(tstate != final_tstate);
2523+
_PyThreadState_Attach(tstate);
2524+
PyThreadState_Clear(tstate);
2525+
_PyThreadState_Detach(tstate);
2526+
PyThreadState_Delete(tstate);
25262527
}
2527-
2528-
/* Enter the subinterpreter. */
2529-
_PyThreadState_Attach(tstate);
2528+
tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_FINI);
25302529

25312530
/* Destroy the subinterpreter. */
2531+
_PyThreadState_Attach(tstate);
25322532
Py_EndInterpreter(tstate);
25332533
assert(_PyThreadState_GET() == NULL);
25342534

0 commit comments

Comments
 (0)