Skip to content

Commit 354e6b5

Browse files
committed
Execute early finalization handlers in a loop
1 parent 642e5df commit 354e6b5

File tree

7 files changed

+86
-37
lines changed

7 files changed

+86
-37
lines changed

Include/internal/pycore_ceval.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ PyAPI_FUNC(int) _PyEval_MakePendingCalls(PyThreadState *);
4343
# define Py_DEFAULT_RECURSION_LIMIT 1000
4444
#endif
4545

46-
extern void _Py_FinishPendingCalls(PyThreadState *tstate);
46+
extern int _Py_FinishPendingCalls(PyThreadState *tstate);
4747
extern void _PyEval_InitState(PyInterpreterState *);
4848
extern void _PyEval_SignalReceived(void);
4949

Include/internal/pycore_pylifecycle.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ extern void _PyErr_DisplayException(PyObject *file, PyObject *exc);
8585

8686
extern void _PyThreadState_DeleteCurrent(PyThreadState *tstate);
8787

88-
extern void _PyAtExit_Call(PyInterpreterState *interp);
88+
extern int _PyAtExit_Call(PyInterpreterState *interp);
8989

9090
extern int _Py_IsCoreInitialized(void);
9191

Lib/threading.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,7 +1558,7 @@ def _shutdown():
15581558
# mark it done here.
15591559
if _main_thread._os_thread_handle.is_done() and _is_main_interpreter():
15601560
# _shutdown() was already called
1561-
return
1561+
return False
15621562

15631563
global _SHUTTING_DOWN
15641564
_SHUTTING_DOWN = True
@@ -1572,7 +1572,7 @@ def _shutdown():
15721572
_main_thread._os_thread_handle._set_done()
15731573

15741574
# Wait for all non-daemon threads to exit.
1575-
_thread_shutdown()
1575+
return _thread_shutdown()
15761576

15771577

15781578
def main_thread():

Modules/_threadmodule.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2345,6 +2345,7 @@ thread_shutdown(PyObject *self, PyObject *args)
23452345
{
23462346
PyThread_ident_t ident = PyThread_get_thread_ident_ex();
23472347
thread_module_state *state = get_thread_state(self);
2348+
int found_thread = 0;
23482349

23492350
for (;;) {
23502351
ThreadHandle *handle = NULL;
@@ -2353,6 +2354,7 @@ thread_shutdown(PyObject *self, PyObject *args)
23532354
HEAD_LOCK(&_PyRuntime);
23542355
struct llist_node *node;
23552356
llist_for_each_safe(node, &state->shutdown_handles) {
2357+
found_thread = 1;
23562358
ThreadHandle *cur = llist_data(node, ThreadHandle, shutdown_node);
23572359
if (cur->ident != ident) {
23582360
ThreadHandle_incref(cur);
@@ -2373,13 +2375,13 @@ thread_shutdown(PyObject *self, PyObject *args)
23732375
PyErr_FormatUnraisable("Exception ignored while joining a thread "
23742376
"in _thread._shutdown()");
23752377
ThreadHandle_decref(handle);
2376-
Py_RETURN_NONE;
2378+
return PyBool_FromLong(found_thread);
23772379
}
23782380

23792381
ThreadHandle_decref(handle);
23802382
}
23812383

2382-
Py_RETURN_NONE;
2384+
return PyBool_FromLong(found_thread);
23832385
}
23842386

23852387
PyDoc_STRVAR(shutdown_doc,

Modules/atexitmodule.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ _PyAtExit_Fini(PyInterpreterState *interp)
9999
}
100100
}
101101

102-
static void
102+
static int
103103
atexit_callfuncs(struct atexit_state *state)
104104
{
105105
assert(!PyErr_Occurred());
@@ -112,10 +112,13 @@ atexit_callfuncs(struct atexit_state *state)
112112
{
113113
PyErr_FormatUnraisable("Exception ignored while "
114114
"copying atexit callbacks");
115-
return;
115+
return 0;
116116
}
117117

118+
int called = 0;
119+
118120
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(copy); ++i) {
121+
called = 1;
119122
// We don't have to worry about evil borrowed references, because
120123
// no other threads can access this list.
121124
PyObject *tuple = PyList_GET_ITEM(copy, i);
@@ -141,14 +144,15 @@ atexit_callfuncs(struct atexit_state *state)
141144
atexit_cleanup(state);
142145

143146
assert(!PyErr_Occurred());
147+
return called;
144148
}
145149

146150

147-
void
151+
int
148152
_PyAtExit_Call(PyInterpreterState *interp)
149153
{
150154
struct atexit_state *state = &interp->atexit;
151-
atexit_callfuncs(state);
155+
return atexit_callfuncs(state);
152156
}
153157

