diff options
author | Sam Gross <colesbury@gmail.com> | 2023-10-05 15:46:33 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-05 15:46:33 (GMT) |
commit | 6e97a9647ae028facb392d12fc24973503693bd6 (patch) | |
tree | cce40bd0d1b8317652405f04c54541d48b3419e9 /Python/pystate.c | |
parent | 9eb2489266c4c1f115b8f72c0728db737cc8a815 (diff) | |
download | cpython-6e97a9647ae028facb392d12fc24973503693bd6.zip cpython-6e97a9647ae028facb392d12fc24973503693bd6.tar.gz cpython-6e97a9647ae028facb392d12fc24973503693bd6.tar.bz2 |
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.
Diffstat (limited to 'Python/pystate.c')
-rw-r--r-- | Python/pystate.c | 125 |
1 files changed, 79 insertions, 46 deletions
diff --git a/Python/pystate.c b/Python/pystate.c index 0687408..a024ae7 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -998,6 +998,7 @@ _PyInterpreterState_Clear(PyThreadState *tstate) static inline void tstate_deactivate(PyThreadState *tstate); +static void tstate_set_detached(PyThreadState *tstate); static void zapthreads(PyInterpreterState *interp); void @@ -1011,9 +1012,7 @@ PyInterpreterState_Delete(PyInterpreterState *interp) PyThreadState *tcur = current_fast_get(runtime); if (tcur != NULL && interp == tcur->interp) { /* Unset current thread. After this, many C API calls become crashy. */ - current_fast_clear(runtime); - tstate_deactivate(tcur); - _PyEval_ReleaseLock(interp, NULL); + _PyThreadState_Detach(tcur); } zapthreads(interp); @@ -1651,6 +1650,7 @@ static void tstate_delete_common(PyThreadState *tstate) { assert(tstate->_status.cleared && !tstate->_status.finalized); + assert(tstate->state != _Py_THREAD_ATTACHED); PyInterpreterState *interp = tstate->interp; if (interp == NULL) { @@ -1711,6 +1711,7 @@ void _PyThreadState_DeleteCurrent(PyThreadState *tstate) { _Py_EnsureTstateNotNULL(tstate); + tstate_set_detached(tstate); tstate_delete_common(tstate); current_fast_clear(tstate->interp->runtime); _PyEval_ReleaseLock(tstate->interp, NULL); @@ -1867,6 +1868,79 @@ tstate_deactivate(PyThreadState *tstate) // It will still be used in PyGILState_Ensure(). } +static int +tstate_try_attach(PyThreadState *tstate) +{ +#ifdef Py_NOGIL + int expected = _Py_THREAD_DETACHED; + if (_Py_atomic_compare_exchange_int( + &tstate->state, + &expected, + _Py_THREAD_ATTACHED)) { + return 1; + } + return 0; +#else + assert(tstate->state == _Py_THREAD_DETACHED); + tstate->state = _Py_THREAD_ATTACHED; + return 1; +#endif +} + +static void +tstate_set_detached(PyThreadState *tstate) +{ + assert(tstate->state == _Py_THREAD_ATTACHED); +#ifdef Py_NOGIL + _Py_atomic_store_int(&tstate->state, _Py_THREAD_DETACHED); +#else + tstate->state = _Py_THREAD_DETACHED; +#endif +} + +void +_PyThreadState_Attach(PyThreadState *tstate) +{ +#if defined(Py_DEBUG) + // This is called from PyEval_RestoreThread(). Similar + // to it, we need to ensure errno doesn't change. + int err = errno; +#endif + + _Py_EnsureTstateNotNULL(tstate); + if (current_fast_get(&_PyRuntime) != NULL) { + Py_FatalError("non-NULL old thread state"); + } + + _PyEval_AcquireLock(tstate); + + // XXX assert(tstate_is_alive(tstate)); + current_fast_set(&_PyRuntime, tstate); + tstate_activate(tstate); + + if (!tstate_try_attach(tstate)) { + // TODO: Once stop-the-world GC is implemented for --disable-gil builds + // this will need to wait until the GC completes. For now, this case + // should never happen. + Py_FatalError("thread attach failed"); + } + +#if defined(Py_DEBUG) + errno = err; +#endif +} + +void +_PyThreadState_Detach(PyThreadState *tstate) +{ + // XXX assert(tstate_is_alive(tstate) && tstate_is_bound(tstate)); + assert(tstate->state == _Py_THREAD_ATTACHED); + assert(tstate == current_fast_get(&_PyRuntime)); + tstate_set_detached(tstate); + tstate_deactivate(tstate); + current_fast_clear(&_PyRuntime); + _PyEval_ReleaseLock(tstate->interp, tstate); +} //---------- // other API @@ -1939,56 +2013,15 @@ PyThreadState_Get(void) return tstate; } - -static void -_swap_thread_states(_PyRuntimeState *runtime, - PyThreadState *oldts, PyThreadState *newts) -{ - // XXX Do this only if oldts != NULL? - current_fast_clear(runtime); - - if (oldts != NULL) { - // XXX assert(tstate_is_alive(oldts) && tstate_is_bound(oldts)); - tstate_deactivate(oldts); - } - - if (newts != NULL) { - // XXX assert(tstate_is_alive(newts)); - assert(tstate_is_bound(newts)); - current_fast_set(runtime, newts); - tstate_activate(newts); - } -} - -PyThreadState * -_PyThreadState_SwapNoGIL(PyThreadState *newts) -{ -#if defined(Py_DEBUG) - /* This can be called from PyEval_RestoreThread(). Similar - to it, we need to ensure errno doesn't change. - */ - int err = errno; -#endif - - PyThreadState *oldts = current_fast_get(&_PyRuntime); - _swap_thread_states(&_PyRuntime, oldts, newts); - -#if defined(Py_DEBUG) - errno = err; -#endif - return oldts; -} - PyThreadState * _PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts) { PyThreadState *oldts = current_fast_get(runtime); if (oldts != NULL) { - _PyEval_ReleaseLock(oldts->interp, oldts); + _PyThreadState_Detach(oldts); } - _swap_thread_states(runtime, oldts, newts); if (newts != NULL) { - _PyEval_AcquireLock(newts); + _PyThreadState_Attach(newts); } return oldts; } |