Skip to content

Commit

Permalink
pythongh-104812: Run Pending Calls in any Thread (pythongh-104813)
Browse files Browse the repository at this point in the history
For a while now, pending calls only run in the main thread (in the main interpreter).  This PR changes things to allow any thread run a pending call, unless the pending call was explicitly added for the main thread to run.
  • Loading branch information
ericsnowcurrently committed Jun 13, 2023
1 parent 27426d8 commit 0fb86c7
Show file tree
Hide file tree
Showing 16 changed files with 760 additions and 118 deletions.
2 changes: 2 additions & 0 deletions Include/cpython/ceval.h
Expand Up @@ -22,6 +22,8 @@ PyAPI_FUNC(PyObject *) _PyEval_EvalFrameDefault(PyThreadState *tstate, struct _P
PyAPI_FUNC(void) _PyEval_SetSwitchInterval(unsigned long microseconds);
PyAPI_FUNC(unsigned long) _PyEval_GetSwitchInterval(void);

PyAPI_FUNC(int) _PyEval_MakePendingCalls(PyThreadState *);

PyAPI_FUNC(Py_ssize_t) PyUnstable_Eval_RequestCodeExtraIndex(freefunc);
// Old name -- remove when this API changes:
_Py_DEPRECATED_EXTERNALLY(3.12) static inline Py_ssize_t
Expand Down
3 changes: 2 additions & 1 deletion Include/internal/pycore_ceval.h
Expand Up @@ -27,7 +27,8 @@ PyAPI_FUNC(void) _PyEval_SignalReceived(PyInterpreterState *interp);
PyAPI_FUNC(int) _PyEval_AddPendingCall(
PyInterpreterState *interp,
int (*func)(void *),
void *arg);
void *arg,
int mainthreadonly);
PyAPI_FUNC(void) _PyEval_SignalAsyncExc(PyInterpreterState *interp);
#ifdef HAVE_FORK
extern PyStatus _PyEval_ReInitThreads(PyThreadState *tstate);
Expand Down
38 changes: 20 additions & 18 deletions Include/internal/pycore_ceval_state.h
Expand Up @@ -13,6 +13,24 @@ extern "C" {
#include "pycore_gil.h" // struct _gil_runtime_state


struct _pending_calls {
int busy;
PyThread_type_lock lock;
/* Request for running pending calls. */
_Py_atomic_int calls_to_do;
/* Request for looking at the `async_exc` field of the current
thread state.
Guarded by the GIL. */
int async_exc;
#define NPENDINGCALLS 32
struct _pending_call {
int (*func)(void *);
void *arg;
} calls[NPENDINGCALLS];
int first;
int last;
};

typedef enum {
PERF_STATUS_FAILED = -1, // Perf trampoline is in an invalid state
PERF_STATUS_NO_INIT = 0, // Perf trampoline is not initialized
Expand Down Expand Up @@ -49,6 +67,8 @@ struct _ceval_runtime_state {
the main thread of the main interpreter can handle signals: see
_Py_ThreadCanHandleSignals(). */
_Py_atomic_int signals_pending;
/* Pending calls to be made only on the main thread. */
struct _pending_calls pending_mainthread;
};

#ifdef PY_HAVE_PERF_TRAMPOLINE
Expand All @@ -62,24 +82,6 @@ struct _ceval_runtime_state {
#endif


struct _pending_calls {
int busy;
PyThread_type_lock lock;
/* Request for running pending calls. */
_Py_atomic_int calls_to_do;
/* Request for looking at the `async_exc` field of the current
thread state.
Guarded by the GIL. */
int async_exc;
#define NPENDINGCALLS 32
struct {
int (*func)(void *);
void *arg;
} calls[NPENDINGCALLS];
int first;
int last;
};

struct _ceval_state {
/* This single variable consolidates all requests to break out of
the fast path in the eval loop. */
Expand Down
8 changes: 0 additions & 8 deletions Include/internal/pycore_pystate.h
Expand Up @@ -60,14 +60,6 @@ _Py_ThreadCanHandleSignals(PyInterpreterState *interp)
}


/* Only execute pending calls on the main thread. */
static inline int
_Py_ThreadCanHandlePendingCalls(void)
{
return _Py_IsMainThread();
}


/* Variable and static inline functions for in-line access to current thread
and interpreter state */

Expand Down
9 changes: 7 additions & 2 deletions Lib/test/support/threading_helper.py
Expand Up @@ -115,7 +115,11 @@ def join_thread(thread, timeout=None):

@contextlib.contextmanager
def start_threads(threads, unlock=None):
import faulthandler
try:
import faulthandler
except ImportError:
# It isn't supported on subinterpreters yet.
faulthandler = None
threads = list(threads)
started = []
try:
Expand Down Expand Up @@ -147,7 +151,8 @@ def start_threads(threads, unlock=None):
finally:
started = [t for t in started if t.is_alive()]
if started:
faulthandler.dump_traceback(sys.stdout)
if faulthandler is not None:
faulthandler.dump_traceback(sys.stdout)
raise AssertionError('Unable to join %d threads' % len(started))


Expand Down

0 comments on commit 0fb86c7

Please sign in to comment.