Skip to content

Commit 71ffa1d

Browse files
committed
Changes from review
1 parent cf85548 commit 71ffa1d

File tree

6 files changed

+71
-62
lines changed

6 files changed

+71
-62
lines changed

Include/cpython/pystate.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ struct _ts {
102102
#endif
103103
int _whence;
104104

105-
/* Thread state (_Py_THREAD_ATTACHED, _Py_THREAD_DETACHED, _Py_THREAD_GC).
105+
/* Thread state (_Py_THREAD_ATTACHED, _Py_THREAD_DETACHED, _Py_THREAD_SUSPENDED).
106106
See Include/internal/pycore_pystate.h for more details. */
107107
int state;
108108

Include/internal/pycore_llist.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ struct llist_node {
3737
};
3838

3939
// Get the struct containing a node.
40-
#define llist_data(node, type, member) \
41-
(type*)((char*)node - offsetof(type, member))
40+
#define llist_data(node, type, member) (_Py_CONTAINER_OF(node, type, member))
4241

4342
// Iterate over a list.
4443
#define llist_for_each(node, head) \

Include/internal/pycore_pystate.h

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,27 @@ extern "C" {
1919
// interpreter at the same time. Only the "bound" thread may perform the
2020
// transitions between "attached" and "detached" on its own PyThreadState.
2121
//
22-
// The "gc" state is used to implement stop-the-world pauses, such as for
23-
// cyclic garbage collection. It is only used in `--disable-gil` builds. It is
24-
// similar to the "detached" state, but only the thread performing a
25-
// stop-the-world pause may transition threads between the "detached" and "gc"
26-
// states. A thread trying to "attach" from the "gc" state will block until
27-
// it is transitioned back to "detached" when the stop-the-world pause is
28-
// complete.
22+
// The "suspended" state is used to implement stop-the-world pauses, such as
23+
// for cyclic garbage collection. It is only used in `--disable-gil` builds. It
24+
// is similar to the "detached" state, but only the thread performing a
25+
// stop-the-world pause may transition a thread from the "suspended" state back
26+
// to the "detached" state. A thread trying to "attach" from the "suspended"
27+
// state will block until it is transitioned back to "detached" when the
28+
// stop-the-world pause is complete.
2929
//
3030
// State transition diagram:
3131
//
3232
// (bound thread) (stop-the-world thread)
33-
// [attached] <-> [detached] <-> [gc]
33+
// [attached] <-> [detached] <-> [suspended]
34+
// + ^
35+
// +--------------------------------------------------------+
36+
// (bound thread)
3437
//
35-
// See `_PyThreadState_Attach()` and `_PyThreadState_Detach()`.
38+
// See `_PyThreadState_Attach()`, `_PyThreadState_Detach()`, and
39+
// `_PyThreadState_Suspend()`.
3640
#define _Py_THREAD_DETACHED 0
3741
#define _Py_THREAD_ATTACHED 1
38-
#define _Py_THREAD_GC 2
42+
#define _Py_THREAD_SUSPENDED 2
3943

4044

4145
/* Check if the current thread is the main thread.
@@ -146,13 +150,13 @@ extern void _PyThreadState_Attach(PyThreadState *tstate);
146150
// calls this function.
147151
extern void _PyThreadState_Detach(PyThreadState *tstate);
148152

149-
// Temporarily pauses the thread in the GC state.
153+
// Detaches the current thread to the "suspended" state if a stop-the-world
154+
// pause is in progress.
150155
//
151-
// This is used to implement stop-the-world pauses. The thread must be in the
152-
// "attached" state. It will switch to the "GC" state and pause until the
153-
// stop-the-world event completes, after which it will switch back to the
154-
// "attached" state.
155-
extern void _PyThreadState_Park(PyThreadState *tstate);
156+
// If there is no stop-the-world pause in progress, then this function is
157+
// a no-op. Returns one if the thread was switched to the "suspended" state and
158+
// zero otherwise.
159+
extern int _PyThreadState_Suspend(PyThreadState *tstate);
156160

157161
// Perform a stop-the-world pause for all threads in the all interpreters.
158162
//

Include/internal/pycore_runtime.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,10 @@ typedef struct pyruntimestate {
227227
struct _faulthandler_runtime_state faulthandler;
228228
struct _tracemalloc_runtime_state tracemalloc;
229229

230+
// The rwmutex is used to prevent overlapping global and per-interpreter
231+
// stop-the-world events. Global stop-the-world events lock the mutex
232+
// exclusively (as a "writer"), while per-interpreter stop-the-world events
233+
// lock it non-exclusively (as "readers").
230234
_PyRWMutex stoptheworld_mutex;
231235
struct _stoptheworld_state stoptheworld;
232236

Python/ceval_gil.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -949,10 +949,14 @@ _Py_HandlePending(PyThreadState *tstate)
949949
{
950950
PyInterpreterState *interp = tstate->interp;
951951

952-
/* Pending signals */
952+
/* Stop-the-world */
953953
if (_Py_eval_breaker_bit_is_set(interp, _PY_EVAL_PLEASE_STOP_BIT)) {
954954
_Py_set_eval_breaker_bit(interp, _PY_EVAL_PLEASE_STOP_BIT, 0);
955-
_PyThreadState_Park(tstate);
955+
if (_PyThreadState_Suspend(tstate)) {
956+
/* The attach blocks until the stop-the-world event is complete. */
957+
_PyThreadState_Attach(tstate);
958+
}
959+
// else: stale stop-the-world event, ignore it!
956960
}
957961

958962
/* Pending signals */

Python/pystate.c

Lines changed: 39 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,7 +1565,7 @@ tstate_delete_common(PyThreadState *tstate)
15651565
if (tstate->next) {
15661566
tstate->next->prev = tstate->prev;
15671567
}
1568-
if (tstate->state != _Py_THREAD_GC) {
1568+
if (tstate->state != _Py_THREAD_SUSPENDED) {
15691569
// Any ongoing stop-the-world request should not wait for us because
15701570
// our thread is getting deleted.
15711571
if (interp->stoptheworld.requested) {
@@ -1805,9 +1805,9 @@ static void
18051805
tstate_wait_attach(PyThreadState *tstate)
18061806
{
18071807
do {
1808-
int expected = _Py_THREAD_GC;
1808+
int expected = _Py_THREAD_SUSPENDED;
18091809

1810-
// Wait until we're switched out of GC to DETACHED.
1810+
// Wait until we're switched out of SUSPENDED to DETACHED.
18111811
_PyParkingLot_Park(&tstate->state, &expected, sizeof(tstate->state),
18121812
/*timeout=*/-1, NULL, /*detach=*/0);
18131813

@@ -1871,19 +1871,8 @@ _PyThreadState_Detach(PyThreadState *tstate)
18711871
detach_thread(tstate, _Py_THREAD_DETACHED);
18721872
}
18731873

1874-
// Decrease stop-the-world counter of remaining number of threads that need to
1875-
// pause. If we are the final thread to pause, notify the requesting thread.
1876-
static void
1877-
decrement_stoptheworld_countdown(struct _stoptheworld_state *stw)
1878-
{
1879-
assert(stw->thread_countdown > 0);
1880-
if (--stw->thread_countdown == 0) {
1881-
_PyEvent_Notify(&stw->stop_event);
1882-
}
1883-
}
1884-
1885-
void
1886-
_PyThreadState_Park(PyThreadState *tstate)
1874+
int
1875+
_PyThreadState_Suspend(PyThreadState *tstate)
18871876
{
18881877
_PyRuntimeState *runtime = &_PyRuntime;
18891878

@@ -1900,25 +1889,30 @@ _PyThreadState_Park(PyThreadState *tstate)
19001889
HEAD_UNLOCK(runtime);
19011890

19021891
if (stw == NULL) {
1903-
// We might be processing a stale EVAL_PLEASE_STOP, in which
1904-
// case there is nothing to do. This can happen if a thread
1905-
// asks us to stop for a previous GC at the same time we detach.
1906-
return;
1892+
// Don't suspend if we are not in a stop-the-world event.
1893+
return 0;
19071894
}
19081895

1909-
// Switch to GC state.
1910-
detach_thread(tstate, _Py_THREAD_GC);
1896+
// Switch to "suspended" state.
1897+
detach_thread(tstate, _Py_THREAD_SUSPENDED);
19111898

19121899
// Decrease the count of remaining threads needing to park.
19131900
HEAD_LOCK(runtime);
19141901
decrement_stoptheworld_countdown(stw);
19151902
HEAD_UNLOCK(runtime);
1916-
1917-
// Wait until we are switched back to DETACHED and then re-attach. After
1918-
// this we will be in the ATTACHED state, the same as before.
1919-
tstate_wait_attach(tstate);
1903+
return 1;
19201904
}
19211905

1906+
// Decrease stop-the-world counter of remaining number of threads that need to
1907+
// pause. If we are the final thread to pause, notify the requesting thread.
1908+
static void
1909+
decrement_stoptheworld_countdown(struct _stoptheworld_state *stw)
1910+
{
1911+
assert(stw->thread_countdown > 0);
1912+
if (--stw->thread_countdown == 0) {
1913+
_PyEvent_Notify(&stw->stop_event);
1914+
}
1915+
}
19221916

19231917
#ifdef Py_GIL_DISABLED
19241918
// Interpreter for _Py_FOR_EACH_THREAD(). For global stop-the-world events,
@@ -1936,10 +1930,10 @@ interp_for_stop_the_world(struct _stoptheworld_state *stw)
19361930
// Loops over threads for a stop-the-world event.
19371931
// For global: all threads in all interpreters
19381932
// For per-interpreter: all threads in the interpreter
1939-
#define _Py_FOR_EACH_THREAD(stw) \
1940-
for (PyInterpreterState *i = interp_for_stop_the_world((stw)); \
1933+
#define _Py_FOR_EACH_THREAD(stw, i, t) \
1934+
for (i = interp_for_stop_the_world((stw)); \
19411935
i != NULL; i = ((stw->is_global) ? i->next : NULL)) \
1942-
for (PyThreadState *t = i->threads.head; t; t = t->next)
1936+
for (t = i->threads.head; t; t = t->next)
19431937

19441938

19451939
// Try to transition threads atomically from the "detached" state to the
@@ -1948,12 +1942,14 @@ static bool
19481942
park_detached_threads(struct _stoptheworld_state *stw)
19491943
{
19501944
int num_parked = 0;
1951-
_Py_FOR_EACH_THREAD(stw) {
1945+
PyInterpreterState *i;
1946+
PyThreadState *t;
1947+
_Py_FOR_EACH_THREAD(stw, i, t) {
19521948
int state = _Py_atomic_load_int_relaxed(&t->state);
19531949
if (state == _Py_THREAD_DETACHED) {
1954-
// Atomically transition to _Py_THREAD_GC if in detached state.
1950+
// Atomically transition to "suspended" if in "detached" state.
19551951
if (_Py_atomic_compare_exchange_int(&t->state,
1956-
&state, _Py_THREAD_GC)) {
1952+
&state, _Py_THREAD_SUSPENDED)) {
19571953
num_parked++;
19581954
}
19591955
}
@@ -1983,9 +1979,12 @@ stop_the_world(struct _stoptheworld_state *stw)
19831979
HEAD_LOCK(runtime);
19841980
stw->requested = 1;
19851981
stw->thread_countdown = 0;
1982+
stw->stop_event = (PyEvent){0}; // zero-initialize (unset)
19861983
stw->requester = _PyThreadState_GET(); // may be NULL
19871984

1988-
_Py_FOR_EACH_THREAD(stw) {
1985+
PyInterpreterState *i;
1986+
PyThreadState *t;
1987+
_Py_FOR_EACH_THREAD(stw, i, t) {
19891988
if (t != stw->requester) {
19901989
// Count all the other threads (we don't wait on ourself).
19911990
stw->thread_countdown++;
@@ -2007,10 +2006,9 @@ stop_the_world(struct _stoptheworld_state *stw)
20072006
break;
20082007
}
20092008

2010-
int64_t wait_ns = 1000*1000; // 1ms
2009+
_PyTime_t wait_ns = 1000*1000; // 1ms (arbitrary, may need tuning)
20112010
if (PyEvent_WaitTimed(&stw->stop_event, wait_ns)) {
20122011
assert(stw->thread_countdown == 0);
2013-
stw->stop_event = (PyEvent){0};
20142012
break;
20152013
}
20162014

@@ -2030,12 +2028,12 @@ start_the_world(struct _stoptheworld_state *stw)
20302028
stw->world_stopped = 0;
20312029
stw->requester = NULL;
20322030
// Switch threads back to the detached state.
2033-
_Py_FOR_EACH_THREAD(stw) {
2034-
int state = _Py_atomic_load_int_relaxed(&t->state);
2035-
if (state == _Py_THREAD_GC &&
2036-
_Py_atomic_compare_exchange_int(&t->state,
2037-
&state,
2038-
_Py_THREAD_DETACHED)) {
2031+
PyInterpreterState *i;
2032+
PyThreadState *t;
2033+
_Py_FOR_EACH_THREAD(stw, i, t) {
2034+
if (t != stw->requester) {
2035+
assert(t->state == _Py_THREAD_SUSPENDED);
2036+
_Py_atomic_store_int(&t->state, _Py_THREAD_DETACHED);
20392037
_PyParkingLot_UnparkAll(&t->state);
20402038
}
20412039
}

0 commit comments

Comments
 (0)