Skip to content

Commit

Permalink
[3.12] gh-104812: Run Pending Calls in any Thread (gh-104813) (gh-105752
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.
(cherry picked from commit 757b402)
  • Loading branch information
ericsnowcurrently committed Jun 14, 2023
1 parent 75239d5 commit 33d3069
Show file tree
Hide file tree
Showing 17 changed files with 1,311 additions and 658 deletions.
1,089 changes: 549 additions & 540 deletions Doc/data/python3.12.abi

Large diffs are not rendered by default.

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 33d3069

Please sign in to comment.