154158

Python/ceval_gil.c

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -837,10 +837,11 @@ handle_signals(PyThreadState *tstate)
837837
}
838838

839839
static int
840-
_make_pending_calls(struct _pending_calls *pending, int32_t *p_npending)
840+
_make_pending_calls(struct _pending_calls *pending, int32_t *p_npending, int *p_called)
841841
{
842842
int res = 0;
843843
int32_t npending = -1;
844+
int called = 0;
844845

845846
assert(sizeof(pending->max) <= sizeof(size_t)
846847
&& ((size_t)pending->max) <= Py_ARRAY_LENGTH(pending->calls));
@@ -868,6 +869,8 @@ _make_pending_calls(struct _pending_calls *pending, int32_t *p_npending)
868869
break;
869870
}
870871

872+
called = 1;
873+
871874
/* having released the lock, perform the callback */
872875
res = func(arg);
873876
if ((flags & _Py_PENDING_RAWFREE) && arg != NULL) {
@@ -881,6 +884,7 @@ _make_pending_calls(struct _pending_calls *pending, int32_t *p_npending)
881884

882885
finally:
883886
*p_npending = npending;
887+
*p_called = called;
884888
return res;
885889
}
886890

@@ -917,7 +921,7 @@ clear_pending_handling_thread(struct _pending_calls *pending)
917921
}
918922

