Skip to content

Commit 842a2f0

Browse files
bpo-33608: Deal with pending calls relative to runtime shutdown. (gh-12246)
1 parent 7c4fcb6 commit 842a2f0

File tree

3 files changed

+70
-22
lines changed

3 files changed

+70
-22
lines changed

Include/internal/pycore_ceval.h

+3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ extern "C" {
1111
#include "pycore_atomic.h"
1212
#include "pythread.h"
1313

14+
PyAPI_FUNC(void) _Py_FinishPendingCalls(void);
15+
1416
struct _pending_calls {
17+
int finishing;
1518
PyThread_type_lock lock;
1619
/* Request for running pending calls. */
1720
_Py_atomic_int calls_to_do;

Python/ceval.c

+61-21
Original file line numberDiff line numberDiff line change
@@ -330,31 +330,33 @@ _PyEval_SignalReceived(void)
330330

331331
/* Push one item onto the queue while holding the lock. */
332332
static int
333-
_push_pending_call(int (*func)(void *), void *arg)
333+
_push_pending_call(struct _pending_calls *pending,
334+
int (*func)(void *), void *arg)
334335
{
335-
int i = _PyRuntime.ceval.pending.last;
336+
int i = pending->last;
336337
int j = (i + 1) % NPENDINGCALLS;
337-
if (j == _PyRuntime.ceval.pending.first) {
338+
if (j == pending->first) {
338339
return -1; /* Queue full */
339340
}
340-
_PyRuntime.ceval.pending.calls[i].func = func;
341-
_PyRuntime.ceval.pending.calls[i].arg = arg;
342-
_PyRuntime.ceval.pending.last = j;
341+
pending->calls[i].func = func;
342+
pending->calls[i].arg = arg;
343+
pending->last = j;
343344
return 0;
344345
}
345346

346347
/* Pop one item off the queue while holding the lock. */
347348
static void
348-
_pop_pending_call(int (**func)(void *), void **arg)
349+
_pop_pending_call(struct _pending_calls *pending,
350+
int (**func)(void *), void **arg)
349351
{
350-
int i = _PyRuntime.ceval.pending.first;
351-
if (i == _PyRuntime.ceval.pending.last) {
352+
int i = pending->first;
353+
if (i == pending->last) {
352354
return; /* Queue empty */
353355
}
354356

355-
*func = _PyRuntime.ceval.pending.calls[i].func;
356-
*arg = _PyRuntime.ceval.pending.calls[i].arg;
357-
_PyRuntime.ceval.pending.first = (i + 1) % NPENDINGCALLS;
357+
*func = pending->calls[i].func;
358+
*arg = pending->calls[i].arg;
359+
pending->first = (i + 1) % NPENDINGCALLS;
358360
}
359361

360362
/* This implementation is thread-safe. It allows
@@ -365,9 +367,23 @@ _pop_pending_call(int (**func)(void *), void **arg)
365367
int
366368
Py_AddPendingCall(int (*func)(void *), void *arg)
367369
{
368-
PyThread_acquire_lock(_PyRuntime.ceval.pending.lock, WAIT_LOCK);
369-
int result = _push_pending_call(func, arg);
370-
PyThread_release_lock(_PyRuntime.ceval.pending.lock);
370+
struct _pending_calls *pending = &_PyRuntime.ceval.pending;
371+
372+
PyThread_acquire_lock(pending->lock, WAIT_LOCK);
373+
if (pending->finishing) {
374+
PyThread_release_lock(pending->lock);
375+
376+
PyObject *exc, *val, *tb;
377+
PyErr_Fetch(&exc, &val, &tb);
378+
PyErr_SetString(PyExc_SystemError,
379+
"Py_AddPendingCall: cannot add pending calls "
380+
"(Python shutting down)");
381+
PyErr_Print();
382+
PyErr_Restore(exc, val, tb);
383+
return -1;
384+
}
385+
int result = _push_pending_call(pending, func, arg);
386+
PyThread_release_lock(pending->lock);
371387

372388
/* signal main loop */
373389
SIGNAL_PENDING_CALLS();
@@ -400,7 +416,7 @@ handle_signals(void)
400416
}
401417

402418
static int
403-
make_pending_calls(void)
419+
make_pending_calls(struct _pending_calls* pending)
404420
{
405421
static int busy = 0;
406422

@@ -425,9 +441,9 @@ make_pending_calls(void)
425441
void *arg = NULL;
426442

427443
/* pop one item off the queue while holding the lock */
428-
PyThread_acquire_lock(_PyRuntime.ceval.pending.lock, WAIT_LOCK);
429-
_pop_pending_call(&func, &arg);
430-
PyThread_release_lock(_PyRuntime.ceval.pending.lock);
444+
PyThread_acquire_lock(pending->lock, WAIT_LOCK);
445+
_pop_pending_call(pending, &func, &arg);
446+
PyThread_release_lock(pending->lock);
431447

432448
/* having released the lock, perform the callback */
433449
if (func == NULL) {
@@ -448,6 +464,30 @@ make_pending_calls(void)
448464
return res;
449465
}
450466

467+
void
468+
_Py_FinishPendingCalls(void)
469+
{
470+
struct _pending_calls *pending = &_PyRuntime.ceval.pending;
471+
472+
assert(PyGILState_Check());
473+
474+
PyThread_acquire_lock(pending->lock, WAIT_LOCK);
475+
pending->finishing = 1;
476+
PyThread_release_lock(pending->lock);
477+
478+
if (!_Py_atomic_load_relaxed(&(pending->calls_to_do))) {
479+
return;
480+
}
481+
482+
if (make_pending_calls(pending) < 0) {
483+
PyObject *exc, *val, *tb;
484+
PyErr_Fetch(&exc, &val, &tb);
485+
PyErr_BadInternalCall();
486+
_PyErr_ChainExceptions(exc, val, tb);
487+
PyErr_Print();
488+
}
489+
}
490+
451491
/* Py_MakePendingCalls() is a simple wrapper for the sake
452492
of backward-compatibility. */
453493
int
@@ -462,7 +502,7 @@ Py_MakePendingCalls(void)
462502
return res;
463503
}
464504

465-
res = make_pending_calls();
505+
res = make_pending_calls(&_PyRuntime.ceval.pending);
466506
if (res != 0) {
467507
return res;
468508
}
@@ -1012,7 +1052,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
10121052
if (_Py_atomic_load_relaxed(
10131053
&_PyRuntime.ceval.pending.calls_to_do))
10141054
{
1015-
if (make_pending_calls() != 0) {
1055+
if (make_pending_calls(&_PyRuntime.ceval.pending) != 0) {
10161056
goto error;
10171057
}
10181058
}

Python/pylifecycle.c

+6-1
Original file line numberDiff line numberDiff line change
@@ -1049,17 +1049,21 @@ Py_FinalizeEx(void)
10491049
if (!_PyRuntime.initialized)
10501050
return status;
10511051

1052+
// Wrap up existing "threading"-module-created, non-daemon threads.
10521053
wait_for_thread_shutdown();
10531054

10541055
/* Get current thread state and interpreter pointer */
10551056
tstate = _PyThreadState_GET();
10561057
interp = tstate->interp;
10571058

1059+
// Make any remaining pending calls.
1060+
_Py_FinishPendingCalls();
1061+
10581062
/* The interpreter is still entirely intact at this point, and the
10591063
* exit funcs may be relying on that. In particular, if some thread
10601064
* or exit func is still waiting to do an import, the import machinery
10611065
* expects Py_IsInitialized() to return true. So don't say the
1062-
* interpreter is uninitialized until after the exit funcs have run.
1066+
* runtime is uninitialized until after the exit funcs have run.
10631067
* Note that Threading.py uses an exit func to do a join on all the
10641068
* threads created thru it, so this also protects pending imports in
10651069
* the threads created via Threading.
@@ -1462,6 +1466,7 @@ Py_EndInterpreter(PyThreadState *tstate)
14621466
Py_FatalError("Py_EndInterpreter: thread still has a frame");
14631467
interp->finalizing = 1;
14641468

1469+
// Wrap up existing "threading"-module-created, non-daemon threads.
14651470
wait_for_thread_shutdown();
14661471

14671472
call_py_exitfuncs(interp);

0 commit comments

Comments
 (0)