summaryrefslogtreecommitdiffstats
path: root/Python
diff options
context:
space:
mode:
authorEric Snow <ericsnowcurrently@gmail.com>2024-04-26 01:05:51 (GMT)
committerGitHub <noreply@github.com>2024-04-26 01:05:51 (GMT)
commit09c29475813ff2a763931fc0b45aaaef57cd2ac7 (patch)
treedc006bd73c8969f5a228a3b778c38349b3cc8bbb /Python
parentd5df25268b037c2f1f4a1184fad5274d0e72f8d6 (diff)
downloadcpython-09c29475813ff2a763931fc0b45aaaef57cd2ac7.zip
cpython-09c29475813ff2a763931fc0b45aaaef57cd2ac7.tar.gz
cpython-09c29475813ff2a763931fc0b45aaaef57cd2ac7.tar.bz2
gh-110693: Pending Calls Machinery Cleanups (gh-118296)
This does some cleanup in preparation for later changes.
Diffstat (limited to 'Python')
-rw-r--r--Python/ceval_gil.c159
1 files changed, 104 insertions, 55 deletions
diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c
index c0819d8..c3c2c54 100644
--- a/Python/ceval_gil.c
+++ b/Python/ceval_gil.c
@@ -84,15 +84,15 @@ update_eval_breaker_for_thread(PyInterpreterState *interp, PyThreadState *tstate
return;
#endif
- int32_t calls_to_do = _Py_atomic_load_int32_relaxed(
- &interp->ceval.pending.calls_to_do);
- if (calls_to_do) {
+ int32_t npending = _Py_atomic_load_int32_relaxed(
+ &interp->ceval.pending.npending);
+ if (npending) {
_Py_set_eval_breaker_bit(tstate, _PY_CALLS_TO_DO_BIT);
}
else if (_Py_IsMainThread()) {
- calls_to_do = _Py_atomic_load_int32_relaxed(
- &_PyRuntime.ceval.pending_mainthread.calls_to_do);
- if (calls_to_do) {
+ npending = _Py_atomic_load_int32_relaxed(
+ &_PyRuntime.ceval.pending_mainthread.npending);
+ if (npending) {
_Py_set_eval_breaker_bit(tstate, _PY_CALLS_TO_DO_BIT);
}
}
@@ -624,6 +624,34 @@ PyEval_RestoreThread(PyThreadState *tstate)
}
+void
+_PyEval_SignalReceived(void)
+{
+ _Py_set_eval_breaker_bit(_PyRuntime.main_tstate, _PY_SIGNALS_PENDING_BIT);
+}
+
+
+#ifndef Py_GIL_DISABLED
+static void
+signal_active_thread(PyInterpreterState *interp, uintptr_t bit)
+{
+ struct _gil_runtime_state *gil = interp->ceval.gil;
+
+ // If a thread from the targeted interpreter is holding the GIL, signal
+ // that thread. Otherwise, the next thread to run from the targeted
+ // interpreter will have its bit set as part of taking the GIL.
+ MUTEX_LOCK(gil->mutex);
+ if (_Py_atomic_load_int_relaxed(&gil->locked)) {
+ PyThreadState *holder = (PyThreadState*)_Py_atomic_load_ptr_relaxed(&gil->last_holder);
+ if (holder->interp == interp) {
+ _Py_set_eval_breaker_bit(holder, bit);
+ }
+ }
+ MUTEX_UNLOCK(gil->mutex);
+}
+#endif
+
+
/* Mechanism whereby asynchronously executing callbacks (e.g. UNIX
signal handlers or Mac I/O completion routines) can schedule calls
to a function to be called synchronously.
@@ -646,29 +674,31 @@ PyEval_RestoreThread(PyThreadState *tstate)
threadstate.
*/
-void
-_PyEval_SignalReceived(void)
-{
- _Py_set_eval_breaker_bit(_PyRuntime.main_tstate, _PY_SIGNALS_PENDING_BIT);
-}
-
/* Push one item onto the queue while holding the lock. */
static int
_push_pending_call(struct _pending_calls *pending,
_Py_pending_call_func func, void *arg, int flags)
{
- int i = pending->last;
- int j = (i + 1) % NPENDINGCALLS;
- if (j == pending->first) {
- return -1; /* Queue full */
+ if (pending->npending == pending->max) {
+ return _Py_ADD_PENDING_FULL;
}
+ assert(pending->npending < pending->max);
+
+ int i = pending->next;
+ assert(pending->calls[i].func == NULL);
+
pending->calls[i].func = func;
pending->calls[i].arg = arg;
pending->calls[i].flags = flags;
- pending->last = j;
- assert(pending->calls_to_do < NPENDINGCALLS);
- _Py_atomic_add_int32(&pending->calls_to_do, 1);
- return 0;
+
+ assert(pending->npending < PENDINGCALLSARRAYSIZE);
+ _Py_atomic_add_int32(&pending->npending, 1);
+
+ pending->next = (i + 1) % PENDINGCALLSARRAYSIZE;
+ assert(pending->next != pending->first
+ || pending->npending == pending->max);
+
+ return _Py_ADD_PENDING_SUCCESS;
}
static int
@@ -676,8 +706,9 @@ _next_pending_call(struct _pending_calls *pending,
int (**func)(void *), void **arg, int *flags)
{
int i = pending->first;
- if (i == pending->last) {
+ if (pending->npending == 0) {
/* Queue empty */
+ assert(i == pending->next);
assert(pending->calls[i].func == NULL);
return -1;
}
@@ -695,38 +726,18 @@ _pop_pending_call(struct _pending_calls *pending,
int i = _next_pending_call(pending, func, arg, flags);
if (i >= 0) {
pending->calls[i] = (struct _pending_call){0};
- pending->first = (i + 1) % NPENDINGCALLS;
- assert(pending->calls_to_do > 0);
- _Py_atomic_add_int32(&pending->calls_to_do, -1);
+ pending->first = (i + 1) % PENDINGCALLSARRAYSIZE;
+ assert(pending->npending > 0);
+ _Py_atomic_add_int32(&pending->npending, -1);
}
}
-#ifndef Py_GIL_DISABLED
-static void
-signal_active_thread(PyInterpreterState *interp, uintptr_t bit)
-{
- struct _gil_runtime_state *gil = interp->ceval.gil;
-
- // If a thread from the targeted interpreter is holding the GIL, signal
- // that thread. Otherwise, the next thread to run from the targeted
- // interpreter will have its bit set as part of taking the GIL.
- MUTEX_LOCK(gil->mutex);
- if (_Py_atomic_load_int_relaxed(&gil->locked)) {
- PyThreadState *holder = (PyThreadState*)_Py_atomic_load_ptr_relaxed(&gil->last_holder);
- if (holder->interp == interp) {
- _Py_set_eval_breaker_bit(holder, bit);
- }
- }
- MUTEX_UNLOCK(gil->mutex);
-}
-#endif
-
/* This implementation is thread-safe. It allows
scheduling to be made from any thread, and even from an executing
callback.
*/
-int
+_Py_add_pending_call_result
_PyEval_AddPendingCall(PyInterpreterState *interp,
_Py_pending_call_func func, void *arg, int flags)
{
@@ -739,7 +750,8 @@ _PyEval_AddPendingCall(PyInterpreterState *interp,
}
PyMutex_Lock(&pending->mutex);
- int result = _push_pending_call(pending, func, arg, flags);
+ _Py_add_pending_call_result result =
+ _push_pending_call(pending, func, arg, flags);
PyMutex_Unlock(&pending->mutex);
if (main_only) {
@@ -762,7 +774,15 @@ Py_AddPendingCall(_Py_pending_call_func func, void *arg)
/* Legacy users of this API will continue to target the main thread
(of the main interpreter). */
PyInterpreterState *interp = _PyInterpreterState_Main();
- return _PyEval_AddPendingCall(interp, func, arg, _Py_PENDING_MAINTHREADONLY);
+ _Py_add_pending_call_result r =
+ _PyEval_AddPendingCall(interp, func, arg, _Py_PENDING_MAINTHREADONLY);
+ if (r == _Py_ADD_PENDING_FULL) {
+ return -1;
+ }
+ else {
+ assert(r == _Py_ADD_PENDING_SUCCESS);
+ return 0;
+ }
}
static int
@@ -782,10 +802,21 @@ handle_signals(PyThreadState *tstate)
}
static int
-_make_pending_calls(struct _pending_calls *pending)
+_make_pending_calls(struct _pending_calls *pending, int32_t *p_npending)
{
+ int res = 0;
+ int32_t npending = -1;
+
+ assert(sizeof(pending->max) <= sizeof(size_t)
+ && ((size_t)pending->max) <= Py_ARRAY_LENGTH(pending->calls));
+ int32_t maxloop = pending->maxloop;
+ if (maxloop == 0) {
+ maxloop = pending->max;
+ }
+ assert(maxloop > 0 && maxloop <= pending->max);
+
/* perform a bounded number of calls, in case of recursion */
- for (int i=0; i<NPENDINGCALLS; i++) {
+ for (int i=0; i<maxloop; i++) {
_Py_pending_call_func func = NULL;
void *arg = NULL;
int flags = 0;
@@ -793,21 +824,29 @@ _make_pending_calls(struct _pending_calls *pending)
/* pop one item off the queue while holding the lock */
PyMutex_Lock(&pending->mutex);
_pop_pending_call(pending, &func, &arg, &flags);
+ npending = pending->npending;
PyMutex_Unlock(&pending->mutex);
- /* having released the lock, perform the callback */
+ /* Check if there are any more pending calls. */
if (func == NULL) {
+ assert(npending == 0);
break;
}
- int res = func(arg);
+
+ /* having released the lock, perform the callback */
+ res = func(arg);
if ((flags & _Py_PENDING_RAWFREE) && arg != NULL) {
PyMem_RawFree(arg);
}
if (res != 0) {
- return -1;
+ res = -1;
+ goto finally;
}
}
- return 0;
+
+finally:
+ *p_npending = npending;
+ return res;
}
static void
@@ -861,26 +900,36 @@ make_pending_calls(PyThreadState *tstate)
added in-between re-signals */
unsignal_pending_calls(tstate, interp);
- if (_make_pending_calls(pending) != 0) {
+ int32_t npending;
+ if (_make_pending_calls(pending, &npending) != 0) {
pending->busy = 0;
/* There might not be more calls to make, but we play it safe. */
signal_pending_calls(tstate, interp);
return -1;
}
+ if (npending > 0) {
+ /* We hit pending->maxloop. */
+ signal_pending_calls(tstate, interp);
+ }
if (_Py_IsMainThread() && _Py_IsMainInterpreter(interp)) {
- if (_make_pending_calls(pending_main) != 0) {
+ if (_make_pending_calls(pending_main, &npending) != 0) {
pending->busy = 0;
/* There might not be more calls to make, but we play it safe. */
signal_pending_calls(tstate, interp);
return -1;
}
+ if (npending > 0) {
+ /* We hit pending_main->maxloop. */
+ signal_pending_calls(tstate, interp);
+ }
}
pending->busy = 0;
return 0;
}
+
void
_Py_set_eval_breaker_bit_all(PyInterpreterState *interp, uintptr_t bit)
{