gh-111964: Implement stop-the-world pauses (gh-112471)

The `--disable-gil` builds occasionally need to pause all but one thread.  Some
examples include:

* Cyclic garbage collection, where this is often called a "stop the world event"
* Before calling `fork()`, to ensure a consistent state for internal data structures
* During interpreter shutdown, to ensure that daemon threads aren't accessing Python objects

This adds the following functions to implement global and per-interpreter pauses:

* `_PyEval_StopTheWorldAll()` and `_PyEval_StartTheWorldAll()` (for the global runtime)
* `_PyEval_StopTheWorld()` and `_PyEval_StartTheWorld()` (per-interpreter)

(The function names may change.)

These functions are no-ops outside of the `--disable-gil` build.
This commit is contained in:
Sam Gross 2024-01-23 13:08:23 -05:00 committed by GitHub
parent 5f1997896d
commit 441affc9e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 336 additions and 29 deletions

View file

@ -21,23 +21,27 @@ extern "C" {
// interpreter at the same time. Only the "bound" thread may perform the
// transitions between "attached" and "detached" on its own PyThreadState.
//
// The "gc" state is used to implement stop-the-world pauses, such as for
// cyclic garbage collection. It is only used in `--disable-gil` builds. It is
// similar to the "detached" state, but only the thread performing a
// stop-the-world pause may transition threads between the "detached" and "gc"
// states. A thread trying to "attach" from the "gc" state will block until
// it is transitioned back to "detached" when the stop-the-world pause is
// complete.
// The "suspended" state is used to implement stop-the-world pauses, such as
// for cyclic garbage collection. It is only used in `--disable-gil` builds.
// The "suspended" state is similar to the "detached" state in that in both
// states the thread is not allowed to call most Python APIs. However, unlike
// the "detached" state, a thread may not transition itself out from the
// "suspended" state. Only the thread performing a stop-the-world pause may
// transition a thread from the "suspended" state back to the "detached" state.
//
// State transition diagram:
//
// (bound thread) (stop-the-world thread)
// [attached] <-> [detached] <-> [gc]
// [attached] <-> [detached] <-> [suspended]
// | ^
// +---------------------------->---------------------------+
// (bound thread)
//
// See `_PyThreadState_Attach()` and `_PyThreadState_Detach()`.
// The (bound thread) and (stop-the-world thread) labels indicate which thread
// is allowed to perform the transition.
#define _Py_THREAD_DETACHED 0
#define _Py_THREAD_ATTACHED 1
#define _Py_THREAD_GC 2
#define _Py_THREAD_SUSPENDED 2
/* Check if the current thread is the main thread.
@ -140,13 +144,36 @@ _PyThreadState_GET(void)
//
// High-level code should generally call PyEval_RestoreThread() instead, which
// calls this function.
void _PyThreadState_Attach(PyThreadState *tstate);
extern void _PyThreadState_Attach(PyThreadState *tstate);
// Detaches the current thread from the interpreter.
//
// High-level code should generally call PyEval_SaveThread() instead, which
// calls this function.
void _PyThreadState_Detach(PyThreadState *tstate);
extern void _PyThreadState_Detach(PyThreadState *tstate);
// Detaches the current thread to the "suspended" state if a stop-the-world
// pause is in progress.
//
// If there is no stop-the-world pause in progress, then the thread switches
// to the "detached" state.
extern void _PyThreadState_Suspend(PyThreadState *tstate);
// Perform a stop-the-world pause for all threads in the all interpreters.
//
// Threads in the "attached" state are paused and transitioned to the "GC"
// state. Threads in the "detached" state switch to the "GC" state, preventing
// them from reattaching until the stop-the-world pause is complete.
//
// NOTE: This is a no-op outside of Py_GIL_DISABLED builds.
extern void _PyEval_StopTheWorldAll(_PyRuntimeState *runtime);
extern void _PyEval_StartTheWorldAll(_PyRuntimeState *runtime);
// Perform a stop-the-world pause for threads in the specified interpreter.
//
// NOTE: This is a no-op outside of Py_GIL_DISABLED builds.
extern void _PyEval_StopTheWorld(PyInterpreterState *interp);
extern void _PyEval_StartTheWorld(PyInterpreterState *interp);
static inline void