Skip to content

Commit 757b402

Browse files
gh-104812: Run Pending Calls in any Thread (gh-104813)
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.
1 parent 4e80082 commit 757b402

File tree

16 files changed

+761
-118
lines changed

16 files changed

+761
-118
lines changed

‎Include/cpython/ceval.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ PyAPI_FUNC(PyObject *) _PyEval_EvalFrameDefault(PyThreadState *tstate, struct _P
2222
PyAPI_FUNC(void) _PyEval_SetSwitchInterval(unsigned longmicroseconds);
2323
PyAPI_FUNC(unsigned long) _PyEval_GetSwitchInterval(void);
2424

25+
PyAPI_FUNC(int) _PyEval_MakePendingCalls(PyThreadState*);
26+
2527
PyAPI_FUNC(Py_ssize_t) PyUnstable_Eval_RequestCodeExtraIndex(freefunc);
2628
// Old name -- remove when this API changes:
2729
_Py_DEPRECATED_EXTERNALLY(3.12) staticinlinePy_ssize_t

‎Include/internal/pycore_ceval.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ PyAPI_FUNC(void) _PyEval_SignalReceived(PyInterpreterState *interp);
2727
PyAPI_FUNC(int) _PyEval_AddPendingCall(
2828
PyInterpreterState*interp,
2929
int (*func)(void*),
30-
void*arg);
30+
void*arg,
31+
intmainthreadonly);
3132
PyAPI_FUNC(void) _PyEval_SignalAsyncExc(PyInterpreterState*interp);
3233
#ifdefHAVE_FORK
3334
externPyStatus_PyEval_ReInitThreads(PyThreadState*tstate);

‎Include/internal/pycore_ceval_state.h‎

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ extern "C"{
1313
#include"pycore_gil.h"// struct _gil_runtime_state
1414

1515

16+
struct_pending_calls{
17+
intbusy;
18+
PyThread_type_locklock;
19+
/* Request for running pending calls. */
20+
_Py_atomic_intcalls_to_do;
21+
/* Request for looking at the `async_exc` field of the current
22+
thread state.
23+
Guarded by the GIL. */
24+
intasync_exc;
25+
#defineNPENDINGCALLS 32
26+
struct_pending_call{
27+
int (*func)(void*);
28+
void*arg;
29+
} calls[NPENDINGCALLS];
30+
intfirst;
31+
intlast;
32+
};
33+
1634
typedefenum{
1735
PERF_STATUS_FAILED=-1, // Perf trampoline is in an invalid state
1836
PERF_STATUS_NO_INIT=0, // Perf trampoline is not initialized
@@ -49,6 +67,8 @@ struct _ceval_runtime_state{
4967
the main thread of the main interpreter can handle signals: see
5068
_Py_ThreadCanHandleSignals(). */
5169
_Py_atomic_intsignals_pending;
70+
/* Pending calls to be made only on the main thread. */
71+
struct_pending_callspending_mainthread;
5272
};
5373

5474
#ifdefPY_HAVE_PERF_TRAMPOLINE
@@ -62,24 +82,6 @@ struct _ceval_runtime_state{
6282
#endif
6383

6484

65-
struct_pending_calls{
66-
intbusy;
67-
PyThread_type_locklock;
68-
/* Request for running pending calls. */
69-
_Py_atomic_intcalls_to_do;
70-
/* Request for looking at the `async_exc` field of the current
71-
thread state.
72-
Guarded by the GIL. */
73-
intasync_exc;
74-
#defineNPENDINGCALLS 32
75-
struct{
76-
int (*func)(void*);
77-
void*arg;
78-
} calls[NPENDINGCALLS];
79-
intfirst;
80-
intlast;
81-
};
82-
8385
struct_ceval_state{
8486
/* This single variable consolidates all requests to break out of
8587
the fast path in the eval loop. */

‎Include/internal/pycore_pystate.h‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,6 @@ _Py_ThreadCanHandleSignals(PyInterpreterState *interp)
6060
}
6161

6262

63-
/* Only execute pending calls on the main thread. */
64-
staticinlineint
65-
_Py_ThreadCanHandlePendingCalls(void)
66-
{
67-
return_Py_IsMainThread();
68-
}
69-
70-
7163
/* Variable and static inline functions for in-line access to current thread
7264
and interpreter state */
7365

‎Lib/test/support/threading_helper.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,11 @@ def join_thread(thread, timeout=None):
115115

116116
@contextlib.contextmanager
117117
defstart_threads(threads, unlock=None):
118-
importfaulthandler
118+
try:
119+
importfaulthandler
120+
exceptImportError:
121+
# It isn't supported on subinterpreters yet.
122+
faulthandler=None
119123
threads=list(threads)
120124
started= []
121125
try:
@@ -147,7 +151,8 @@ def start_threads(threads, unlock=None):
147151
finally:
148152
started= [tfortinstartedift.is_alive()]
149153
ifstarted:
150-
faulthandler.dump_traceback(sys.stdout)
154+
iffaulthandlerisnotNone:
155+
faulthandler.dump_traceback(sys.stdout)
151156
raiseAssertionError('Unable to join %d threads'%len(started))
152157

153158

0 commit comments

Comments
(0)