Skip to content

Commit e15443b

Browse files
committed
ceval: move eval_breaker to per-thread state
The eval_breaker variable is used as a signal to break out of the interpreter loop to handle signals, asynchronous exceptions, stop for GC, or release the GIL to let another thread run. We will be able to have multiple active threads running the interpreter loop so it's useful to move eval_breaker to per-thread state so that notifications can target a specific thread. The specific signals are combined as bits in eval_breaker to simplify atomic updates.
1 parent f546dbf commit e15443b

File tree

14 files changed

+116
-230
lines changed

14 files changed

+116
-230
lines changed

‎Include/cpython/pystate.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ struct _ts {
117117
/* thread status (attached, detached, gc) */
118118
int status;
119119

120+
uintptr_t eval_breaker;
121+
120122
/* Has been initialized to a safe state.
121123
122124
In order to be effective, this must be set to 0 during or right

‎Include/internal/pycore_ceval.h‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,10 @@ extern void _Py_FinishPendingCalls(PyThreadState *tstate);
2424
extern void _PyEval_InitRuntimeState(struct _ceval_runtime_state *);
2525
extern void _PyEval_InitState(struct _ceval_state *, PyThread_type_lock);
2626
extern void _PyEval_FiniState(struct _ceval_state *ceval);
27-
PyAPI_FUNC(void) _PyEval_SignalReceived(PyInterpreterState *interp);
2827
PyAPI_FUNC(int) _PyEval_AddPendingCall(
2928
PyInterpreterState *interp,
3029
int (*func)(void *),
3130
void *arg);
32-
PyAPI_FUNC(void) _PyEval_SignalAsyncExc(PyInterpreterState *interp);
3331
#ifdef HAVE_FORK
3432
extern PyStatus _PyEval_ReInitThreads(PyThreadState *tstate);
3533
#endif

‎Include/internal/pycore_ceval_state.h‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,6 @@ struct _pending_calls {
8383

8484
struct _ceval_state {
8585
int recursion_limit;
86-
/* This single variable consolidates all requests to break out of
87-
the fast path in the eval loop. */
88-
_Py_atomic_int eval_breaker;
89-
/* Request for dropping the GIL */
90-
_Py_atomic_int gil_drop_request;
91-
/* The GC is ready to be executed */
92-
_Py_atomic_int gc_scheduled;
9386
struct _pending_calls pending;
9487
};
9588

‎Include/internal/pycore_gil.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ struct _gil_runtime_state {
2828
/* Last PyThreadState holding / having held the GIL. This helps us
2929
know whether anyone else was scheduled after we dropped the GIL. */
3030
_Py_atomic_address last_holder;
31+
/* Current PyThreadState holding the GIL. Protected by mutex. */
32+
PyThreadState *holder;
3133
/* Whether the GIL is already taken (-1 if uninitialized). This is
3234
atomic because it can be read without any lock taken in ceval.c. */
3335
_Py_atomic_int locked;

‎Include/internal/pycore_pystate.h‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ enum _threadstatus {
1616
_Py_THREAD_GC = 2
1717
};
1818

19+
enum {
20+
EVAL_PLEASE_STOP = 1U << 0,
21+
EVAL_PENDING_SIGNALS = 1U << 1,
22+
EVAL_PENDING_CALLS = 1U << 2,
23+
EVAL_DROP_GIL = 1U << 3,
24+
EVAL_ASYNC_EXC = 1U << 4,
25+
EVAL_EXPLICIT_MERGE = 1U << 5,
26+
EVAL_GC = 1U << 6
27+
};
28+
1929
/* Check if the current thread is the main thread.
2030
Use _Py_IsMainInterpreter() to check if it's the main interpreter. */
2131
static inline int
@@ -147,6 +157,25 @@ PyAPI_FUNC(void) _PyThreadState_DeleteExcept(
147157
_PyRuntimeState *runtime,
148158
PyThreadState *tstate);
149159

160+
static inline void
161+
_PyThreadState_Signal(PyThreadState *tstate, uintptr_t bit)
162+
{
163+
_Py_atomic_or_uintptr(&tstate->eval_breaker, bit);
164+
}
165+
166+
static inline void
167+
_PyThreadState_Unsignal(PyThreadState *tstate, uintptr_t bit)
168+
{
169+
_Py_atomic_and_uintptr(&tstate->eval_breaker, ~bit);
170+
}
171+
172+
static inline int
173+
_PyThreadState_IsSignalled(PyThreadState *tstate, uintptr_t bit)
174+
{
175+
uintptr_t b = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker);
176+
return (b & bit) != 0;
177+
}
178+
150179

