Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gh-128639: Don't assume one thread in subinterpreter finalization#128640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
17 commits
Select commitHold shift + click to select a range
025cc17
Fix subinterpreter cleanup.
ZeroIntensityJan 8, 2025
eea3ba9
Add and fix tests.
ZeroIntensityJan 8, 2025
149e0cf
Add blurb
ZeroIntensityJan 8, 2025
1761c37
Fix threading tests.
ZeroIntensityJan 8, 2025
8febfbf
Fix test_embed.
ZeroIntensityJan 8, 2025
5a73ce1
Remove buggy test.
ZeroIntensityJan 8, 2025
a7cc3fe
Skip flaky test.
ZeroIntensityJan 8, 2025
7c44c0b
Add an assertion.
ZeroIntensityFeb 4, 2025
6551bfa
Remove stray newline.
ZeroIntensityFeb 5, 2025
f19e28d
Add another assertion.
ZeroIntensityFeb 5, 2025
02da72a
Merge branch 'main' into subinterpreter-thread-shutdown
ZeroIntensityFeb 7, 2025
a759540
Merge branch 'main' of https://github.com/python/cpython into subinte…
ZeroIntensityMar 28, 2025
76e4ec9
Use new is_after_fork parameter
ZeroIntensityMar 28, 2025
1555731
Apply suggestions from code review
ZeroIntensityMay 19, 2025
d61099e
Move call to finalize_subinterpreters()
ZeroIntensityMay 19, 2025
09851dc
Unskip the threading test.
ZeroIntensityMay 19, 2025
b223396
Add a comment for the test.
ZeroIntensityMay 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 58 additions & 2 deletionsLib/test/test_interpreters/test_api.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -647,6 +647,59 @@ def test_created_with_capi(self):
self.interp_exists(interpid))


def test_remaining_threads(self):
r_interp, w_interp = self.pipe()

FINISHED = b'F'

# It's unlikely, but technically speaking, it's possible
# that the thread could've finished before interp.close() is
# reached, so this test might not properly exercise the case.
# However, it's quite unlikely and I'm too lazy to deal with it.
interp = interpreters.create()
interp.exec(f"""if True:
import os
import threading
import time

def task():
time.sleep(1)
os.write({w_interp}, {FINISHED!r})

threads = [threading.Thread(target=task) for _ in range(3)]
for t in threads:
t.start()
""")
interp.close()

self.assertEqual(os.read(r_interp, 1), FINISHED)

def test_remaining_daemon_threads(self):
interp = _interpreters.create(
types.SimpleNamespace(
use_main_obmalloc=False,
allow_fork=False,
allow_exec=False,
allow_threads=True,
allow_daemon_threads=True,
check_multi_interp_extensions=True,
gil='own',
)
)
_interpreters.exec(interp, f"""if True:
import threading
import time

def task():
time.sleep(100)

threads = [threading.Thread(target=task, daemon=True) for _ in range(3)]
for t in threads:
t.start()
""")
_interpreters.destroy(interp)


class TestInterpreterPrepareMain(TestBase):

def test_empty(self):
Expand DownExpand Up@@ -756,7 +809,10 @@ def script():
spam.eggs()

interp = interpreters.create()
interp.exec(script)
try:
interp.exec(script)
finally:
interp.close()
""")

stdout, stderr = self.assert_python_failure(scriptfile)
Expand All@@ -765,7 +821,7 @@ def script():
# File "{interpreters.__file__}", line 179, in exec
self.assertEqual(stderr, dedent(f"""\
Traceback (most recent call last):
File "{scriptfile}", line9, in <module>
File "{scriptfile}", line10, in <module>
interp.exec(script)
~~~~~~~~~~~^^^^^^^^
{interpmod_line.strip()}
Expand Down
6 changes: 5 additions & 1 deletionLib/test/test_interpreters/test_lifecycle.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -132,6 +132,7 @@ def test_sys_path_0(self):
'sub': sys.path[0],
}}, indent=4), flush=True)
""")
interp.close()
'''
# <tmp>/
# pkg/
Expand DownExpand Up@@ -172,7 +173,10 @@ def test_gh_109793(self):
argv= [sys.executable,'-c','''if True:
from test.support import interpreters
interp = interpreters.create()
raise Exception
try:
raise Exception
finally:
interp.close()
''']
proc=subprocess.run(argv,capture_output=True,text=True)
self.assertIn('Traceback',proc.stderr)
Expand Down
5 changes: 1 addition & 4 deletionsLib/test/test_threading.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1612,10 +1612,7 @@ def f():

_testcapi.run_in_subinterp(%r)
""" % (subinterp_code,)
with test.support.SuppressCrashReport():
rc, out, err = assert_python_failure("-c", script)
self.assertIn("Fatal Python error: Py_EndInterpreter: "
"not the last thread", err.decode())
assert_python_ok("-c", script)

