Skip to content

Commit c34abd4

Browse files
committed
gh-151518: Preserve attach waiters when resuming BRC suspensions
Share the waiter-aware resume transition between stop-the-world pauses and biased reference count merging so a concurrently registering waiter keeps its opportunity to attach. Restore the support import needed by the new upstream GC regression and align the thread-state constants.
1 parent cec1cce commit c34abd4

3 files changed

Lines changed: 27 additions & 26 deletions

File tree

‎Include/internal/pycore_pystate.h‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ extern "C" {
2121
// interpreter at the same time. Only the "bound" thread may perform the
2222
// transitions between "attached" and "detached" on its own PyThreadState.
2323
//
24-
// The "suspended" states are used to implement stop-the-world pauses, such as
25-
// for cyclic garbage collection. They are only used in `--disable-gil` builds.
24+
// The "suspended" states are used to implement stop-the-world pauses and to
25+
// merge biased reference counts on behalf of detached threads. They are only
26+
// used in `--disable-gil` builds.
2627
// They are similar to the "detached" state in that the thread is not allowed
2728
// to call most Python APIs. A suspended thread trying to attach marks itself
28-
// as "suspended-waiting". Only the thread performing a stop-the-world pause
29-
// may resume a suspended thread, moving it to "detached" or "detached-waiting".
29+
// as "suspended-waiting". Only the thread responsible for suspending it may
30+
// resume it, moving it to "detached" or "detached-waiting".
3031
// A "detached-waiting" thread must attach before it can be suspended again.
3132
//
3233
// The "shutting down" state is used when the interpreter is being finalized.
@@ -38,10 +39,10 @@ extern "C" {
3839
// attached -> suspended
3940
// suspended -> suspended-waiting
4041
// detached-waiting -> attached
41-
// Stop-the-world thread: detached <-> suspended
42+
// Suspending thread: detached <-> suspended
4243
// suspended-waiting -> detached-waiting
43-
#define _Py_THREAD_DETACHED 0
44-
#define _Py_THREAD_ATTACHED 1
44+
#define _Py_THREAD_DETACHED 0
45+
#define _Py_THREAD_ATTACHED 1
4546
#define _Py_THREAD_SUSPENDED 2
4647
#define _Py_THREAD_SHUTTING_DOWN 3
4748
#define _Py_THREAD_SUSPENDED_WAITING 4
@@ -161,8 +162,9 @@ extern void _PyThreadState_Suspend(PyThreadState *tstate);
161162
// Returns 1 on success, 0 if the thread was not in the "detached" state.
162163
extern int _PyThreadState_TrySuspendDetached(PyThreadState *tstate);
163164

164-
// Undo a successful _PyThreadState_TrySuspendDetached(): switch the thread
165-
// back to "detached" and wake it if it is waiting to attach.
165+
// Resume a thread suspended by _PyThreadState_TrySuspendDetached() or a
166+
// stop-the-world pause: switch it back to "detached" or "detached-waiting"
167+
// and wake it if it is waiting to attach.
166168
extern void _PyThreadState_ResumeDetached(PyThreadState *tstate);
167169
#endif
168170

‎Lib/test/test_free_threading/test_gc.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import gc
88
import weakref
99

10+
from test import support
1011
from test.support import threading_helper
1112

1213

‎Python/pystate.c‎

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2426,8 +2426,20 @@ void
24262426
_PyThreadState_ResumeDetached(PyThreadState *tstate)
24272427
{
24282428
assert(tstate != _PyThreadState_GET());
2429-
assert(_Py_atomic_load_int_relaxed(&tstate->state) == _Py_THREAD_SUSPENDED);
2430-
_Py_atomic_store_int(&tstate->state, _Py_THREAD_DETACHED);
2429+
int state = _Py_atomic_load_int_relaxed(&tstate->state);
2430+
int next_state;
2431+
do {
2432+
assert(state == _Py_THREAD_SUSPENDED ||
2433+
state == _Py_THREAD_SUSPENDED_WAITING);
2434+
if (state == _Py_THREAD_SUSPENDED_WAITING) {
2435+
next_state = _Py_THREAD_DETACHED_WAITING;
2436+
}
2437+
else {
2438+
next_state = _Py_THREAD_DETACHED;
2439+
}
2440+
// Retry if an attach waiter registered concurrently.
2441+
} while (!_Py_atomic_compare_exchange_int(
2442+
&tstate->state, &state, next_state));
24312443
// Wake the thread if it is parked in tstate_wait_attach().
24322444
_PyParkingLot_UnparkAll(&tstate->state);
24332445
}
@@ -2564,21 +2576,7 @@ start_the_world(struct _stoptheworld_state *stw)
25642576
_Py_FOR_EACH_STW_INTERP(stw, i) {
25652577
_Py_FOR_EACH_TSTATE_UNLOCKED(i, t) {
25662578
if (t != stw->requester) {
2567-
int state = _Py_atomic_load_int_relaxed(&t->state);
2568-
int next_state;
2569-
do {
2570-
assert(state == _Py_THREAD_SUSPENDED ||
2571-
state == _Py_THREAD_SUSPENDED_WAITING);
2572-
if (state == _Py_THREAD_SUSPENDED_WAITING) {
2573-
next_state = _Py_THREAD_DETACHED_WAITING;
2574-
}
2575-
else {
2576-
next_state = _Py_THREAD_DETACHED;
2577-
}
2578-
// Retry if an attach waiter registered concurrently.
2579-
} while (!_Py_atomic_compare_exchange_int(
2580-
&t->state, &state, next_state));
2581-
_PyParkingLot_UnparkAll(&t->state);
2579+
_PyThreadState_ResumeDetached(t);
25822580
}
25832581
}
25842582
}

0 commit comments

Comments
 (0)