Skip to content

Commit 8c25c8f

Browse files
committed
pythongh-116522: Refactor _PyThreadState_DeleteExcept
Split `_PyThreadState_DeleteExcept` into two functions: - `_PyThreadState_RemoveExcept` removes all thread states other than one passed as an argument. It returns the removed thread states as a linked list. - `_PyThreadState_DeleteList` deletes those dead thread states. It may call destructors, so we want to "start the world" before calling `_PyThreadState_DeleteList` to avoid potential deadlocks.
1 parent bbee57f commit 8c25c8f

File tree

5 files changed

+41
-23
lines changed

5 files changed

+41
-23
lines changed

Include/internal/pycore_pystate.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,8 @@ extern PyThreadState * _PyThreadState_New(
215215
PyInterpreterState *interp,
216216
int whence);
217217
extern void _PyThreadState_Bind(PyThreadState *tstate);
218-
extern void _PyThreadState_DeleteExcept(PyThreadState *tstate);
218+
extern PyThreadState * _PyThreadState_RemoveExcept(PyThreadState *tstate);
219+
extern void _PyThreadState_DeleteList(PyThreadState *list);
219220
extern void _PyThreadState_ClearMimallocHeaps(PyThreadState *tstate);
220221

221222
// Export for '_testinternalcapi' shared extension

Modules/posixmodule.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,14 @@ PyOS_AfterFork_Child(void)
660660
goto fatal_error;
661661
}
662662

663+
// Remove the dead thread states. We "start the world" once we are the only
664+
// thread state left to undo the stop the world call in `PyOS_BeforeFork`.
665+
// That needs to happen before `_PyThreadState_DeleteList`, because that
666+
// may call destructors.
667+
PyThreadState *list = _PyThreadState_RemoveExcept(tstate);
668+
_PyEval_StartTheWorldAll(&_PyRuntime);
669+
_PyThreadState_DeleteList(list);
670+
663671
status = _PyImport_ReInitLock(tstate->interp);
664672
if (_PyStatus_EXCEPTION(status)) {
665673
goto fatal_error;

Python/ceval_gil.c

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -579,9 +579,8 @@ PyEval_ReleaseThread(PyThreadState *tstate)
579579
}
580580

581581
#ifdef HAVE_FORK
582-
/* This function is called from PyOS_AfterFork_Child to destroy all threads
583-
which are not running in the child process, and clear internal locks
584-
which might be held by those threads. */
582+
/* This function is called from PyOS_AfterFork_Child to re-initialize the
583+
GIL and pending calls lock. */
585584
PyStatus
586585
_PyEval_ReInitThreads(PyThreadState *tstate)
587586
{
@@ -598,8 +597,6 @@ _PyEval_ReInitThreads(PyThreadState *tstate)
598597
struct _pending_calls *pending = &tstate->interp->ceval.pending;
599598
_PyMutex_at_fork_reinit(&pending->mutex);
600599

601-
/* Destroy all threads except the current one */
602-
_PyThreadState_DeleteExcept(tstate);
603600
return _PyStatus_OK();
604601
}
605602
#endif

Python/pylifecycle.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1930,8 +1930,11 @@ Py_FinalizeEx(void)
19301930
will be called in the current Python thread. Since
19311931
_PyRuntimeState_SetFinalizing() has been called, no other Python thread
19321932
can take the GIL at this point: if they try, they will exit
1933-
immediately. */
1934-
_PyThreadState_DeleteExcept(tstate);
1933+
immediately. We start the world once we are the only thread state left,
1934+
before we call destructors. */
1935+
PyThreadState *list = _PyThreadState_RemoveExcept(tstate);
1936+
_PyEval_StartTheWorldAll(runtime);
1937+
_PyThreadState_DeleteList(list);
19351938

19361939
/* At this point no Python code should be running at all.
19371940
The only thread state left should be the main thread of the main

Python/pystate.c

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,15 +1733,17 @@ PyThreadState_DeleteCurrent(void)
17331733
}
17341734

17351735

1736-
/*
1737-
* Delete all thread states except the one passed as argument.
1738-
* Note that, if there is a current thread state, it *must* be the one
1739-
* passed as argument. Also, this won't touch any other interpreters
1740-
* than the current one, since we don't know which thread state should
1741-
* be kept in those other interpreters.
1742-
*/
1743-
void
1744-
_PyThreadState_DeleteExcept(PyThreadState *tstate)
1736+
// Unlinks and removes all thread states from `tstate->interp`, with the
1737+
// exception of the one passed as an argument. However, it does not delete
1738+
// these thread states. Instead, it returns the removed thread states as a
1739+
// linked list.
1740+
//
1741+
// Note that if there is a current thread state, it *must* be the one
1742+
// passed as argument. Also, this won't touch any interpreters other
1743+
// than the current one, since we don't know which thread state should
1744+
// be kept in those other interpreters.
1745+
PyThreadState *
1746+
_PyThreadState_RemoveExcept(PyThreadState *tstate)
17451747
{
17461748
assert(tstate != NULL);
17471749
PyInterpreterState *interp = tstate->interp;
@@ -1753,8 +1755,7 @@ _PyThreadState_DeleteExcept(PyThreadState *tstate)
17531755

17541756
HEAD_LOCK(runtime);
17551757
/* Remove all thread states, except tstate, from the linked list of
1756-
thread states. This will allow calling PyThreadState_Clear()
1757-
without holding the lock. */
1758+
thread states. */
17581759
PyThreadState *list = interp->threads.head;
17591760
if (list == tstate) {
17601761
list = tstate->next;
@@ -1769,11 +1770,19 @@ _PyThreadState_DeleteExcept(PyThreadState *tstate)
17691770
interp->threads.head = tstate;
17701771
HEAD_UNLOCK(runtime);
17711772

1772-
_PyEval_StartTheWorldAll(runtime);
1773+
return list;
1774+
}
1775+
1776+
// Deletes the thread states in the linked list `list`.
1777+
//
1778+
// This is intended to be used in conjunction with _PyThreadState_RemoveExcept.
1779+
void
1780+
_PyThreadState_DeleteList(PyThreadState *list)
1781+
{
1782+
// The world can't be stopped because we PyThreadState_Clear() can
1783+
// call destructors.
1784+
assert(!_PyRuntime.stoptheworld.world_stopped);
17731785

1774-
/* Clear and deallocate all stale thread states. Even if this
1775-
executes Python code, we should be safe since it executes
1776-
in the current thread, not one of the stale threads. */
17771786
PyThreadState *p, *next;
17781787
for (p = list; p; p = next) {
17791788
next = p->next;

0 commit comments

Comments
 (0)