def _check_allowed(self, before_start='', *,
allowed=True,
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
Fix a crash when using threads inside of a subinterpreter.
9 changes: 6 additions & 3 deletionsPrograms/_testembed.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1424,9 +1424,12 @@ static int test_audit_subinterpreter(void)
PySys_AddAuditHook(_audit_subinterpreter_hook, NULL);
_testembed_Py_InitializeFromConfig();

Py_NewInterpreter();
Py_NewInterpreter();
Py_NewInterpreter();
PyThreadState *tstate = PyThreadState_Get();
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I think this test working before was a fluke.Py_NewInterpreter changes the thread state, and I don't think we support callingPy_Finalize from a subinterpreter.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It's probably okay but your change helps future-proof the test.

for (int i = 0; i < 3; ++i)
{
Py_EndInterpreter(Py_NewInterpreter());
PyThreadState_Swap(tstate);
}

Py_Finalize();

Expand Down
56 changes: 28 additions & 28 deletionsPython/pylifecycle.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1982,6 +1982,7 @@ resolve_final_tstate(_PyRuntimeState *runtime)
}
else {
/* Fall back to the current tstate. It's better than nothing. */
// XXX No it's not
main_tstate = tstate;
}
}
Expand DownExpand Up@@ -2027,6 +2028,16 @@ _Py_Finalize(_PyRuntimeState *runtime)

_PyAtExit_Call(tstate->interp);

/* Clean up any lingering subinterpreters.

Two preconditions need to be met here:

- This has to happen before _PyRuntimeState_SetFinalizing is
called, or else threads might get prematurely blocked.
- The world must not be stopped, as finalizers can run.
*/
finalize_subinterpreters();

assert(_PyThreadState_GET() == tstate);

/* Copy the core config, PyInterpreterState_Delete() free
Expand DownExpand Up@@ -2114,9 +2125,6 @@ _Py_Finalize(_PyRuntimeState *runtime)
_PyImport_FiniExternal(tstate->interp);
finalize_modules(tstate);

/* Clean up any lingering subinterpreters. */
finalize_subinterpreters();

/* Print debug stats if any */
_PyEval_Fini();

Expand DownExpand Up@@ -2400,9 +2408,8 @@ Py_NewInterpreter(void)
return tstate;
}

/* Delete an interpreter and its last thread. This requires that the
given thread state is current, that the thread has no remaining
frames, and that it is its interpreter's only remaining thread.
/* Delete an interpreter. This requires that the given thread state
is current, and that the thread has no remaining frames.
It is a fatal error to violate these constraints.

(Py_FinalizeEx() doesn't have these constraints -- it zaps
Expand DownExpand Up@@ -2432,14 +2439,15 @@ Py_EndInterpreter(PyThreadState *tstate)
_Py_FinishPendingCalls(tstate);

_PyAtExit_Call(tstate->interp);

if (tstate != interp->threads.head || tstate->next != NULL) {
Py_FatalError("not the last thread");
}
_PyRuntimeState *runtime = interp->runtime;
_PyEval_StopTheWorldAll(runtime);
PyThreadState *list = _PyThreadState_RemoveExcept(tstate);

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

// XXX Call something like _PyImport_Disable() here?

Expand DownExpand Up@@ -2470,6 +2478,8 @@ finalize_subinterpreters(void)
PyInterpreterState *main_interp = _PyInterpreterState_Main();
assert(final_tstate->interp == main_interp);
_PyRuntimeState *runtime = main_interp->runtime;
assert(!runtime->stoptheworld.world_stopped);
assert(_PyRuntimeState_GetFinalizing(runtime) == NULL);
struct pyinterpreters *interpreters = &runtime->interpreters;

/* Get the first interpreter in the list. */
Expand DownExpand Up@@ -2498,27 +2508,17 @@ finalize_subinterpreters(void)

/* Clean up all remaining subinterpreters. */
while (interp != NULL) {
assert(!_PyInterpreterState_IsRunningMain(interp));

/* Find the tstate to use for fini. We assume the interpreter
will have at most one tstate at this point. */
PyThreadState *tstate = interp->threads.head;
if (tstate != NULL) {
/* Ideally we would be able to use tstate as-is, and rely
on it being in a ready state: no exception set, not
running anything (tstate->current_frame), matching the
current thread ID (tstate->thread_id). To play it safe,
we always delete it and use a fresh tstate instead. */
assert(tstate != final_tstate);
_PyThreadState_Attach(tstate);
PyThreadState_Clear(tstate);
_PyThreadState_Detach(tstate);
PyThreadState_Delete(tstate);
/* Make a tstate for finalization. */
PyThreadState *tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_FINI);
if (tstate == NULL) {
// XXX Some graceful way to always get a thread state?
Py_FatalError("thread state allocation failed");
}
tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_FINI);

/*Destroy the subinterpreter. */
/*Enter the subinterpreter. */
_PyThreadState_Attach(tstate);

/* Destroy the subinterpreter. */
Py_EndInterpreter(tstate);
assert(_PyThreadState_GET() == NULL);

Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp