bpo-33608: Deal with pending calls relative to runtime shutdown. (gh-12246)
This commit is contained in:
parent
7c4fcb6b05
commit
842a2f07f2
|
@ -11,7 +11,10 @@ extern "C" {
|
||||||
#include "pycore_atomic.h"
|
#include "pycore_atomic.h"
|
||||||
#include "pythread.h"
|
#include "pythread.h"
|
||||||
|
|
||||||
|
PyAPI_FUNC(void) _Py_FinishPendingCalls(void);
|
||||||
|
|
||||||
struct _pending_calls {
|
struct _pending_calls {
|
||||||
|
int finishing;
|
||||||
PyThread_type_lock lock;
|
PyThread_type_lock lock;
|
||||||
/* Request for running pending calls. */
|
/* Request for running pending calls. */
|
||||||
_Py_atomic_int calls_to_do;
|
_Py_atomic_int calls_to_do;
|
||||||
|
|
|
@ -330,31 +330,33 @@ _PyEval_SignalReceived(void)
|
||||||
|
|
||||||
/* Push one item onto the queue while holding the lock. */
|
/* Push one item onto the queue while holding the lock. */
|
||||||
static int
|
static int
|
||||||
_push_pending_call(int (*func)(void *), void *arg)
|
_push_pending_call(struct _pending_calls *pending,
|
||||||
|
int (*func)(void *), void *arg)
|
||||||
{
|
{
|
||||||
int i = _PyRuntime.ceval.pending.last;
|
int i = pending->last;
|
||||||
int j = (i + 1) % NPENDINGCALLS;
|
int j = (i + 1) % NPENDINGCALLS;
|
||||||
if (j == _PyRuntime.ceval.pending.first) {
|
if (j == pending->first) {
|
||||||
return -1; /* Queue full */
|
return -1; /* Queue full */
|
||||||
}
|
}
|
||||||
_PyRuntime.ceval.pending.calls[i].func = func;
|
pending->calls[i].func = func;
|
||||||
_PyRuntime.ceval.pending.calls[i].arg = arg;
|
pending->calls[i].arg = arg;
|
||||||
_PyRuntime.ceval.pending.last = j;
|
pending->last = j;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pop one item off the queue while holding the lock. */
|
/* Pop one item off the queue while holding the lock. */
|
||||||
static void
|
static void
|
||||||
_pop_pending_call(int (**func)(void *), void **arg)
|
_pop_pending_call(struct _pending_calls *pending,
|
||||||
|
int (**func)(void *), void **arg)
|
||||||
{
|
{
|
||||||
int i = _PyRuntime.ceval.pending.first;
|
int i = pending->first;
|
||||||
if (i == _PyRuntime.ceval.pending.last) {
|
if (i == pending->last) {
|
||||||
return; /* Queue empty */
|
return; /* Queue empty */
|
||||||
}
|
}
|
||||||
|
|
||||||
*func = _PyRuntime.ceval.pending.calls[i].func;
|
*func = pending->calls[i].func;
|
||||||
*arg = _PyRuntime.ceval.pending.calls[i].arg;
|
*arg = pending->calls[i].arg;
|
||||||
_PyRuntime.ceval.pending.first = (i + 1) % NPENDINGCALLS;
|
pending->first = (i + 1) % NPENDINGCALLS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This implementation is thread-safe. It allows
|
/* This implementation is thread-safe. It allows
|
||||||
|
@ -365,9 +367,23 @@ _pop_pending_call(int (**func)(void *), void **arg)
|
||||||
int
|
int
|
||||||
Py_AddPendingCall(int (*func)(void *), void *arg)
|
Py_AddPendingCall(int (*func)(void *), void *arg)
|
||||||
{
|
{
|
||||||
PyThread_acquire_lock(_PyRuntime.ceval.pending.lock, WAIT_LOCK);
|
struct _pending_calls *pending = &_PyRuntime.ceval.pending;
|
||||||
int result = _push_pending_call(func, arg);
|
|
||||||
PyThread_release_lock(_PyRuntime.ceval.pending.lock);
|
PyThread_acquire_lock(pending->lock, WAIT_LOCK);
|
||||||
|
if (pending->finishing) {
|
||||||
|
PyThread_release_lock(pending->lock);
|
||||||
|
|
||||||
|
PyObject *exc, *val, *tb;
|
||||||
|
PyErr_Fetch(&exc, &val, &tb);
|
||||||
|
PyErr_SetString(PyExc_SystemError,
|
||||||
|
"Py_AddPendingCall: cannot add pending calls "
|
||||||
|
"(Python shutting down)");
|
||||||
|
PyErr_Print();
|
||||||
|
PyErr_Restore(exc, val, tb);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int result = _push_pending_call(pending, func, arg);
|
||||||
|
PyThread_release_lock(pending->lock);
|
||||||
|
|
||||||
/* signal main loop */
|
/* signal main loop */
|
||||||
SIGNAL_PENDING_CALLS();
|
SIGNAL_PENDING_CALLS();
|
||||||
|
@ -400,7 +416,7 @@ handle_signals(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
make_pending_calls(void)
|
make_pending_calls(struct _pending_calls* pending)
|
||||||
{
|
{
|
||||||
static int busy = 0;
|
static int busy = 0;
|
||||||
|
|
||||||
|
@ -425,9 +441,9 @@ make_pending_calls(void)
|
||||||
void *arg = NULL;
|
void *arg = NULL;
|
||||||
|
|
||||||
/* pop one item off the queue while holding the lock */
|
/* pop one item off the queue while holding the lock */
|
||||||
PyThread_acquire_lock(_PyRuntime.ceval.pending.lock, WAIT_LOCK);
|
PyThread_acquire_lock(pending->lock, WAIT_LOCK);
|
||||||
_pop_pending_call(&func, &arg);
|
_pop_pending_call(pending, &func, &arg);
|
||||||
PyThread_release_lock(_PyRuntime.ceval.pending.lock);
|
PyThread_release_lock(pending->lock);
|
||||||
|
|
||||||
/* having released the lock, perform the callback */
|
/* having released the lock, perform the callback */
|
||||||
if (func == NULL) {
|
if (func == NULL) {
|
||||||
|
@ -448,6 +464,30 @@ error:
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_Py_FinishPendingCalls(void)
|
||||||
|
{
|
||||||
|
struct _pending_calls *pending = &_PyRuntime.ceval.pending;
|
||||||
|
|
||||||
|
assert(PyGILState_Check());
|
||||||
|
|
||||||
|
PyThread_acquire_lock(pending->lock, WAIT_LOCK);
|
||||||
|
pending->finishing = 1;
|
||||||
|
PyThread_release_lock(pending->lock);
|
||||||
|
|
||||||
|
if (!_Py_atomic_load_relaxed(&(pending->calls_to_do))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (make_pending_calls(pending) < 0) {
|
||||||
|
PyObject *exc, *val, *tb;
|
||||||
|
PyErr_Fetch(&exc, &val, &tb);
|
||||||
|
PyErr_BadInternalCall();
|
||||||
|
_PyErr_ChainExceptions(exc, val, tb);
|
||||||
|
PyErr_Print();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Py_MakePendingCalls() is a simple wrapper for the sake
|
/* Py_MakePendingCalls() is a simple wrapper for the sake
|
||||||
of backward-compatibility. */
|
of backward-compatibility. */
|
||||||
int
|
int
|
||||||
|
@ -462,7 +502,7 @@ Py_MakePendingCalls(void)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
res = make_pending_calls();
|
res = make_pending_calls(&_PyRuntime.ceval.pending);
|
||||||
if (res != 0) {
|
if (res != 0) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -1012,7 +1052,7 @@ main_loop:
|
||||||
if (_Py_atomic_load_relaxed(
|
if (_Py_atomic_load_relaxed(
|
||||||
&_PyRuntime.ceval.pending.calls_to_do))
|
&_PyRuntime.ceval.pending.calls_to_do))
|
||||||
{
|
{
|
||||||
if (make_pending_calls() != 0) {
|
if (make_pending_calls(&_PyRuntime.ceval.pending) != 0) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1049,17 +1049,21 @@ Py_FinalizeEx(void)
|
||||||
if (!_PyRuntime.initialized)
|
if (!_PyRuntime.initialized)
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
|
// Wrap up existing "threading"-module-created, non-daemon threads.
|
||||||
wait_for_thread_shutdown();
|
wait_for_thread_shutdown();
|
||||||
|
|
||||||
/* Get current thread state and interpreter pointer */
|
/* Get current thread state and interpreter pointer */
|
||||||
tstate = _PyThreadState_GET();
|
tstate = _PyThreadState_GET();
|
||||||
interp = tstate->interp;
|
interp = tstate->interp;
|
||||||
|
|
||||||
|
// Make any remaining pending calls.
|
||||||
|
_Py_FinishPendingCalls();
|
||||||
|
|
||||||
/* The interpreter is still entirely intact at this point, and the
|
/* The interpreter is still entirely intact at this point, and the
|
||||||
* exit funcs may be relying on that. In particular, if some thread
|
* exit funcs may be relying on that. In particular, if some thread
|
||||||
* or exit func is still waiting to do an import, the import machinery
|
* or exit func is still waiting to do an import, the import machinery
|
||||||
* expects Py_IsInitialized() to return true. So don't say the
|
* expects Py_IsInitialized() to return true. So don't say the
|
||||||
* interpreter is uninitialized until after the exit funcs have run.
|
* runtime is uninitialized until after the exit funcs have run.
|
||||||
* Note that Threading.py uses an exit func to do a join on all the
|
* Note that Threading.py uses an exit func to do a join on all the
|
||||||
* threads created thru it, so this also protects pending imports in
|
* threads created thru it, so this also protects pending imports in
|
||||||
* the threads created via Threading.
|
* the threads created via Threading.
|
||||||
|
@ -1462,6 +1466,7 @@ Py_EndInterpreter(PyThreadState *tstate)
|
||||||
Py_FatalError("Py_EndInterpreter: thread still has a frame");
|
Py_FatalError("Py_EndInterpreter: thread still has a frame");
|
||||||
interp->finalizing = 1;
|
interp->finalizing = 1;
|
||||||
|
|
||||||
|
// Wrap up existing "threading"-module-created, non-daemon threads.
|
||||||
wait_for_thread_shutdown();
|
wait_for_thread_shutdown();
|
||||||
|
|
||||||
call_py_exitfuncs(interp);
|
call_py_exitfuncs(interp);
|
||||||
|
|
Loading…
Reference in New Issue