Skip to content

Commit

Permalink
ceval: move eval_breaker to per-thread state
Browse files Browse the repository at this point in the history
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 now can 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.
  • Loading branch information
colesbury committed Mar 3, 2020
1 parent 6777b5f commit 66ef0ef
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 212 deletions.
2 changes: 2 additions & 0 deletions Include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ struct _ts {
/* thread status */
int32_t status;

uintptr_t eval_breaker;

mi_heap_t *heap_backing;
mi_heap_t *heap_obj;
mi_heap_t *heap_gc;
Expand Down
5 changes: 0 additions & 5 deletions Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,11 @@ PyAPI_FUNC(void) _Py_FinishPendingCalls(PyThreadState *tstate);
PyAPI_FUNC(void) _PyEval_Initialize(struct _ceval_runtime_state *);
PyAPI_FUNC(void) _PyEval_FiniThreads(
struct _ceval_runtime_state *ceval);
PyAPI_FUNC(void) _PyEval_SignalReceived(
struct _ceval_runtime_state *ceval);
PyAPI_FUNC(int) _PyEval_AddPendingCall(
PyThreadState *tstate,
struct _ceval_runtime_state *ceval,
int (*func)(void *),
void *arg);
PyAPI_FUNC(void) _PyEval_SignalAsyncExc(
struct _ceval_runtime_state *ceval);
PyAPI_FUNC(void) _PyEval_ComputeEvalBreaker(void);
PyAPI_FUNC(void) _PyEval_ReInitThreads(
struct pyruntimestate *runtime);
PyAPI_FUNC(void) _PyEval_SetCoroutineOriginTrackingDepth(
Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_gil.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ struct _gil_runtime_state {
/* Last PyThreadState holding / having held the GIL. This helps us
know whether anyone else was scheduled after we dropped the GIL. */
_Py_atomic_address last_holder;
/* Current PyThreadState holding the GIL. Protected by mutex. */
PyThreadState *holder;
/* Whether the GIL is already taken (-1 if uninitialized). This is
atomic because it can be read without any lock taken in ceval.c. */
_Py_atomic_int locked;
Expand Down
19 changes: 10 additions & 9 deletions Include/internal/pycore_pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ typedef enum {
_Py_THREAD_GC,
} _Py_thread_status;

enum {
EVAL_PLEASE_STOP = 1U << 0,
EVAL_PENDING_SIGNALS = 1U << 1,
EVAL_PENDING_CALLS = 1U << 2,
EVAL_DROP_GIL = 1U << 3,
EVAL_ASYNC_EXC = 1U << 4
};

/* ceval state */

Expand Down Expand Up @@ -52,16 +59,7 @@ struct _ceval_runtime_state {
c_tracefunc. This speeds up the if statement in
PyEval_EvalFrameEx() after fast_next_opcode. */
int tracing_possible;
/* This single variable consolidates all requests to break out of
the fast path in the eval loop. */
_Py_atomic_int eval_breaker;
/* Request for dropping the GIL */
_Py_atomic_int gil_drop_request;
struct _pending_calls pending;
/* Request for checking signals. */
_Py_atomic_int signals_pending;
/* Request to stop all threads (e.g. for GC). */
_Py_atomic_int please_stop;
/* Enable the GIL */
int use_gil;
struct _gil_runtime_state gil;
Expand Down Expand Up @@ -261,6 +259,7 @@ typedef struct pyruntimestate {
} xidregistry;

unsigned long main_thread;
PyThreadState *main_tstate;

#define NEXITFUNCS 32
void (*exitfuncs[NEXITFUNCS])(void);
Expand Down Expand Up @@ -389,6 +388,8 @@ PyAPI_FUNC(void) _PyThreadState_DeleteExcept(
PyAPI_FUNC(int) _PyThreadState_GetStatus(PyThreadState *tstate);
PyAPI_FUNC(void) _PyThreadState_GC_Park(PyThreadState *tstate);
PyAPI_FUNC(void) _PyThreadState_GC_Stop(PyThreadState *tstate);
PyAPI_FUNC(void) _PyThreadState_Signal(PyThreadState *tstate, uintptr_t bit);
PyAPI_FUNC(void) _PyThreadState_Unsignal(PyThreadState *tstate, uintptr_t bit);

PyAPI_FUNC(PyThreadState *) _PyThreadState_Swap(
struct _gilstate_runtime_state *gilstate,
Expand Down
4 changes: 2 additions & 2 deletions Modules/signalmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -259,10 +259,10 @@ trip_signal(int sig_num)
cleared in PyErr_CheckSignals() before .tripped. */
_Py_atomic_store(&is_tripped, 1);

/* Notify ceval.c */
/* Notify main thread */
_PyRuntimeState *runtime = &_PyRuntime;
_PyThreadState_Signal(runtime->main_tstate, EVAL_PENDING_SIGNALS);
PyThreadState *tstate = _PyRuntimeState_GetThreadState(runtime);
_PyEval_SignalReceived(&runtime->ceval);

/* And then write to the wakeup fd *after* setting all the globals and
doing the _PyEval_SignalReceived. We used to write to the wakeup fd
Expand Down
Loading

0 comments on commit 66ef0ef

Please sign in to comment.