919923
static int
920-
make_pending_calls(PyThreadState *tstate)
924+
make_pending_calls_with_count(PyThreadState *tstate)
921925
{
922926
PyInterpreterState *interp = tstate->interp;
923927
struct _pending_calls *pending = &interp->ceval.pending;
@@ -947,7 +951,8 @@ make_pending_calls(PyThreadState *tstate)
947951
unsignal_pending_calls(tstate, interp);
948952

949953
int32_t npending;
950-
if (_make_pending_calls(pending, &npending) != 0) {
954+
int called;
955+
if (_make_pending_calls(pending, &npending, &called) != 0) {
951956
clear_pending_handling_thread(pending);
952957
/* There might not be more calls to make, but we play it safe. */
953958
signal_pending_calls(tstate, interp);
@@ -958,8 +963,9 @@ make_pending_calls(PyThreadState *tstate)
958963
signal_pending_calls(tstate, interp);
959964
}
960965

966+
int main_called = 0;
961967
if (_Py_IsMainThread() && _Py_IsMainInterpreter(interp)) {
962-
if (_make_pending_calls(pending_main, &npending) != 0) {
968+
if (_make_pending_calls(pending_main, &npending, &main_called) != 0) {
963969
clear_pending_handling_thread(pending);
964970
/* There might not be more calls to make, but we play it safe. */
965971
signal_pending_calls(tstate, interp);
@@ -972,6 +978,16 @@ make_pending_calls(PyThreadState *tstate)
972978
}
973979

974980
clear_pending_handling_thread(pending);
981+
return Py_MAX(called, main_called);
982+
}
983+
984+
static int
985+
make_pending_calls(PyThreadState *tstate)
986+
{
987+
if (make_pending_calls_with_count(tstate) < 0) {
988+
return -1;
989+
}
990+
975991
return 0;
976992
}
977993

@@ -994,7 +1010,7 @@ _Py_unset_eval_breaker_bit_all(PyInterpreterState *interp, uintptr_t bit)
9941010
_Py_FOR_EACH_TSTATE_END(interp);
9951011
}
9961012

997-
void
1013+
int
9981014
_Py_FinishPendingCalls(PyThreadState *tstate)
9991015
{
10001016
_Py_AssertHoldsTstate();
@@ -1011,13 +1027,18 @@ _Py_FinishPendingCalls(PyThreadState *tstate)
10111027
#ifndef NDEBUG
10121028
int32_t npending_prev = INT32_MAX;
10131029
#endif
1030+
int called = 0;
10141031
do {
1015-
if (make_pending_calls(tstate) < 0) {
1032+
int res = make_pending_calls_with_count(tstate);
1033+
if (res < 0) {
10161034
PyObject *exc = _PyErr_GetRaisedException(tstate);
10171035
PyErr_BadInternalCall();
10181036
_PyErr_ChainExceptions1(exc);
10191037
_PyErr_Print(tstate);
10201038
}
1039+
if (res != 0) {
1040+
called = 1;
1041+
}
10211042

10221043
npending = _Py_atomic_load_int32_relaxed(&pending->npending);
10231044
if (pending_main != NULL) {
@@ -1028,6 +1049,8 @@ _Py_FinishPendingCalls(PyThreadState *tstate)
10281049
npending_prev = npending;
10291050
#endif
10301051
} while (npending > 0);
1052+
1053+
return called;
10311054
}
10321055

10331056
int

Python/pylifecycle.c

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ static PyStatus init_android_streams(PyThreadState *tstate);
9696
#if defined(__APPLE__) && HAS_APPLE_SYSTEM_LOG
9797
static PyStatus init_apple_streams(PyThreadState *tstate);
9898
#endif
99-
static void wait_for_thread_shutdown(PyThreadState *tstate);
99+
static int wait_for_thread_shutdown(PyThreadState *tstate);
100100
static void finalize_subinterpreters(void);
101101
static void call_ll_exitfuncs(_PyRuntimeState *runtime);
102102

@@ -2003,6 +2003,39 @@ resolve_final_tstate(_PyRuntimeState *runtime)
20032003
return main_tstate;
20042004
}
20052005

2006+
static void
2007+
make_pre_finalization_calls(PyThreadState *tstate)
2008+
{
2009+
/* Each of these functions can start one another, e.g. a pending call
2010+
* could start a thread or vice versa. To ensure that we properly clean
2011+
* call everything, we run these in a loop until none of them run anything. */
2012+
for (;;) {
2013+
int called = 0;
2014+
2015+
// Wrap up existing "threading"-module-created, non-daemon threads.
2016+
called = Py_MAX(called, wait_for_thread_shutdown(tstate));
2017+
2018+
// Make any remaining pending calls.
2019+
called = Py_MAX(called, _Py_FinishPendingCalls(tstate));
2020+
2021+
/* The interpreter is still entirely intact at this point, and the
2022+
* exit funcs may be relying on that. In particular, if some thread
2023+
* or exit func is still waiting to do an import, the import machinery
2024+
* expects Py_IsInitialized() to return true. So don't say the
2025+
* runtime is uninitialized until after the exit funcs have run.
2026+
* Note that Threading.py uses an exit func to do a join on all the
2027+
* threads created thru it, so this also protects pending imports in
2028+
* the threads created via Threading.
2029+
*/
2030+
2031+
called = Py_MAX(called, _PyAtExit_Call(tstate->interp));
2032+
2033+
if (called == 0) {
2034+
break;
2035+
}
2036+
}
2037+
}
2038+
20062039
static int
20072040
_Py_Finalize(_PyRuntimeState *runtime)
20082041
{
@@ -2019,23 +2052,7 @@ _Py_Finalize(_PyRuntimeState *runtime)
20192052
// Block some operations.
20202053
tstate->interp->finalizing = 1;
20212054

2022-
// Wrap up existing "threading"-module-created, non-daemon threads.
2023-
wait_for_thread_shutdown(tstate);
2024-
2025-
// Make any remaining pending calls.
2026-
_Py_FinishPendingCalls(tstate);
2027-
2028-
/* The interpreter is still entirely intact at this point, and the
2029-
* exit funcs may be relying on that. In particular, if some thread
2030-
* or exit func is still waiting to do an import, the import machinery
2031-
* expects Py_IsInitialized() to return true. So don't say the
2032-
* runtime is uninitialized until after the exit funcs have run.
2033-
* Note that Threading.py uses an exit func to do a join on all the
2034-
* threads created thru it, so this also protects pending imports in
2035-
* the threads created via Threading.
2036-
*/
2037-
2038-
_PyAtExit_Call(tstate->interp);
2055+
make_pre_finalization_calls(tstate);
20392056

20402057
assert(_PyThreadState_GET() == tstate);
20412058

@@ -3442,7 +3459,7 @@ Py_ExitStatusException(PyStatus status)
34423459
the threading module was imported in the first place.
34433460
The shutdown routine will wait until all non-daemon
34443461
"threading" threads have completed. */
3445-
static void
3462+
static int
34463463
wait_for_thread_shutdown(PyThreadState *tstate)
34473464
{
34483465
PyObject *result;
@@ -3452,16 +3469,19 @@ wait_for_thread_shutdown(PyThreadState *tstate)
34523469
PyErr_FormatUnraisable("Exception ignored on threading shutdown");
34533470
}
34543471
/* else: threading not imported */
3455-
return;
3472+
return 0;
34563473
}
3474+
int called = 0;
34573475
result = PyObject_CallMethodNoArgs(threading, &_Py_ID(_shutdown));
34583476
if (result == NULL) {
34593477
PyErr_FormatUnraisable("Exception ignored on threading shutdown");
34603478
}
34613479
else {
3462-
Py_DECREF(result);
3480+
assert(PyBool_Check(result) && _Py_IsImmortal(result));
3481+
called = result == Py_True;
34633482
}
34643483
Py_DECREF(threading);
3484+
return called;
34653485
}
34663486

34673487
int Py_AtExit(void (*func)(void))

0 commit comments

Comments
 (0)