Skip to content

Commit 441affc

Browse files
authored
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.
1 parent 5f19978 commit 441affc

File tree

10 files changed

+336
-29
lines changed

10 files changed

+336
-29
lines changed

Include/cpython/pystate.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ struct _ts {
102102
#endif
103103
int _whence;
104104

105-
/* Thread state (_Py_THREAD_ATTACHED, _Py_THREAD_DETACHED, _Py_THREAD_GC).
105+
/* Thread state (_Py_THREAD_ATTACHED, _Py_THREAD_DETACHED, _Py_THREAD_SUSPENDED).
106106
See Include/internal/pycore_pystate.h for more details. */
107107
int state;
108108

Include/internal/pycore_ceval.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ void _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame)
205205
#define _PY_CALLS_TO_DO_BIT 2
206206
#define _PY_ASYNC_EXCEPTION_BIT 3
207207
#define _PY_GC_SCHEDULED_BIT 4
208+
#define _PY_EVAL_PLEASE_STOP_BIT 5
208209

209210
/* Reserve a few bits for future use */
210211
#define _PY_EVAL_EVENTS_BITS 8

Include/internal/pycore_interp.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,22 @@ struct _Py_long_state {
4141
int max_str_digits;
4242
};
4343

44+
// Support for stop-the-world events. This exists in both the PyRuntime struct
45+
// for global pauses and in each PyInterpreterState for per-interpreter pauses.
46+
struct _stoptheworld_state {
47+
PyMutex mutex; // Serializes stop-the-world attempts.
48+
49+
// NOTE: The below fields are protected by HEAD_LOCK(runtime), not by the
50+
// above mutex.
51+
bool requested; // Set when a pause is requested.
52+
bool world_stopped; // Set when the world is stopped.
53+
bool is_global; // Set when contained in PyRuntime struct.
54+
55+
PyEvent stop_event; // Set when thread_countdown reaches zero.
56+
Py_ssize_t thread_countdown; // Number of threads that must pause.
57+
58+
PyThreadState *requester; // Thread that requested the pause (may be NULL).
59+
};
4460

4561
/* cross-interpreter data registry */
4662

