Skip to content

Commit 31ec6f0

Browse files
committed
pystate: refcount threads to handle race between interpreter shutdown and thread exit
1 parent 5722416 commit 31ec6f0

4 files changed

Lines changed: 34 additions & 4 deletions

File tree

‎Include/cpython/pystate.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ struct _ts {
132132

133133
mi_heap_t *heaps[Py_NUM_HEAPS];
134134

135+
Py_ssize_t refcount;
136+
135137
/* Has been initialized to a safe state.
136138
137139
In order to be effective, this must be set to 0 during or right

‎Include/internal/pycore_pystate.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ PyAPI_FUNC(PyThreadState *) _PyThreadState_UnlinkExcept(
169169
int already_dead);
170170
PyAPI_FUNC(void) _PyThreadState_DeleteGarbage(PyThreadState *garbage);
171171

172+
extern void _PyThreadState_Exit(PyThreadState *tstate);
173+
172174
static inline void
173175
_PyThreadState_Signal(PyThreadState *tstate, uintptr_t bit)
174176
{

‎Python/ceval_gil.c‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ take_gil(PyThreadState *tstate)
263263
This code path can be reached by a daemon thread after Py_Finalize()
264264
completes. In this case, tstate is a dangling pointer: points to
265265
PyThreadState freed memory. */
266-
PyThread_exit_thread();
266+
_PyThreadState_Exit(tstate);
267267
}
268268

269269
assert(is_tstate_valid(tstate));
@@ -310,7 +310,7 @@ take_gil(PyThreadState *tstate)
310310
_PyThreadState_Unsignal(gil->holder, EVAL_DROP_GIL);
311311
}
312312
MUTEX_UNLOCK(gil->mutex);
313-
PyThread_exit_thread();
313+
_PyThreadState_Exit(tstate);
314314
}
315315
assert(is_tstate_valid(tstate));
316316

@@ -350,7 +350,7 @@ take_gil(PyThreadState *tstate)
350350
wait_for_thread_shutdown() from Py_Finalize(). */
351351
MUTEX_UNLOCK(gil->mutex);
352352
drop_gil(ceval, ceval2, tstate);
353-
PyThread_exit_thread();
353+
_PyThreadState_Exit(tstate);
354354
}
355355
assert(is_tstate_valid(tstate));
356356

‎Python/pystate.c‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,26 @@ free_threadstate(PyThreadState *tstate)
992992
}
993993
}
994994

995+
static void
996+
_PyThreadState_DecRef(PyThreadState *tstate)
997+
{
998+
if (tstate != &tstate->interp->_initial_thread.tstate) {
999+
if (_Py_atomic_add_ssize(&tstate->refcount, -1) == 1) {
1000+
free_threadstate(tstate);
1001+
}
1002+
}
1003+
}
1004+
1005+
void
1006+
_PyThreadState_Exit(PyThreadState *tstate)
1007+
{
1008+
if (_PyThreadState_GetStatus(tstate) == _Py_THREAD_ATTACHED) {
1009+
_Py_atomic_store_int(&tstate->status, _Py_THREAD_DETACHED);
1010+
}
1011+
_PyThreadState_DecRef(tstate);
1012+
PyThread_exit_thread();
1013+
}
1014+
9951015
/* Get the thread state to a minimal consistent state.
9961016
Further init happens in pylifecycle.c before it can be used.
9971017
All fields not initialized here are expected to be zeroed out,
@@ -1061,6 +1081,7 @@ init_threadstate(PyThreadState *tstate,
10611081
if (_PyRuntime.stop_the_world_requested) {
10621082
tstate->status = _Py_THREAD_GC;
10631083
}
1084+
tstate->refcount = 2;
10641085
tstate->_initialized = 1;
10651086
}
10661087

@@ -1562,7 +1583,12 @@ void
15621583
_PyThreadState_DeleteExcept(_PyRuntimeState *runtime, PyThreadState *tstate)
15631584
{
15641585
PyThreadState *garbage = _PyThreadState_UnlinkExcept(runtime, tstate, 0);
1565-
_PyThreadState_DeleteGarbage(garbage);
1586+
PyThreadState *next;
1587+
for (PyThreadState *p = garbage; p; p = next) {
1588+
next = p->next;
1589+
PyThreadState_Clear(p);
1590+
_PyThreadState_DecRef(tstate);
1591+
}
15661592
}
15671593

15681594
PyThreadState *

0 commit comments

Comments
 (0)