Skip to content

Commit 6e97a96

Browse files
authored
gh-109549: Add new states to PyThreadState to support PEP 703 (gh-109915)
This adds a new field 'state' to PyThreadState that can take on one of three values: _Py_THREAD_ATTACHED, _Py_THREAD_DETACHED, or _Py_THREAD_GC. The "attached" and "detached" states correspond closely to acquiring and releasing the GIL. The "gc" state is current unused, but will be used to implement stop-the-world GC for --disable-gil builds in the near future.
1 parent 9eb2489 commit 6e97a96

File tree

6 files changed

+141
-92
lines changed

6 files changed

+141
-92
lines changed

Include/cpython/pystate.h

+4
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ struct _ts {
102102
#endif
103103
int _whence;
104104

105+
/* Thread state (_Py_THREAD_ATTACHED, _Py_THREAD_DETACHED, _Py_THREAD_GC).
106+
See Include/internal/pycore_pystate.h for more details. */
107+
int state;
108+
105109
int py_recursion_remaining;
106110
int py_recursion_limit;
107111

Include/internal/pycore_ceval.h

-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ extern void _PyEval_FiniGIL(PyInterpreterState *interp);
121121

122122
extern void _PyEval_AcquireLock(PyThreadState *tstate);
123123
extern void _PyEval_ReleaseLock(PyInterpreterState *, PyThreadState *);
124-
extern PyThreadState * _PyThreadState_SwapNoGIL(PyThreadState *);
125124

126125
extern void _PyEval_DeactivateOpCache(void);
127126

Include/internal/pycore_pystate.h

+42
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,33 @@ extern "C" {
1111
#include "pycore_runtime.h" // _PyRuntime
1212

1313

14+
// Values for PyThreadState.state. A thread must be in the "attached" state
15+
// before calling most Python APIs. If the GIL is enabled, then "attached"
16+
// implies that the thread holds the GIL and "detached" implies that the
17+
// thread does not hold the GIL (or is in the process of releasing it). In
18+
// `--disable-gil` builds, multiple threads may be "attached" to the same
19+
// interpreter at the same time. Only the "bound" thread may perform the
20+
// transitions between "attached" and "detached" on its own PyThreadState.
21+
//
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.
29+
//
30+
// State transition diagram:
31+
//
32+
// (bound thread) (stop-the-world thread)
33+
// [attached] <-> [detached] <-> [gc]
34+
//
35+
// See `_PyThreadState_Attach()` and `_PyThreadState_Detach()`.
36+
#define _Py_THREAD_DETACHED 0
37+
#define _Py_THREAD_ATTACHED 1
38+
#define _Py_THREAD_GC 2
39+
40+
1441
/* Check if the current thread is the main thread.
1542
Use _Py_IsMainInterpreter() to check if it's the main interpreter. */
1643
static inline int
@@ -104,6 +131,21 @@ _PyThreadState_GET(void)
104131
#endif
105132
}
106133

134+
// Attaches the current thread to the interpreter.
135+
//
136+
// This may block while acquiring the GIL (if the GIL is enabled) or while
137+
// waiting for a stop-the-world pause (if the GIL is disabled).
138+
//
139+
// High-level code should generally call PyEval_RestoreThread() instead, which
140+
// calls this function.
141+
void _PyThreadState_Attach(PyThreadState *tstate);
142+
143+
// Detaches the current thread from the interpreter.
144+
//
145+
// High-level code should generally call PyEval_SaveThread() instead, which
146+
// calls this function.
147+
void _PyThreadState_Detach(PyThreadState *tstate);
148+
107149

108150
static inline void
109151
_Py_EnsureFuncTstateNotNULL(const char *func, PyThreadState *tstate)

Python/ceval_gil.c

+11-36
Original file line numberDiff line numberDiff line change
@@ -462,24 +462,22 @@ PyStatus
462462
_PyEval_InitGIL(PyThreadState *tstate, int own_gil)
463463
{
464464
assert(tstate->interp->ceval.gil == NULL);
465-
int locked;
466465
if (!own_gil) {
467466
/* The interpreter will share the main interpreter's instead. */
468467
PyInterpreterState *main_interp = _PyInterpreterState_Main();
469468
assert(tstate->interp != main_interp);
470469
struct _gil_runtime_state *gil = main_interp->ceval.gil;
471470
init_shared_gil(tstate->interp, gil);
472-
locked = current_thread_holds_gil(gil, tstate);
471+
assert(!current_thread_holds_gil(gil, tstate));
473472
}
474473
else {
475474
PyThread_init_thread();
476475
init_own_gil(tstate->interp, &tstate->interp->_gil);
477-
locked = 0;
478-
}
479-
if (!locked) {
480-
take_gil(tstate);
481476
}
482477

478+
// Lock the GIL and mark the current thread as attached.
479+
_PyThreadState_Attach(tstate);
480+
483481
return _PyStatus_OK();
484482
}
485483

@@ -569,24 +567,14 @@ void
569567
PyEval_AcquireThread(PyThreadState *tstate)
570568
{
571569
_Py_EnsureTstateNotNULL(tstate);
572-
573-
take_gil(tstate);
574-
575-
if (_PyThreadState_SwapNoGIL(tstate) != NULL) {
576-
Py_FatalError("non-NULL old thread state");
577-
}
570+
_PyThreadState_Attach(tstate);
578571
}
579572

580573
void
581574
PyEval_ReleaseThread(PyThreadState *tstate)
582575
{
583576
assert(_PyThreadState_CheckConsistency(tstate));
584-
585-
PyThreadState *new_tstate = _PyThreadState_SwapNoGIL(NULL);
586-
if (new_tstate != tstate) {
587-
Py_FatalError("wrong thread state");
588-
}
589-
drop_gil(tstate->interp, tstate);
577+
_PyThreadState_Detach(tstate);
590578
}
591579

592580
#ifdef HAVE_FORK
@@ -629,22 +617,16 @@ _PyEval_SignalAsyncExc(PyInterpreterState *interp)
629617
PyThreadState *
630618
PyEval_SaveThread(void)
631619
{
632-
PyThreadState *tstate = _PyThreadState_SwapNoGIL(NULL);
633-
_Py_EnsureTstateNotNULL(tstate);
634-
635-
assert(gil_created(tstate->interp->ceval.gil));
636-
drop_gil(tstate->interp, tstate);
620+
PyThreadState *tstate = _PyThreadState_GET();
621+
_PyThreadState_Detach(tstate);
637622
return tstate;
638623
}
639624

640625
void
641626
PyEval_RestoreThread(PyThreadState *tstate)
642627
{
643628
_Py_EnsureTstateNotNULL(tstate);
644-
645-
take_gil(tstate);
646-
647-
_PyThreadState_SwapNoGIL(tstate);
629+
_PyThreadState_Attach(tstate);
648630
}
649631

650632

@@ -1015,18 +997,11 @@ _Py_HandlePending(PyThreadState *tstate)
1015997
/* GIL drop request */
1016998
if (_Py_eval_breaker_bit_is_set(interp, _PY_GIL_DROP_REQUEST_BIT)) {
1017999
/* Give another thread a chance */
1018-
if (_PyThreadState_SwapNoGIL(NULL) != tstate) {
1019-
Py_FatalError("tstate mix-up");
1020-
}
1021-
drop_gil(interp, tstate);
1000+
_PyThreadState_Detach(tstate);
10221001

10231002
/* Other threads may run now */
10241003

1025-
take_gil(tstate);
1026-
1027-
if (_PyThreadState_SwapNoGIL(tstate) != NULL) {
1028-
Py_FatalError("orphan tstate");
1029-
}
1004+
_PyThreadState_Attach(tstate);
10301005
}
10311006

10321007
/* Check for asynchronous exception. */

Python/pylifecycle.c

+5-9
Original file line numberDiff line numberDiff line change
@@ -661,8 +661,6 @@ pycore_create_interpreter(_PyRuntimeState *runtime,
661661
return _PyStatus_ERR("can't make first thread");
662662
}
663663
_PyThreadState_Bind(tstate);
664-
// XXX For now we do this before the GIL is created.
665-
(void) _PyThreadState_SwapNoGIL(tstate);
666664

667665
status = init_interp_create_gil(tstate, config.gil);
668666
if (_PyStatus_EXCEPTION(status)) {
@@ -2060,8 +2058,7 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config)
20602058
}
20612059
_PyThreadState_Bind(tstate);
20622060