@@ -166,6 +182,7 @@ struct _is {
166182

167183
struct _warnings_runtime_state warnings;
168184
struct atexit_state atexit;
185+
struct _stoptheworld_state stoptheworld;
169186

170187
#if defined(Py_GIL_DISABLED)
171188
struct _mimalloc_interp_state mimalloc;

Include/internal/pycore_llist.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ struct llist_node {
3737
};
3838

3939
// Get the struct containing a node.
40-
#define llist_data(node, type, member) \
41-
(type*)((char*)node - offsetof(type, member))
40+
#define llist_data(node, type, member) (_Py_CONTAINER_OF(node, type, member))
4241

4342
// Iterate over a list.
4443
#define llist_for_each(node, head) \

Include/internal/pycore_pystate.h

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,27 @@ extern "C" {
2121
// interpreter at the same time. Only the "bound" thread may perform the
2222
// transitions between "attached" and "detached" on its own PyThreadState.
2323
//
24-
// The "gc" state is used to implement stop-the-world pauses, such as for
25-
// cyclic garbage collection. It is only used in `--disable-gil` builds. It is
26-
// similar to the "detached" state, but only the thread performing a
27-
// stop-the-world pause may transition threads between the "detached" and "gc"
28-
// states. A thread trying to "attach" from the "gc" state will block until
29-
// it is transitioned back to "detached" when the stop-the-world pause is
30-
// complete.
24+
// The "suspended" state is used to implement stop-the-world pauses, such as
25+
// for cyclic garbage collection. It is only used in `--disable-gil` builds.
26+
// The "suspended" state is similar to the "detached" state in that in both
27+
// states the thread is not allowed to call most Python APIs. However, unlike
28+
// the "detached" state, a thread may not transition itself out from the
29+
// "suspended" state. Only the thread performing a stop-the-world pause may
30+
// transition a thread from the "suspended" state back to the "detached" state.
3131
//
3232
// State transition diagram:
3333
//
3434
// (bound thread) (stop-the-world thread)
35-
// [attached] <-> [detached] <-> [gc]
35+
// [attached] <-> [detached] <-> [suspended]
36+
// | ^
37+
// +---------------------------->---------------------------+
38+
// (bound thread)
3639
//
37-
// See `_PyThreadState_Attach()` and `_PyThreadState_Detach()`.
40+
// The (bound thread) and (stop-the-world thread) labels indicate which thread
41+
// is allowed to perform the transition.
3842
#define _Py_THREAD_DETACHED 0
3943
#define _Py_THREAD_ATTACHED 1
40-
#define _Py_THREAD_GC 2
44+
#define _Py_THREAD_SUSPENDED 2
4145

4246

4347
/* Check if the current thread is the main thread.
@@ -140,13 +144,36 @@ _PyThreadState_GET(void)
140144
//
141145
// High-level code should generally call PyEval_RestoreThread() instead, which
142146
// calls this function.
143-
void _PyThreadState_Attach(PyThreadState *tstate);
147+
extern void _PyThreadState_Attach(PyThreadState *tstate);
144148

145149
// Detaches the current thread from the interpreter.
146150
//
147151
// High-level code should generally call PyEval_SaveThread() instead, which
148152
// calls this function.
149-
void _PyThreadState_Detach(PyThreadState *tstate);
153+
extern void _PyThreadState_Detach(PyThreadState *tstate);
154+
155+
// Detaches the current thread to the "suspended" state if a stop-the-world
156+
// pause is in progress.
157+
//
158+
// If there is no stop-the-world pause in progress, then the thread switches
159+
// to the "detached" state.
160+
extern void _PyThreadState_Suspend(PyThreadState *tstate);
161+
162+
// Perform a stop-the-world pause for all threads in the all interpreters.
163+
//
164+
// Threads in the "attached" state are paused and transitioned to the "GC"
165+
// state. Threads in the "detached" state switch to the "GC" state, preventing
166+
// them from reattaching until the stop-the-world pause is complete.
167+
//
168+
// NOTE: This is a no-op outside of Py_GIL_DISABLED builds.
169+
extern void _PyEval_StopTheWorldAll(_PyRuntimeState *runtime);
170+
extern void _PyEval_StartTheWorldAll(_PyRuntimeState *runtime);
171+
172+
// Perform a stop-the-world pause for threads in the specified interpreter.
173+
//
174+
// NOTE: This is a no-op outside of Py_GIL_DISABLED builds.
175+
extern void _PyEval_StopTheWorld(PyInterpreterState *interp);
176+
extern void _PyEval_StartTheWorld(PyInterpreterState *interp);
150177

151178

152179
static inline void

Include/internal/pycore_runtime.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,13 @@ typedef struct pyruntimestate {
227227
struct _faulthandler_runtime_state faulthandler;
228228
struct _tracemalloc_runtime_state tracemalloc;
229229

230+
// The rwmutex is used to prevent overlapping global and per-interpreter
231+
// stop-the-world events. Global stop-the-world events lock the mutex
232+
// exclusively (as a "writer"), while per-interpreter stop-the-world events
233+
// lock it non-exclusively (as "readers").
234+
_PyRWMutex stoptheworld_mutex;
235+
struct _stoptheworld_state stoptheworld;
236+
230237
PyPreConfig preconfig;
231238

232239
// Audit values must be preserved when Py_Initialize()/Py_Finalize()

Include/internal/pycore_runtime_init.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ extern PyTypeObject _PyExc_MemoryError;
116116
}, \
117117
.faulthandler = _faulthandler_runtime_state_INIT, \
118118
.tracemalloc = _tracemalloc_runtime_state_INIT, \
119+
.stoptheworld = { \
120+
.is_global = 1, \
121+
}, \
119122
.float_state = { \
120123
.float_format = _py_float_format_unknown, \
121124
.double_format = _py_float_format_unknown, \

Include/pymacro.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@
160160
Py_FatalError("Unreachable C code path reached")
161161
#endif
162162

163+
#define _Py_CONTAINER_OF(ptr, type, member) \
164+
(type*)((char*)ptr - offsetof(type, member))
165+
163166
// Prevent using an expression as a l-value.
164167
// For example, "int x; _Py_RVALUE(x) = 1;" fails with a compiler error.
165168
#define _Py_RVALUE(EXPR) ((void)0, (EXPR))

Python/ceval_gil.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,15 @@ _Py_HandlePending(PyThreadState *tstate)
949949
{
950950
PyInterpreterState *interp = tstate->interp;
951951

952+
/* Stop-the-world */
953+
if (_Py_eval_breaker_bit_is_set(interp, _PY_EVAL_PLEASE_STOP_BIT)) {
954+
_Py_set_eval_breaker_bit(interp, _PY_EVAL_PLEASE_STOP_BIT, 0);
955+
_PyThreadState_Suspend(tstate);
956+
957+
/* The attach blocks until the stop-the-world event is complete. */
958+
_PyThreadState_Attach(tstate);
959+
}
960+
952961
/* Pending signals */
953962
if (_Py_eval_breaker_bit_is_set(interp, _PY_SIGNALS_PENDING_BIT)) {
954963
if (handle_signals(tstate) != 0) {

0 commit comments

Comments
 (0)