Skip to content

Commit 9cd75b7

Browse files
committed
Atomically check if there are threads, atexit callbacks, or pending calls.
1 parent 9ccdb5f commit 9cd75b7

File tree

2 files changed

+38
-4
lines changed

2 files changed

+38
-4
lines changed

Modules/_threadmodule.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ thread_run(void *boot_raw)
347347
{
348348
struct bootstate *boot = (struct bootstate *) boot_raw;
349349
PyThreadState *tstate = boot->tstate;
350+
PyInterpreterState *interp = tstate->interp;
350351

351352
// Wait until the handle is marked as running
352353
PyEvent_Wait(&boot->handle_ready);
@@ -373,7 +374,7 @@ thread_run(void *boot_raw)
373374

374375
_PyThreadState_Bind(tstate);
375376
PyEval_AcquireThread(tstate);
376-
_Py_atomic_add_ssize(&tstate->interp->threads.count, 1);
377+
_Py_atomic_add_ssize(&interp->threads.count, 1);
377378

378379
PyObject *res = PyObject_Call(boot->func, boot->args, boot->kwargs);
379380
if (res == NULL) {
@@ -391,13 +392,13 @@ thread_run(void *boot_raw)
391392

392393
thread_bootstate_free(boot, 1);
393394

394-
_Py_atomic_add_ssize(&tstate->interp->threads.count, -1);
395+
_Py_atomic_add_ssize(&interp->threads.count, -1);
395396
PyThreadState_Clear(tstate);
396397
_PyThreadState_DeleteCurrent(tstate);
397398

398399
exit:
399400
// Don't need to wait for this thread anymore
400-
remove_from_shutdown_handles(handle, _PyInterpreterState_GET());
401+
remove_from_shutdown_handles(handle, interp);
401402

402403
_PyEvent_Notify(&handle->thread_is_exiting);
403404
ThreadHandle_decref(handle);
@@ -1881,7 +1882,7 @@ do_start_new_thread(thread_module_state *state, PyObject *func, PyObject *args,
18811882

18821883
if (ThreadHandle_start(handle, func, args, kwargs) < 0) {
18831884
if (!daemon) {
1884-
remove_from_shutdown_handles(handle, _PyInterpreterState_GET());
1885+
remove_from_shutdown_handles(handle, interp);
18851886
}
18861887
return -1;
18871888
}

Python/pylifecycle.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2003,9 +2003,35 @@ resolve_final_tstate(_PyRuntimeState *runtime)
20032003
return main_tstate;
20042004
}
20052005

2006+
static int
2007+
interp_has_threads(PyInterpreterState *interp)
2008+
{
2009+
assert(interp != NULL);
2010+
assert(interp->threads.head != NULL);
2011+
return interp->threads.head->next != NULL;
2012+
}
2013+
2014+
static int
2015+
interp_has_pending_calls(PyInterpreterState *interp)
2016+
{
2017+
assert(interp != NULL);
2018+
return interp->ceval.pending.npending != 0;
2019+
}
2020+
2021+
static int
2022+
interp_has_atexit_callbacks(PyInterpreterState *interp)
2023+
{
2024+
assert(interp != NULL);
2025+
assert(interp->atexit.callbacks != NULL);
2026+
assert(PyList_CheckExact(interp->atexit.callbacks));
2027+
return PyList_GET_SIZE(interp->atexit.callbacks) != 0;
2028+
}
2029+
20062030
static void
20072031
make_pre_finalization_calls(PyThreadState *tstate)
20082032
{
2033+
assert(tstate != NULL);
2034+
PyInterpreterState *interp = tstate->interp;
20092035
/* Each of these functions can start one another, e.g. a pending call
20102036
* could start a thread or vice versa. To ensure that we properly clean
20112037
* call everything, we run these in a loop until none of them run anything. */
@@ -2028,7 +2054,14 @@ make_pre_finalization_calls(PyThreadState *tstate)
20282054

20292055
_PyAtExit_Call(tstate->interp);
20302056

2057+
_PyRWMutex_Lock(&tstate->interp->prefini_mutex);
2058+
int should_continue = (interp_has_threads(interp)
2059+
|| interp_has_atexit_callbacks(interp)
2060+
|| interp_has_pending_calls(interp));
20312061
_PyRWMutex_Unlock(&tstate->interp->prefini_mutex);
2062+
if (!should_continue) {
2063+
break;
2064+
}
20322065
}
20332066
}
20342067

0 commit comments

Comments
 (0)