2063-
// XXX For now we do this before the GIL is created.
2064-
PyThreadState *save_tstate = _PyThreadState_SwapNoGIL(tstate);
2061+
PyThreadState *save_tstate = _PyThreadState_GET();
20652062
int has_gil = 0;
20662063

20672064
/* From this point until the init_interp_create_gil() call,
@@ -2073,7 +2070,7 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config)
20732070
const PyConfig *src_config;
20742071
if (save_tstate != NULL) {
20752072
// XXX Might new_interpreter() have been called without the GIL held?
2076-
_PyEval_ReleaseLock(save_tstate->interp, save_tstate);
2073+
_PyThreadState_Detach(save_tstate);
20772074
src_config = _PyInterpreterState_GetConfig(save_tstate->interp);
20782075
}
20792076
else
@@ -2120,12 +2117,11 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config)
21202117
*tstate_p = NULL;
21212118

21222119
/* Oops, it didn't work. Undo it all. */
2123-
PyErr_PrintEx(0);
21242120
if (has_gil) {
2125-
PyThreadState_Swap(save_tstate);
2121+
_PyThreadState_Detach(tstate);
21262122
}
2127-
else {
2128-
_PyThreadState_SwapNoGIL(save_tstate);
2123+
if (save_tstate != NULL) {
2124+
_PyThreadState_Attach(save_tstate);
21292125
}
21302126
PyThreadState_Clear(tstate);
21312127
PyThreadState_Delete(tstate);

Python/pystate.c

+79-46
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,7 @@ _PyInterpreterState_Clear(PyThreadState *tstate)
998998

999999

10001000
static inline void tstate_deactivate(PyThreadState *tstate);
1001+
static void tstate_set_detached(PyThreadState *tstate);
10011002
static void zapthreads(PyInterpreterState *interp);
10021003

10031004
void
@@ -1011,9 +1012,7 @@ PyInterpreterState_Delete(PyInterpreterState *interp)
10111012
PyThreadState *tcur = current_fast_get(runtime);
10121013
if (tcur != NULL && interp == tcur->interp) {
10131014
/* Unset current thread. After this, many C API calls become crashy. */
1014-
current_fast_clear(runtime);
1015-
tstate_deactivate(tcur);
1016-
_PyEval_ReleaseLock(interp, NULL);
1015+
_PyThreadState_Detach(tcur);
10171016
}
10181017

10191018
zapthreads(interp);
@@ -1651,6 +1650,7 @@ static void
16511650
tstate_delete_common(PyThreadState *tstate)
16521651
{
16531652
assert(tstate->_status.cleared && !tstate->_status.finalized);
1653+
assert(tstate->state != _Py_THREAD_ATTACHED);
16541654

16551655
PyInterpreterState *interp = tstate->interp;
16561656
if (interp == NULL) {
@@ -1711,6 +1711,7 @@ void
17111711
_PyThreadState_DeleteCurrent(PyThreadState *tstate)
17121712
{
17131713
_Py_EnsureTstateNotNULL(tstate);
1714+
tstate_set_detached(tstate);
17141715
tstate_delete_common(tstate);
17151716
current_fast_clear(tstate->interp->runtime);
17161717
_PyEval_ReleaseLock(tstate->interp, NULL);
@@ -1867,6 +1868,79 @@ tstate_deactivate(PyThreadState *tstate)
18671868
// It will still be used in PyGILState_Ensure().
18681869
}
18691870

1871+
static int
1872+
tstate_try_attach(PyThreadState *tstate)
1873+
{
1874+
#ifdef Py_NOGIL
1875+
int expected = _Py_THREAD_DETACHED;
1876+
if (_Py_atomic_compare_exchange_int(
1877+
&tstate->state,
1878+
&expected,
1879+
_Py_THREAD_ATTACHED)) {
1880+
return 1;
1881+
}
1882+
return 0;
1883+
#else
1884+
assert(tstate->state == _Py_THREAD_DETACHED);
1885+
tstate->state = _Py_THREAD_ATTACHED;
1886+
return 1;
1887+
#endif
1888+
}
1889+
1890+
static void
1891+
tstate_set_detached(PyThreadState *tstate)
1892+
{
1893+
assert(tstate->state == _Py_THREAD_ATTACHED);
1894+
#ifdef Py_NOGIL
1895+
_Py_atomic_store_int(&tstate->state, _Py_THREAD_DETACHED);
1896+
#else
1897+
tstate->state = _Py_THREAD_DETACHED;
1898+
#endif
1899+
}
1900+
1901+
void
1902+
_PyThreadState_Attach(PyThreadState *tstate)
1903+
{
1904+
#if defined(Py_DEBUG)
1905+
// This is called from PyEval_RestoreThread(). Similar
1906+
// to it, we need to ensure errno doesn't change.
1907+
int err = errno;
1908+
#endif
1909+
1910+
_Py_EnsureTstateNotNULL(tstate);
1911+
if (current_fast_get(&_PyRuntime) != NULL) {
1912+
Py_FatalError("non-NULL old thread state");
1913+
}
1914+
1915+
_PyEval_AcquireLock(tstate);
1916+
1917+
// XXX assert(tstate_is_alive(tstate));
1918+
current_fast_set(&_PyRuntime, tstate);
1919+
tstate_activate(tstate);
1920+
1921+
if (!tstate_try_attach(tstate)) {
1922+
// TODO: Once stop-the-world GC is implemented for --disable-gil builds
1923+
// this will need to wait until the GC completes. For now, this case
1924+
// should never happen.
1925+
Py_FatalError("thread attach failed");
1926+
}
1927+
1928+
#if defined(Py_DEBUG)
1929+
errno = err;
1930+
#endif
1931+
}
1932+
1933+
void
1934+
_PyThreadState_Detach(PyThreadState *tstate)
1935+
{
1936+
// XXX assert(tstate_is_alive(tstate) && tstate_is_bound(tstate));
1937+
assert(tstate->state == _Py_THREAD_ATTACHED);
1938+
assert(tstate == current_fast_get(&_PyRuntime));
1939+
tstate_set_detached(tstate);
1940+
tstate_deactivate(tstate);
1941+
current_fast_clear(&_PyRuntime);
1942+
_PyEval_ReleaseLock(tstate->interp, tstate);
1943+
}
18701944

18711945
//----------
18721946
// other API
@@ -1939,56 +2013,15 @@ PyThreadState_Get(void)
19392013
return tstate;
19402014
}
19412015

1942-
1943-
static void
1944-
_swap_thread_states(_PyRuntimeState *runtime,
1945-
PyThreadState *oldts, PyThreadState *newts)
1946-
{
1947-
// XXX Do this only if oldts != NULL?
1948-
current_fast_clear(runtime);
1949-
1950-
if (oldts != NULL) {
1951-
// XXX assert(tstate_is_alive(oldts) && tstate_is_bound(oldts));
1952-
tstate_deactivate(oldts);
1953-
}
1954-
1955-
if (newts != NULL) {
1956-
// XXX assert(tstate_is_alive(newts));
1957-
assert(tstate_is_bound(newts));
1958-
current_fast_set(runtime, newts);
1959-
tstate_activate(newts);
1960-
}
1961-
}
1962-
1963-
PyThreadState *
1964-
_PyThreadState_SwapNoGIL(PyThreadState *newts)
1965-
{
1966-
#if defined(Py_DEBUG)
1967-
/* This can be called from PyEval_RestoreThread(). Similar
1968-
to it, we need to ensure errno doesn't change.
1969-
*/
1970-
int err = errno;
1971-
#endif
1972-
1973-
PyThreadState *oldts = current_fast_get(&_PyRuntime);
1974-
_swap_thread_states(&_PyRuntime, oldts, newts);
1975-
1976-
#if defined(Py_DEBUG)
1977-
errno = err;
1978-
#endif
1979-
return oldts;
1980-
}
1981-
19822016
PyThreadState *
19832017
_PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts)
19842018
{
19852019
PyThreadState *oldts = current_fast_get(runtime);
19862020
if (oldts != NULL) {
1987-
_PyEval_ReleaseLock(oldts->interp, oldts);
2021+
_PyThreadState_Detach(oldts);
19882022
}
1989-
_swap_thread_states(runtime, oldts, newts);
19902023
if (newts != NULL) {
1991-
_PyEval_AcquireLock(newts);
2024+
_PyThreadState_Attach(newts);
19922025
}
19932026
return oldts;
19942027
}

0 commit comments

Comments
 (0)