151180
static inline void
152181
_PyThreadState_UpdateTracingState(PyThreadState *tstate)

‎Include/internal/pycore_runtime.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ typedef struct pyruntimestate {
119119
} xidregistry;
120120

121121
unsigned long main_thread;
122+
PyThreadState *main_tstate;
122123

123124
PyWideStringList orig_argv;
124125

‎Modules/gcmodule.c‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2258,10 +2258,9 @@ _Py_ScheduleGC(PyInterpreterState *interp)
22582258
if (gcstate->collecting == 1) {
22592259
return;
22602260
}
2261-
struct _ceval_state *ceval = &interp->ceval;
2262-
if (!_Py_atomic_load_relaxed(&ceval->gc_scheduled)) {
2263-
_Py_atomic_store_relaxed(&ceval->gc_scheduled, 1);
2264-
_Py_atomic_store_relaxed(&ceval->eval_breaker, 1);
2261+
PyThreadState *tstate = _PyThreadState_GET();
2262+
if (!_PyThreadState_IsSignalled(tstate, EVAL_GC)) {
2263+
_PyThreadState_Signal(tstate, EVAL_GC);
22652264
}
22662265
}
22672266

‎Modules/signalmodule.c‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,9 @@ trip_signal(int sig_num)
276276
/* Signals are always handled by the main interpreter */
277277
PyInterpreterState *interp = _PyInterpreterState_Main();
278278

279-
/* Notify ceval.c */
280-
_PyEval_SignalReceived(interp);
279+
/* Notify main thread */
280+
_PyRuntimeState *runtime = &_PyRuntime;
281+
_PyThreadState_Signal(runtime->main_tstate, EVAL_PENDING_SIGNALS);
281282

282283
/* And then write to the wakeup fd *after* setting all the globals and
283284
doing the _PyEval_SignalReceived. We used to write to the wakeup fd
@@ -1764,9 +1765,8 @@ PyErr_CheckSignals(void)
17641765
Python code to ensure signals are handled. Checking for the GC here
17651766
allows long running native code to clean cycles created using the C-API
17661767
even if it doesn't run the evaluation loop */
1767-
struct _ceval_state *interp_ceval_state = &tstate->interp->ceval;
1768-
if (_Py_atomic_load_relaxed(&interp_ceval_state->gc_scheduled)) {
1769-
_Py_atomic_store_relaxed(&interp_ceval_state->gc_scheduled, 0);
1768+
if (_PyThreadState_IsSignalled(tstate, EVAL_GC)) {
1769+
_PyThreadState_Unsignal(tstate, EVAL_GC);
17701770
_Py_RunGC(tstate);
17711771
}
17721772

‎Python/bytecodes.c‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ dummy_func(
124124
inst(RESUME, (--)) {
125125
assert(tstate->cframe == &cframe);
126126
assert(frame == cframe.current_frame);
127-
if (_Py_atomic_load_relaxed_int32(eval_breaker) && oparg < 2) {
128-
goto handle_eval_breaker;
127+
if (oparg < 2) {
128+
CHECK_EVAL_BREAKER();
129129
}
130130
}
131131

‎Python/ceval.c‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
719719

720720
#define CHECK_EVAL_BREAKER() \
721721
_Py_CHECK_EMSCRIPTEN_SIGNALS_PERIODICALLY(); \
722-
if (_Py_atomic_load_relaxed_int32(eval_breaker)) { \
722+
if (!_Py_atomic_uintptr_is_zero(&tstate->eval_breaker)) { \
723723
goto handle_eval_breaker; \
724724
}
725725

@@ -1073,7 +1073,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
10731073
// for the big switch below (in combination with the EXTRA_CASES macro).
10741074
uint8_t opcode; /* Current opcode */
10751075
int oparg; /* Current opcode argument, if any */
1076-
_Py_atomic_int * const eval_breaker = &tstate->interp->ceval.eval_breaker;
10771076
#ifdef LLTRACE
10781077
int lltrace = 0;
10791078
#endif

0 commit comments

Comments
 (0)