From 1828b9a8ec7a1a962374a81086eb4c899a152bb6 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 24 Apr 2025 16:59:16 -0400 Subject: [PATCH 01/85] Implement interpreter state reference counting. --- Include/cpython/pystate.h | 9 +++++++ Include/internal/pycore_interp_structs.h | 4 +++ Python/pylifecycle.c | 1 + Python/pystate.c | 33 ++++++++++++++++++++++-- 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index c562426767c2cb..86072a0b9c9403 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -265,3 +265,12 @@ PyAPI_FUNC(_PyFrameEvalFunction) _PyInterpreterState_GetEvalFrameFunc( PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( PyInterpreterState *interp, _PyFrameEvalFunction eval_frame); + +/* Similar to PyInterpreterState_Get(), but returns the interpreter with an + * incremented reference count. PyInterpreterState_Delete() won't delete the + * full interpreter structure until the reference is released by + * PyThreadState_Ensure() or PyInterpreterState_Release(). */ +PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_Hold(); + +/* Release a reference to an interpreter incremented by PyInterpreterState_Hold() */ +PyAPI_FUNC(void) PyInterpreterState_Release(PyInterpreterState *interp); diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index af6ee3ab48939f..4783b40e0cf86a 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -949,6 +949,10 @@ struct _is { # endif #endif + /* This prevents the interpreter from deleting. + See PEP 788 for the full specification. */ + Py_ssize_t refcount; + /* the initial PyInterpreterState.threads.head */ _PyThreadStateImpl _initial_thread; // _initial_thread should be the last field of PyInterpreterState. diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 1b9832bff17ba5..fb8ef7f48a8798 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2288,6 +2288,7 @@ new_interpreter(PyThreadState **tstate_p, } _PyInterpreterState_SetWhence(interp, whence); interp->_ready = 1; + interp->refcount = 1; // XXX Might new_interpreter() have been called without the GIL held? PyThreadState *save_tstate = _PyThreadState_GET(); diff --git a/Python/pystate.c b/Python/pystate.c index aba558279a657d..0bdb605a0e77d3 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -600,6 +600,15 @@ free_interpreter(PyInterpreterState *interp) } } +static void +decref_interpreter(PyInterpreterState *interp) +{ + assert(interp != NULL); + if (_Py_atomic_add_ssize(&interp->refcount, -1) == 1) { + free_interpreter(interp); + } +} + #ifndef NDEBUG static inline int check_interpreter_whence(long); #endif @@ -793,6 +802,7 @@ _PyInterpreterState_New(PyThreadState *tstate, PyInterpreterState **pinterp) HEAD_UNLOCK(runtime); if (interp != NULL) { + assert(interp->refcount == 1); free_interpreter(interp); } return status; @@ -1043,7 +1053,7 @@ PyInterpreterState_Delete(PyInterpreterState *interp) _PyObject_FiniState(interp); - free_interpreter(interp); + decref_interpreter(interp); } @@ -1079,7 +1089,7 @@ _PyInterpreterState_DeleteExceptMain(_PyRuntimeState *runtime) zapthreads(interp); PyInterpreterState *prev_interp = interp; interp = interp->next; - free_interpreter(prev_interp); + decref_interpreter(prev_interp); } HEAD_UNLOCK(runtime); @@ -3185,3 +3195,22 @@ _Py_GetMainConfig(void) } return _PyInterpreterState_GetConfig(interp); } + +PyInterpreterState * +PyInterpreterState_Hold(void) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + assert(_Py_atomic_load_ssize_relaxed(&interp->refcount) > 0); + _Py_atomic_add_ssize(&interp->refcount, 1); + return interp; +} + +void +PyInterpreterState_Release(PyInterpreterState *interp) +{ + PyThreadState *tstate = PyThreadState_Get(); + if (tstate->interp != interp) { + Py_FatalError("thread state has the wrong interpreter"); + } + decref_interpreter(interp); +} From d0895f9be8e267120ed8eea0da72a34df7a876f9 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 24 Apr 2025 17:12:27 -0400 Subject: [PATCH 02/85] Add a test for interpreter reference counts. --- Include/cpython/pystate.h | 5 ++++- Modules/_testcapimodule.c | 25 +++++++++++++++++++++++++ Python/pystate.c | 14 ++++++++++---- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 86072a0b9c9403..867d070a110283 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -270,7 +270,10 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( * incremented reference count. PyInterpreterState_Delete() won't delete the * full interpreter structure until the reference is released by * PyThreadState_Ensure() or PyInterpreterState_Release(). */ -PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_Hold(); +PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_Hold(void); /* Release a reference to an interpreter incremented by PyInterpreterState_Hold() */ PyAPI_FUNC(void) PyInterpreterState_Release(PyInterpreterState *interp); + +// Export for '_testcapi' shared extension +PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp); diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 3aa6e4c9e43a26..bde04705aa6826 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2546,6 +2546,30 @@ toggle_reftrace_printer(PyObject *ob, PyObject *arg) Py_RETURN_NONE; } +static PyObject * +test_interp_refcount(PyObject *self, PyObject *unused) +{ + PyThreadState *save = PyThreadState_Get(); + PyThreadState *tstate = Py_NewInterpreter(); + assert(tstate == PyThreadState_Get()); + PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate); + assert(interp != NULL); + + assert(_PyInterpreterState_Refcount(interp) == 1); + PyInterpreterState *held = PyInterpreterState_Hold(); + assert(_PyInterpreterState_Refcount(interp) == 2); + PyInterpreterState_Release(held); + assert(_PyInterpreterState_Refcount(interp) == 1); + + held = PyInterpreterState_Hold(); + Py_EndInterpreter(tstate); + PyThreadState_Swap(save); + assert(_PyInterpreterState_Refcount(interp) == 1); + PyInterpreterState_Release(held); + + Py_RETURN_NONE; +} + static PyMethodDef TestMethods[] = { {"set_errno", set_errno, METH_VARARGS}, {"test_config", test_config, METH_NOARGS}, @@ -2640,6 +2664,7 @@ static PyMethodDef TestMethods[] = { {"test_atexit", test_atexit, METH_NOARGS}, {"code_offset_to_line", _PyCFunction_CAST(code_offset_to_line), METH_FASTCALL}, {"toggle_reftrace_printer", toggle_reftrace_printer, METH_O}, + {"test_interp_refcount", test_interp_refcount, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/pystate.c b/Python/pystate.c index 0bdb605a0e77d3..cd37b12c74deac 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3196,6 +3196,15 @@ _Py_GetMainConfig(void) return _PyInterpreterState_GetConfig(interp); } +Py_ssize_t +_PyInterpreterState_Refcount(PyInterpreterState *interp) +{ + assert(interp != NULL); + Py_ssize_t refcount = _Py_atomic_load_ssize_relaxed(&interp->refcount); + assert(refcount > 0); + return refcount; +} + PyInterpreterState * PyInterpreterState_Hold(void) { @@ -3208,9 +3217,6 @@ PyInterpreterState_Hold(void) void PyInterpreterState_Release(PyInterpreterState *interp) { - PyThreadState *tstate = PyThreadState_Get(); - if (tstate->interp != interp) { - Py_FatalError("thread state has the wrong interpreter"); - } + assert(interp != NULL); decref_interpreter(interp); } From 5c3ee8da16ec9a7c245713b2ff40d6fdfeeb8f96 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 24 Apr 2025 20:00:27 -0400 Subject: [PATCH 03/85] Add thread state daemon-ness that doesn't work yet. --- Include/cpython/pystate.h | 3 +++ Python/pystate.c | 20 +++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 867d070a110283..9d3a5b85d15e7f 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -210,6 +210,9 @@ struct _ts { */ PyObject *threading_local_sentinel; _PyRemoteDebuggerSupport remote_debugger_support; + + /* Whether this thread hangs when the interpreter is finalizing. */ + uint8_t daemon; }; # define Py_C_RECURSION_LIMIT 5000 diff --git a/Python/pystate.c b/Python/pystate.c index cd37b12c74deac..851a0ac8f80c1e 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1612,6 +1612,7 @@ new_threadstate(PyInterpreterState *interp, int whence) return NULL; } #endif + ((PyThreadState *)tstate)->daemon = 1; /* We serialize concurrent creation to protect global state. */ HEAD_LOCK(interp->runtime); @@ -2129,7 +2130,7 @@ tstate_wait_attach(PyThreadState *tstate) _PyParkingLot_Park(&tstate->state, &state, sizeof(tstate->state), /*timeout=*/-1, NULL, /*detach=*/0); } - else if (state == _Py_THREAD_SHUTTING_DOWN) { + else if (state == _Py_THREAD_SHUTTING_DOWN && tstate->daemon) { // We're shutting down, so we can't attach. _PyThreadState_HangThread(tstate); } @@ -3069,6 +3070,9 @@ _PyThreadState_CheckConsistency(PyThreadState *tstate) int _PyThreadState_MustExit(PyThreadState *tstate) { + if (!tstate->daemon) { + return 0; + } int state = _Py_atomic_load_int_relaxed(&tstate->state); return state == _Py_THREAD_SHUTTING_DOWN; } @@ -3220,3 +3224,17 @@ PyInterpreterState_Release(PyInterpreterState *interp) assert(interp != NULL); decref_interpreter(interp); } + +int +PyThreadState_SetDaemon(int daemon) +{ + PyThreadState *tstate = PyThreadState_Get(); + if (daemon != 0 && daemon != 1) { + Py_FatalError("daemon must be 0 or 1"); + } + if (tstate->daemon == daemon) { + return 0; + } + tstate->daemon = daemon; + return 1; +} From 7b9ac5957e62888ac4c18b480ed7e66973e69db2 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 24 Apr 2025 20:30:40 -0400 Subject: [PATCH 04/85] Add untested implementation of non-daemon native threads. --- Include/internal/pycore_interp_structs.h | 6 ++++++ Python/pylifecycle.c | 26 ++++++++++++++++++++++++ Python/pystate.c | 19 +++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 4783b40e0cf86a..07d4a02a76985c 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -792,6 +792,12 @@ struct _is { or the size specified by the THREAD_STACK_SIZE macro. */ /* Used in Python/thread.c. */ size_t stacksize; + + struct _Py_finalizing_threads { + Py_ssize_t countdown; + PyEvent finished; + PyMutex mutex; + } finalizing; } threads; /* Reference to the _PyRuntime global variable. This field exists diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index fb8ef7f48a8798..4a62bb92b94237 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -13,6 +13,7 @@ #include "pycore_freelist.h" // _PyObject_ClearFreeLists() #include "pycore_global_objects_fini_generated.h" // _PyStaticObjects_CheckRefcnt() #include "pycore_initconfig.h" // _PyStatus_OK() +#include "pycore_interp_structs.h" #include "pycore_long.h" // _PyLong_InitTypes() #include "pycore_object.h" // _PyDebug_PrintTotalRefs() #include "pycore_obmalloc.h" // _PyMem_init_obmalloc() @@ -96,6 +97,7 @@ static PyStatus init_android_streams(PyThreadState *tstate); static PyStatus init_apple_streams(PyThreadState *tstate); #endif static void wait_for_thread_shutdown(PyThreadState *tstate); +static void wait_for_native_shutdown(PyInterpreterState *interp); static void finalize_subinterpreters(void); static void call_ll_exitfuncs(_PyRuntimeState *runtime); @@ -2012,6 +2014,9 @@ _Py_Finalize(_PyRuntimeState *runtime) // Wrap up existing "threading"-module-created, non-daemon threads. wait_for_thread_shutdown(tstate); + // Wrap up non-daemon native threads + wait_for_native_shutdown(tstate->interp); + // Make any remaining pending calls. _Py_FinishPendingCalls(tstate); @@ -2429,6 +2434,9 @@ Py_EndInterpreter(PyThreadState *tstate) // Wrap up existing "threading"-module-created, non-daemon threads. wait_for_thread_shutdown(tstate); + // Wrap up non-daemon native threads + wait_for_native_shutdown(tstate->interp); + // Make any remaining pending calls. _Py_FinishPendingCalls(tstate); @@ -3455,6 +3463,24 @@ wait_for_thread_shutdown(PyThreadState *tstate) Py_DECREF(threading); } +/* Wait for all non-daemon native threads to finish. + See PEP 788. */ +static void +wait_for_native_shutdown(PyInterpreterState *interp) +{ + assert(interp != NULL); + struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; + PyMutex_Lock(&finalizing->mutex); + if (finalizing->countdown == 0) { + PyMutex_Unlock(&finalizing->mutex); + return; + } + PyMutex_Unlock(&finalizing->mutex); + + PyEvent_Wait(&finalizing->finished); + assert(finalizing->countdown == 0); +} + int Py_AtExit(void (*func)(void)) { struct _atexit_runtime_state *state = &_PyRuntime.atexit; diff --git a/Python/pystate.c b/Python/pystate.c index 851a0ac8f80c1e..b2a0264f4b8a93 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1851,6 +1851,13 @@ tstate_delete_common(PyThreadState *tstate, int release_gil) decrement_stoptheworld_countdown(&runtime->stoptheworld); } } + if (tstate->daemon == 0 + && tstate != (PyThreadState *)&interp->_initial_thread) { + struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; + if (--finalizing->countdown == 0) { + _PyEvent_Notify(&finalizing->finished); + } + } #if defined(Py_REF_DEBUG) && defined(Py_GIL_DISABLED) // Add our portion of the total refcount to the interpreter's total. @@ -3232,9 +3239,21 @@ PyThreadState_SetDaemon(int daemon) if (daemon != 0 && daemon != 1) { Py_FatalError("daemon must be 0 or 1"); } + PyInterpreterState *interp = tstate->interp; + assert(interp != NULL); if (tstate->daemon == daemon) { return 0; } + + struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; + PyMutex_Lock(&finalizing->mutex); + if (_PyEvent_IsSet(&finalizing->finished)) { + /* Native threads have already finalized */ + PyMutex_Unlock(&finalizing->mutex); + return -1; + } + ++finalizing->countdown; + PyMutex_Unlock(&finalizing->mutex); tstate->daemon = daemon; return 1; } From 0868c156f381745846f197e84238a0a53114a3a1 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 24 Apr 2025 20:55:12 -0400 Subject: [PATCH 05/85] Add a test for PyThreadState_SetDaemon(). --- Include/cpython/pystate.h | 2 ++ Programs/_testembed.c | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 9d3a5b85d15e7f..63ad2b8ffbf0d6 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -280,3 +280,5 @@ PyAPI_FUNC(void) PyInterpreterState_Release(PyInterpreterState *interp); // Export for '_testcapi' shared extension PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp); + +PyAPI_FUNC(int) PyThreadState_SetDaemon(int daemon); diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 6f6d0cae58010e..57717605d5c256 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2341,6 +2341,45 @@ test_get_incomplete_frame(void) return result; } +static void +non_daemon_native(void *arg) +{ + PyEvent *event = (PyEvent *)arg; + PyThreadState *tstate = PyThreadState_New(PyInterpreterState_Main()); + PyThreadState_Swap(tstate); + int res = PyThreadState_SetDaemon(0); + assert(res == 1); + _PyEvent_Notify(event); + const char *code = "import time\n" + "time.sleep(0.2)\n" + "def fib(n):\n" + " if n <= 1:\n" + " return n\n" + " else:\n" + " return fib(n - 1) + fib(n - 2)\n" + "fib(10)"; + res = PyRun_SimpleString(code); + assert(res == 0); + PyThreadState_Clear(tstate); + PyThreadState_Swap(NULL); + PyThreadState_Delete(tstate); +} + +static int +test_non_daemon_native_thread(void) +{ + _testembed_Py_InitializeFromConfig(); + PyThread_handle_t handle; + PyThread_ident_t ident; + PyEvent event; + if (PyThread_start_joinable_thread(non_daemon_native, &event, + &ident, &handle) < 0) { + return -1; + } + PyEvent_Wait(&event); + Py_Finalize(); + return 0; +} /* ********************************************************* * List of test cases and the function that implements it. @@ -2431,6 +2470,7 @@ static struct TestCase TestCases[] = { {"test_frozenmain", test_frozenmain}, #endif {"test_get_incomplete_frame", test_get_incomplete_frame}, + {"test_non_daemon_native_thread", test_non_daemon_native_thread}, {NULL, NULL} }; From e9ea644212620d96f046a0764747da5760a33a04 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 25 Apr 2025 13:22:08 +0000 Subject: [PATCH 06/85] Add untested implementation of Ensure()/Release() that probably doesn't work and isn't thread-safe. --- Include/cpython/pystate.h | 12 ++++ Python/pystate.c | 119 +++++++++++++++++++++++++++++++++----- 2 files changed, 116 insertions(+), 15 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 63ad2b8ffbf0d6..f20ad0fd507bf9 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -63,6 +63,12 @@ typedef struct _stack_chunk { PyObject * data[1]; /* Variable sized */ } _PyStackChunk; +typedef struct _ensured_tstate { + struct _ensured_tstate *next; + PyThreadState *prior_tstate; + uint8_t was_daemon; +} _Py_ensured_tstate; + struct _ts { /* See Python/ceval.c for comments explaining most fields */ @@ -213,6 +219,8 @@ struct _ts { /* Whether this thread hangs when the interpreter is finalizing. */ uint8_t daemon; + + _Py_ensured_tstate *ensured; }; # define Py_C_RECURSION_LIMIT 5000 @@ -282,3 +290,7 @@ PyAPI_FUNC(void) PyInterpreterState_Release(PyInterpreterState *interp); PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp); PyAPI_FUNC(int) PyThreadState_SetDaemon(int daemon); + +PyAPI_FUNC(int) PyThreadState_Ensure(PyInterpreterState *interp); + +PyAPI_FUNC(void) PyThreadState_Release(void); diff --git a/Python/pystate.c b/Python/pystate.c index b2a0264f4b8a93..80a84dca1d69cd 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -600,13 +600,16 @@ free_interpreter(PyInterpreterState *interp) } } -static void +static Py_ssize_t decref_interpreter(PyInterpreterState *interp) { assert(interp != NULL); - if (_Py_atomic_add_ssize(&interp->refcount, -1) == 1) { + Py_ssize_t old_refcnt = _Py_atomic_add_ssize(&interp->refcount, -1); + if (old_refcnt == 1) { free_interpreter(interp); } + + return old_refcnt; } #ifndef NDEBUG @@ -1817,6 +1820,16 @@ PyThreadState_Clear(PyThreadState *tstate) static void decrement_stoptheworld_countdown(struct _stoptheworld_state *stw); +static void +decrement_daemon_count(PyInterpreterState *interp) +{ + assert(interp != NULL); + struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; + if (--finalizing->countdown == 0) { + _PyEvent_Notify(&finalizing->finished); + } +} + /* Common code for PyThreadState_Delete() and PyThreadState_DeleteCurrent() */ static void tstate_delete_common(PyThreadState *tstate, int release_gil) @@ -1853,10 +1866,7 @@ tstate_delete_common(PyThreadState *tstate, int release_gil) } if (tstate->daemon == 0 && tstate != (PyThreadState *)&interp->_initial_thread) { - struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; - if (--finalizing->countdown == 0) { - _PyEvent_Notify(&finalizing->finished); - } + decrement_daemon_count(interp); } #if defined(Py_REF_DEBUG) && defined(Py_GIL_DISABLED) @@ -3232,6 +3242,26 @@ PyInterpreterState_Release(PyInterpreterState *interp) decref_interpreter(interp); } +static int +tstate_set_daemon(PyThreadState *tstate, PyInterpreterState *interp, int daemon) +{ + assert(tstate != NULL); + assert(interp != NULL); + assert(tstate->interp == interp); + assert(daemon == 1 || daemon == 0); + struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; + PyMutex_Lock(&finalizing->mutex); + if (_PyEvent_IsSet(&finalizing->finished)) { + /* Native threads have already finalized */ + PyMutex_Unlock(&finalizing->mutex); + return -1; + } + ++finalizing->countdown; + PyMutex_Unlock(&finalizing->mutex); + tstate->daemon = daemon; + return 1; +} + int PyThreadState_SetDaemon(int daemon) { @@ -3241,19 +3271,78 @@ PyThreadState_SetDaemon(int daemon) } PyInterpreterState *interp = tstate->interp; assert(interp != NULL); + if (tstate == &interp->_initial_thread) { + Py_FatalError("thread cannot be the main thread"); + } if (tstate->daemon == daemon) { return 0; } - struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; - PyMutex_Lock(&finalizing->mutex); - if (_PyEvent_IsSet(&finalizing->finished)) { - /* Native threads have already finalized */ - PyMutex_Unlock(&finalizing->mutex); + return tstate_set_daemon(tstate, interp, daemon); +} + +int +PyThreadState_Ensure(PyInterpreterState *interp) +{ + assert(interp != NULL); + _Py_ensured_tstate *entry = PyMem_RawMalloc(sizeof(_Py_ensured_tstate)); + if (entry == NULL) { + decref_interpreter(interp); return -1; } - ++finalizing->countdown; - PyMutex_Unlock(&finalizing->mutex); - tstate->daemon = daemon; - return 1; + PyThreadState *save = _PyThreadState_GET(); + if (save != NULL && save->interp == interp) { + Py_ssize_t refcnt = decref_interpreter(interp); + assert(refcnt > 1); + entry->was_daemon = save->daemon; + entry->next = save->ensured; + entry->prior_tstate = NULL; + if (tstate_set_daemon(save, interp, 0) < 0) { + PyMem_RawFree(entry); + return -1; + } + save->ensured = entry; + return 0; + } + + PyThreadState *tstate = PyThreadState_New(interp); + decref_interpreter(interp); + if (tstate == NULL) { + PyMem_RawFree(entry); + return -1; + } + if (tstate_set_daemon(tstate, interp, 0) < 0) { + PyMem_RawFree(entry); + return -1; + } + entry->was_daemon = 0; + entry->prior_tstate = save; + entry->next = NULL; + tstate->ensured = entry; + + return 0; +} + +void +PyThreadState_Release(void) +{ + PyThreadState *tstate = _PyThreadState_GET(); + _Py_EnsureTstateNotNULL(tstate); + _Py_ensured_tstate *ensured = tstate->ensured; + if (ensured == NULL) { + Py_FatalError("PyThreadState_Release() called without PyThreadState_Ensure()"); + } + if (ensured->prior_tstate != NULL) { + assert(ensured->was_daemon == 0); + PyThreadState_Clear(tstate); + PyThreadState_Swap(ensured->prior_tstate); + PyMem_RawFree(ensured); + PyThreadState_Delete(tstate); + return; + } + + decrement_daemon_count(tstate->interp); + tstate->ensured = ensured->next; + tstate->daemon = ensured->was_daemon; + PyMem_RawFree(ensured); } From 0ebdca45b386f1adacdf3419665edbc6db0d315c Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 25 Apr 2025 13:27:25 +0000 Subject: [PATCH 07/85] Change some comments. --- Python/pylifecycle.c | 1 + Python/pystate.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 4a62bb92b94237..5d4a5cf576cc4b 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -3472,6 +3472,7 @@ wait_for_native_shutdown(PyInterpreterState *interp) struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; PyMutex_Lock(&finalizing->mutex); if (finalizing->countdown == 0) { + // Nothing to do. PyMutex_Unlock(&finalizing->mutex); return; } diff --git a/Python/pystate.c b/Python/pystate.c index 80a84dca1d69cd..57c9c85263b376 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3292,8 +3292,9 @@ PyThreadState_Ensure(PyInterpreterState *interp) } PyThreadState *save = _PyThreadState_GET(); if (save != NULL && save->interp == interp) { + /* We already have a thread state that matches the + interpreter. */ Py_ssize_t refcnt = decref_interpreter(interp); - assert(refcnt > 1); entry->was_daemon = save->daemon; entry->next = save->ensured; entry->prior_tstate = NULL; From 40989de0553cda4882acc9efa4cd603767b48233 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 25 Apr 2025 13:36:47 +0000 Subject: [PATCH 08/85] Add a test that I'm sure doesn't work. --- Programs/_testembed.c | 57 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 57717605d5c256..53077e033a17d7 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2341,6 +2341,15 @@ test_get_incomplete_frame(void) return result; } +const char *THREAD_CODE = "import time\n" + "time.sleep(0.2)\n" + "def fib(n):\n" + " if n <= 1:\n" + " return n\n" + " else:\n" + " return fib(n - 1) + fib(n - 2)\n" + "fib(10)"; + static void non_daemon_native(void *arg) { @@ -2350,15 +2359,7 @@ non_daemon_native(void *arg) int res = PyThreadState_SetDaemon(0); assert(res == 1); _PyEvent_Notify(event); - const char *code = "import time\n" - "time.sleep(0.2)\n" - "def fib(n):\n" - " if n <= 1:\n" - " return n\n" - " else:\n" - " return fib(n - 1) + fib(n - 2)\n" - "fib(10)"; - res = PyRun_SimpleString(code); + res = PyRun_SimpleString(THREAD_CODE); assert(res == 0); PyThreadState_Clear(tstate); PyThreadState_Swap(NULL); @@ -2371,7 +2372,7 @@ test_non_daemon_native_thread(void) _testembed_Py_InitializeFromConfig(); PyThread_handle_t handle; PyThread_ident_t ident; - PyEvent event; + PyEvent event = {0}; if (PyThread_start_joinable_thread(non_daemon_native, &event, &ident, &handle) < 0) { return -1; @@ -2381,6 +2382,41 @@ test_non_daemon_native_thread(void) return 0; } +typedef struct { + PyInterpreterState *interp; + PyEvent *event; +} ThreadData; + +static void +do_tstate_ensure(void *arg) +{ + ThreadData *data = (ThreadData *)arg; + PyEvent *event = data->event; + int res = PyThreadState_Ensure(data->interp); + assert(res == 0); + _PyEvent_Notify(event); + res = PyRun_SimpleString(THREAD_CODE); + assert(res == 0); + PyThreadState_Release(); +} + +static int +test_thread_state_ensure(void) +{ + _testembed_Py_InitializeFromConfig(); + PyThread_handle_t handle; + PyThread_ident_t ident; + PyEvent event = {0}; + ThreadData data = { PyInterpreterState_Hold(), &event }; + if (PyThread_start_joinable_thread(non_daemon_native, &data, + &ident, &handle) < 0) { + return -1; + } + PyEvent_Wait(&event); + Py_Finalize(); + return 0; +} + /* ********************************************************* * List of test cases and the function that implements it. * @@ -2471,6 +2507,7 @@ static struct TestCase TestCases[] = { #endif {"test_get_incomplete_frame", test_get_incomplete_frame}, {"test_non_daemon_native_thread", test_non_daemon_native_thread}, + {"test_thread_state_ensure", test_thread_state_ensure}, {NULL, NULL} }; From d501f35fdea36a6b3ce41b88fee3fad959772357 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 25 Apr 2025 19:44:05 -0400 Subject: [PATCH 09/85] Use the interpreter's reference count and native thread countdown as one. --- Include/internal/pycore_interp_structs.h | 4 -- Programs/_testembed.c | 46 +++--------------- Python/pylifecycle.c | 4 +- Python/pystate.c | 59 +++++++++--------------- 4 files changed, 29 insertions(+), 84 deletions(-) diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 07d4a02a76985c..33caf1edfa82c0 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -955,10 +955,6 @@ struct _is { # endif #endif - /* This prevents the interpreter from deleting. - See PEP 788 for the full specification. */ - Py_ssize_t refcount; - /* the initial PyInterpreterState.threads.head */ _PyThreadStateImpl _initial_thread; // _initial_thread should be the last field of PyInterpreterState. diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 53077e033a17d7..8fd8f7ecb1c99b 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2350,54 +2350,21 @@ const char *THREAD_CODE = "import time\n" " return fib(n - 1) + fib(n - 2)\n" "fib(10)"; -static void -non_daemon_native(void *arg) -{ - PyEvent *event = (PyEvent *)arg; - PyThreadState *tstate = PyThreadState_New(PyInterpreterState_Main()); - PyThreadState_Swap(tstate); - int res = PyThreadState_SetDaemon(0); - assert(res == 1); - _PyEvent_Notify(event); - res = PyRun_SimpleString(THREAD_CODE); - assert(res == 0); - PyThreadState_Clear(tstate); - PyThreadState_Swap(NULL); - PyThreadState_Delete(tstate); -} - -static int -test_non_daemon_native_thread(void) -{ - _testembed_Py_InitializeFromConfig(); - PyThread_handle_t handle; - PyThread_ident_t ident; - PyEvent event = {0}; - if (PyThread_start_joinable_thread(non_daemon_native, &event, - &ident, &handle) < 0) { - return -1; - } - PyEvent_Wait(&event); - Py_Finalize(); - return 0; -} - typedef struct { PyInterpreterState *interp; - PyEvent *event; + int done; } ThreadData; static void do_tstate_ensure(void *arg) { ThreadData *data = (ThreadData *)arg; - PyEvent *event = data->event; int res = PyThreadState_Ensure(data->interp); assert(res == 0); - _PyEvent_Notify(event); res = PyRun_SimpleString(THREAD_CODE); assert(res == 0); PyThreadState_Release(); + data->done = 1; } static int @@ -2406,14 +2373,14 @@ test_thread_state_ensure(void) _testembed_Py_InitializeFromConfig(); PyThread_handle_t handle; PyThread_ident_t ident; - PyEvent event = {0}; - ThreadData data = { PyInterpreterState_Hold(), &event }; - if (PyThread_start_joinable_thread(non_daemon_native, &data, + ThreadData data = { PyInterpreterState_Hold() }; + if (PyThread_start_joinable_thread(do_tstate_ensure, &data, &ident, &handle) < 0) { + PyInterpreterState_Release(data.interp); return -1; } - PyEvent_Wait(&event); Py_Finalize(); + assert(data.done == 1); return 0; } @@ -2506,7 +2473,6 @@ static struct TestCase TestCases[] = { {"test_frozenmain", test_frozenmain}, #endif {"test_get_incomplete_frame", test_get_incomplete_frame}, - {"test_non_daemon_native_thread", test_non_daemon_native_thread}, {"test_thread_state_ensure", test_thread_state_ensure}, {NULL, NULL} diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 5d4a5cf576cc4b..a9ce30be38453b 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2293,7 +2293,6 @@ new_interpreter(PyThreadState **tstate_p, } _PyInterpreterState_SetWhence(interp, whence); interp->_ready = 1; - interp->refcount = 1; // XXX Might new_interpreter() have been called without the GIL held? PyThreadState *save_tstate = _PyThreadState_GET(); @@ -3471,7 +3470,7 @@ wait_for_native_shutdown(PyInterpreterState *interp) assert(interp != NULL); struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; PyMutex_Lock(&finalizing->mutex); - if (finalizing->countdown == 0) { + if (_Py_atomic_load_ssize_relaxed(&finalizing->countdown) == 0) { // Nothing to do. PyMutex_Unlock(&finalizing->mutex); return; @@ -3479,7 +3478,6 @@ wait_for_native_shutdown(PyInterpreterState *interp) PyMutex_Unlock(&finalizing->mutex); PyEvent_Wait(&finalizing->finished); - assert(finalizing->countdown == 0); } int Py_AtExit(void (*func)(void)) diff --git a/Python/pystate.c b/Python/pystate.c index 57c9c85263b376..161bc49a793d5d 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -600,18 +600,6 @@ free_interpreter(PyInterpreterState *interp) } } -static Py_ssize_t -decref_interpreter(PyInterpreterState *interp) -{ - assert(interp != NULL); - Py_ssize_t old_refcnt = _Py_atomic_add_ssize(&interp->refcount, -1); - if (old_refcnt == 1) { - free_interpreter(interp); - } - - return old_refcnt; -} - #ifndef NDEBUG static inline int check_interpreter_whence(long); #endif @@ -805,7 +793,6 @@ _PyInterpreterState_New(PyThreadState *tstate, PyInterpreterState **pinterp) HEAD_UNLOCK(runtime); if (interp != NULL) { - assert(interp->refcount == 1); free_interpreter(interp); } return status; @@ -1056,7 +1043,7 @@ PyInterpreterState_Delete(PyInterpreterState *interp) _PyObject_FiniState(interp); - decref_interpreter(interp); + free_interpreter(interp); } @@ -1092,7 +1079,7 @@ _PyInterpreterState_DeleteExceptMain(_PyRuntimeState *runtime) zapthreads(interp); PyInterpreterState *prev_interp = interp; interp = interp->next; - decref_interpreter(prev_interp); + free_interpreter(prev_interp); } HEAD_UNLOCK(runtime); @@ -1825,7 +1812,7 @@ decrement_daemon_count(PyInterpreterState *interp) { assert(interp != NULL); struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; - if (--finalizing->countdown == 0) { + if (_Py_atomic_load_ssize_relaxed(&finalizing->countdown) == 1) { _PyEvent_Notify(&finalizing->finished); } } @@ -3221,8 +3208,7 @@ Py_ssize_t _PyInterpreterState_Refcount(PyInterpreterState *interp) { assert(interp != NULL); - Py_ssize_t refcount = _Py_atomic_load_ssize_relaxed(&interp->refcount); - assert(refcount > 0); + Py_ssize_t refcount = _Py_atomic_load_ssize_relaxed(&interp->threads.finalizing.countdown); return refcount; } @@ -3230,8 +3216,8 @@ PyInterpreterState * PyInterpreterState_Hold(void) { PyInterpreterState *interp = PyInterpreterState_Get(); - assert(_Py_atomic_load_ssize_relaxed(&interp->refcount) > 0); - _Py_atomic_add_ssize(&interp->refcount, 1); + assert(_Py_atomic_load_ssize_relaxed(&interp->threads.finalizing.countdown) >= 0); + _Py_atomic_add_ssize(&interp->threads.finalizing.countdown, 1); return interp; } @@ -3239,7 +3225,7 @@ void PyInterpreterState_Release(PyInterpreterState *interp) { assert(interp != NULL); - decref_interpreter(interp); + decrement_daemon_count(interp); } static int @@ -3251,12 +3237,16 @@ tstate_set_daemon(PyThreadState *tstate, PyInterpreterState *interp, int daemon) assert(daemon == 1 || daemon == 0); struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; PyMutex_Lock(&finalizing->mutex); + if (_Py_atomic_load_ssize_relaxed(&finalizing->countdown) == 0) { + PyMutex_Unlock(&finalizing->mutex); + return -1; + } if (_PyEvent_IsSet(&finalizing->finished)) { /* Native threads have already finalized */ PyMutex_Unlock(&finalizing->mutex); return -1; } - ++finalizing->countdown; + _Py_atomic_add_ssize(&finalizing->countdown, 1); PyMutex_Unlock(&finalizing->mutex); tstate->daemon = daemon; return 1; @@ -3271,7 +3261,7 @@ PyThreadState_SetDaemon(int daemon) } PyInterpreterState *interp = tstate->interp; assert(interp != NULL); - if (tstate == &interp->_initial_thread) { + if (tstate == (PyThreadState *)&interp->_initial_thread) { Py_FatalError("thread cannot be the main thread"); } if (tstate->daemon == daemon) { @@ -3287,39 +3277,31 @@ PyThreadState_Ensure(PyInterpreterState *interp) assert(interp != NULL); _Py_ensured_tstate *entry = PyMem_RawMalloc(sizeof(_Py_ensured_tstate)); if (entry == NULL) { - decref_interpreter(interp); + decrement_daemon_count(interp); return -1; } PyThreadState *save = _PyThreadState_GET(); if (save != NULL && save->interp == interp) { - /* We already have a thread state that matches the - interpreter. */ - Py_ssize_t refcnt = decref_interpreter(interp); entry->was_daemon = save->daemon; entry->next = save->ensured; entry->prior_tstate = NULL; - if (tstate_set_daemon(save, interp, 0) < 0) { - PyMem_RawFree(entry); - return -1; - } save->ensured = entry; + // Setting 'daemon' to 0 passes off the interpreter's reference + save->daemon = 0; return 0; } PyThreadState *tstate = PyThreadState_New(interp); - decref_interpreter(interp); if (tstate == NULL) { PyMem_RawFree(entry); return -1; } - if (tstate_set_daemon(tstate, interp, 0) < 0) { - PyMem_RawFree(entry); - return -1; - } + tstate->daemon = 0; entry->was_daemon = 0; entry->prior_tstate = save; entry->next = NULL; tstate->ensured = entry; + PyThreadState_Swap(tstate); return 0; } @@ -3342,8 +3324,11 @@ PyThreadState_Release(void) return; } - decrement_daemon_count(tstate->interp); tstate->ensured = ensured->next; tstate->daemon = ensured->was_daemon; PyMem_RawFree(ensured); + PyThreadState_Clear(tstate); + PyThreadState_Swap(NULL); + PyInterpreterState *interp = tstate->interp; + PyThreadState_Delete(tstate); } From c5ec89ca8759cd3edf82e335706218116a6e0236 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 25 Apr 2025 20:09:07 -0400 Subject: [PATCH 10/85] Fix the countdown decrement. --- Programs/_testembed.c | 6 ++++++ Python/pystate.c | 14 +++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 8fd8f7ecb1c99b..a65238138ec949 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2361,7 +2361,13 @@ do_tstate_ensure(void *arg) ThreadData *data = (ThreadData *)arg; int res = PyThreadState_Ensure(data->interp); assert(res == 0); + PyThreadState_Ensure(PyInterpreterState_Hold()); + PyThreadState_Ensure(PyInterpreterState_Hold()); + PyThreadState_Ensure(PyInterpreterState_Hold()); res = PyRun_SimpleString(THREAD_CODE); + PyThreadState_Release(); + PyThreadState_Release(); + PyThreadState_Release(); assert(res == 0); PyThreadState_Release(); data->done = 1; diff --git a/Python/pystate.c b/Python/pystate.c index 161bc49a793d5d..8e5277fa6cb981 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1812,7 +1812,7 @@ decrement_daemon_count(PyInterpreterState *interp) { assert(interp != NULL); struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; - if (_Py_atomic_load_ssize_relaxed(&finalizing->countdown) == 1) { + if (_Py_atomic_add_ssize(&finalizing->countdown, -1) == 1) { _PyEvent_Notify(&finalizing->finished); } } @@ -3324,11 +3324,15 @@ PyThreadState_Release(void) return; } + PyInterpreterState *interp = tstate->interp; tstate->ensured = ensured->next; tstate->daemon = ensured->was_daemon; PyMem_RawFree(ensured); - PyThreadState_Clear(tstate); - PyThreadState_Swap(NULL); - PyInterpreterState *interp = tstate->interp; - PyThreadState_Delete(tstate); + if (tstate->ensured == NULL) { + PyThreadState_Clear(tstate); + PyThreadState_Swap(NULL); + PyThreadState_Delete(tstate); + } else { + decrement_daemon_count(tstate->interp); + } } From 4e1f599ca19d68120773dd4d50889e7a21490c06 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 25 Apr 2025 20:12:01 -0400 Subject: [PATCH 11/85] Remove unused variable. --- Python/pystate.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Python/pystate.c b/Python/pystate.c index 8e5277fa6cb981..90a2a27da65fd3 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3324,7 +3324,6 @@ PyThreadState_Release(void) return; } - PyInterpreterState *interp = tstate->interp; tstate->ensured = ensured->next; tstate->daemon = ensured->was_daemon; PyMem_RawFree(ensured); From 3127a3fe2782e6c26cfdf33b459f357006091fb7 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 25 Apr 2025 20:16:28 -0400 Subject: [PATCH 12/85] Test for PyGILState_Ensure() --- Programs/_testembed.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Programs/_testembed.c b/Programs/_testembed.c index a65238138ec949..a9effc43735064 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2363,9 +2363,11 @@ do_tstate_ensure(void *arg) assert(res == 0); PyThreadState_Ensure(PyInterpreterState_Hold()); PyThreadState_Ensure(PyInterpreterState_Hold()); + PyGILState_STATE gstate = PyGILState_Ensure(); PyThreadState_Ensure(PyInterpreterState_Hold()); res = PyRun_SimpleString(THREAD_CODE); PyThreadState_Release(); + PyGILState_Release(gstate); PyThreadState_Release(); PyThreadState_Release(); assert(res == 0); From 82b5b9f4d937bb080eb0deb68fc4983a66e755cf Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 25 Apr 2025 20:49:12 -0400 Subject: [PATCH 13/85] Fix the test for the new reference counting. --- Modules/_testcapimodule.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index bde04705aa6826..2e7a432e478599 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2555,17 +2555,18 @@ test_interp_refcount(PyObject *self, PyObject *unused) PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate); assert(interp != NULL); - assert(_PyInterpreterState_Refcount(interp) == 1); + assert(_PyInterpreterState_Refcount(interp) == 0); PyInterpreterState *held = PyInterpreterState_Hold(); - assert(_PyInterpreterState_Refcount(interp) == 2); - PyInterpreterState_Release(held); assert(_PyInterpreterState_Refcount(interp) == 1); + PyInterpreterState_Release(held); + assert(_PyInterpreterState_Refcount(interp) == 0); held = PyInterpreterState_Hold(); Py_EndInterpreter(tstate); PyThreadState_Swap(save); assert(_PyInterpreterState_Refcount(interp) == 1); PyInterpreterState_Release(held); + assert(_PyInterpreterState_Refcount(interp) == 0); Py_RETURN_NONE; } From fda9886edd26ed7a7e1946f5030cfe96bcbbe2a6 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 26 Apr 2025 11:52:29 -0400 Subject: [PATCH 14/85] Add PyInterpreterState_Lookup() --- Include/cpython/pystate.h | 2 ++ Python/pystate.c | 26 +++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index f20ad0fd507bf9..9de35fd0ac807a 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -283,6 +283,8 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( * PyThreadState_Ensure() or PyInterpreterState_Release(). */ PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_Hold(void); +PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_Lookup(int64_t interp_id); + /* Release a reference to an interpreter incremented by PyInterpreterState_Hold() */ PyAPI_FUNC(void) PyInterpreterState_Release(PyInterpreterState *interp); diff --git a/Python/pystate.c b/Python/pystate.c index 90a2a27da65fd3..0d566abee603af 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3074,9 +3074,6 @@ _PyThreadState_CheckConsistency(PyThreadState *tstate) int _PyThreadState_MustExit(PyThreadState *tstate) { - if (!tstate->daemon) { - return 0; - } int state = _Py_atomic_load_int_relaxed(&tstate->state); return state == _Py_THREAD_SHUTTING_DOWN; } @@ -3221,6 +3218,29 @@ PyInterpreterState_Hold(void) return interp; } +PyInterpreterState * +PyInterpreterState_Lookup(int64_t interp_id) +{ + PyInterpreterState *interp = _PyInterpreterState_LookUpID(interp_id); + if (interp == NULL) { + return NULL; + } + HEAD_LOCK(&_PyRuntime); // Prevent deletion + struct _Py_finalizing_threads finalizing = interp->threads.finalizing; + PyMutex *mutex = &finalizing.mutex; + PyMutex_Lock(mutex); // Synchronize TOCTOU with the event flag + if (_PyEvent_IsSet(&finalizing.finished)) { + /* Interpreter has already finished threads */ + interp = NULL; + } else { + _Py_atomic_add_ssize(&finalizing.countdown, 1); + } + PyMutex_Unlock(mutex); + HEAD_UNLOCK(&_PyRuntime); + + return interp; +} + void PyInterpreterState_Release(PyInterpreterState *interp) { From f7723c0aa18847906984e8f52b70e906708196b3 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 26 Apr 2025 12:13:13 -0400 Subject: [PATCH 15/85] Fix a few bugs and add a test. --- Include/internal/pycore_interp_structs.h | 1 + Modules/_testcapimodule.c | 21 ++++++++++++ Python/pylifecycle.c | 1 + Python/pystate.c | 43 ++++++++++++++++++------ 4 files changed, 55 insertions(+), 11 deletions(-) diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 33caf1edfa82c0..190bee6a614f12 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -797,6 +797,7 @@ struct _is { Py_ssize_t countdown; PyEvent finished; PyMutex mutex; + int shutting_down; } finalizing; } threads; diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 2e7a432e478599..e043dfed7c8112 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2571,6 +2571,26 @@ test_interp_refcount(PyObject *self, PyObject *unused) Py_RETURN_NONE; } +static PyObject * +test_interp_lookup(PyObject *self, PyObject *unused) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + assert(_PyInterpreterState_Refcount(interp) == 0); + int64_t interp_id = PyInterpreterState_GetID(interp); + PyInterpreterState *ref = PyInterpreterState_Lookup(interp_id); + assert(ref == interp); + assert(_PyInterpreterState_Refcount(interp) == 1); + PyInterpreterState_Release(ref); + assert(PyInterpreterState_Lookup(10000) == NULL); + Py_BEGIN_ALLOW_THREADS; + ref = PyInterpreterState_Lookup(interp_id); + assert(ref == interp); + PyInterpreterState_Release(ref); + Py_END_ALLOW_THREADS; + + Py_RETURN_NONE; +} + static PyMethodDef TestMethods[] = { {"set_errno", set_errno, METH_VARARGS}, {"test_config", test_config, METH_NOARGS}, @@ -2666,6 +2686,7 @@ static PyMethodDef TestMethods[] = { {"code_offset_to_line", _PyCFunction_CAST(code_offset_to_line), METH_FASTCALL}, {"toggle_reftrace_printer", toggle_reftrace_printer, METH_O}, {"test_interp_refcount", test_interp_refcount, METH_NOARGS}, + {"test_interp_lookup", test_interp_lookup, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index a9ce30be38453b..cd89ca2c3386d2 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -3469,6 +3469,7 @@ wait_for_native_shutdown(PyInterpreterState *interp) { assert(interp != NULL); struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; + _Py_atomic_store_int_release(&finalizing->shutting_down, 1); PyMutex_Lock(&finalizing->mutex); if (_Py_atomic_load_ssize_relaxed(&finalizing->countdown) == 0) { // Nothing to do. diff --git a/Python/pystate.c b/Python/pystate.c index 0d566abee603af..862fb6a488f282 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1373,12 +1373,8 @@ interp_look_up_id(_PyRuntimeState *runtime, int64_t requested_id) return NULL; } -/* Return the interpreter state with the given ID. - - Fail with RuntimeError if the interpreter is not found. */ - PyInterpreterState * -_PyInterpreterState_LookUpID(int64_t requested_id) +_PyInterpreterState_LookUpIDNoErr(int64_t requested_id) { PyInterpreterState *interp = NULL; if (requested_id >= 0) { @@ -1387,6 +1383,18 @@ _PyInterpreterState_LookUpID(int64_t requested_id) interp = interp_look_up_id(runtime, requested_id); HEAD_UNLOCK(runtime); } + return interp; +} + +/* Return the interpreter state with the given ID. + + Fail with RuntimeError if the interpreter is not found. */ + +PyInterpreterState * +_PyInterpreterState_LookUpID(int64_t requested_id) +{ + assert(_PyThreadState_GET() != NULL); + PyInterpreterState *interp = _PyInterpreterState_LookUpIDNoErr(requested_id); if (interp == NULL && !PyErr_Occurred()) { PyErr_Format(PyExc_InterpreterNotFoundError, "unrecognized interpreter ID %lld", requested_id); @@ -1807,13 +1815,23 @@ PyThreadState_Clear(PyThreadState *tstate) static void decrement_stoptheworld_countdown(struct _stoptheworld_state *stw); +static int +shutting_down_natives(PyInterpreterState *interp) +{ + assert(interp != NULL); + return _Py_atomic_load_int_relaxed(&interp->threads.finalizing.shutting_down); +} + static void decrement_daemon_count(PyInterpreterState *interp) { assert(interp != NULL); struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; - if (_Py_atomic_add_ssize(&finalizing->countdown, -1) == 1) { + Py_ssize_t old = _Py_atomic_add_ssize(&finalizing->countdown, -1); + if (old == 1 && shutting_down_natives(interp)) { _PyEvent_Notify(&finalizing->finished); + } else if (old <= 0) { + Py_FatalError("interpreter has negative reference count"); } } @@ -3074,6 +3092,9 @@ _PyThreadState_CheckConsistency(PyThreadState *tstate) int _PyThreadState_MustExit(PyThreadState *tstate) { + if (!tstate->daemon) { + return 0; + } int state = _Py_atomic_load_int_relaxed(&tstate->state); return state == _Py_THREAD_SHUTTING_DOWN; } @@ -3221,19 +3242,19 @@ PyInterpreterState_Hold(void) PyInterpreterState * PyInterpreterState_Lookup(int64_t interp_id) { - PyInterpreterState *interp = _PyInterpreterState_LookUpID(interp_id); + PyInterpreterState *interp = _PyInterpreterState_LookUpIDNoErr(interp_id); if (interp == NULL) { return NULL; } HEAD_LOCK(&_PyRuntime); // Prevent deletion - struct _Py_finalizing_threads finalizing = interp->threads.finalizing; - PyMutex *mutex = &finalizing.mutex; + struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; + PyMutex *mutex = &finalizing->mutex; PyMutex_Lock(mutex); // Synchronize TOCTOU with the event flag - if (_PyEvent_IsSet(&finalizing.finished)) { + if (_PyEvent_IsSet(&finalizing->finished)) { /* Interpreter has already finished threads */ interp = NULL; } else { - _Py_atomic_add_ssize(&finalizing.countdown, 1); + _Py_atomic_add_ssize(&finalizing->countdown, 1); } PyMutex_Unlock(mutex); HEAD_UNLOCK(&_PyRuntime); From 62e95491065f2a37aa8827641c90c6a4a88c0466 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 27 Apr 2025 10:36:45 -0400 Subject: [PATCH 16/85] Add a test for PyThreadState_Ensure() across interpreters. --- Include/cpython/pystate.h | 3 ++- Modules/_testcapimodule.c | 31 +++++++++++++++++++++++++++++++ Python/pystate.c | 11 +++++++++-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 9de35fd0ac807a..a72eb038328aa3 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -288,8 +288,9 @@ PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_Lookup(int64_t interp_id); /* Release a reference to an interpreter incremented by PyInterpreterState_Hold() */ PyAPI_FUNC(void) PyInterpreterState_Release(PyInterpreterState *interp); -// Export for '_testcapi' shared extension +// Exports for '_testcapi' shared extension PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp); +PyAPI_FUNC(void) _PyInterpreterState_Incref(PyInterpreterState *interp); PyAPI_FUNC(int) PyThreadState_SetDaemon(int daemon); diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index e043dfed7c8112..3d71445cef5138 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2591,6 +2591,36 @@ test_interp_lookup(PyObject *self, PyObject *unused) Py_RETURN_NONE; } +static PyObject * +test_interp_ensure(PyObject *self, PyObject *unused) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + PyThreadState *save_tstate = PyThreadState_Swap(NULL); + PyThreadState *tstate = Py_NewInterpreter(); + PyInterpreterState *subinterp = PyThreadState_GetInterpreter(tstate); + + for (int i = 0; i < 10; ++i) { + _PyInterpreterState_Incref(interp); + int res = PyThreadState_Ensure(interp); + assert(res == 0); + assert(PyInterpreterState_Get() == interp); + } + + for (int i = 0; i < 10; ++i) { + _PyInterpreterState_Incref(subinterp); + int res = PyThreadState_Ensure(subinterp); + assert(res == 0); + assert(PyInterpreterState_Get() == subinterp); + } + + for (int i = 0; i < 20; ++i) { + PyThreadState_Release(); + } + + PyThreadState_Swap(save_tstate); + Py_RETURN_NONE; +} + static PyMethodDef TestMethods[] = { {"set_errno", set_errno, METH_VARARGS}, {"test_config", test_config, METH_NOARGS}, @@ -2687,6 +2717,7 @@ static PyMethodDef TestMethods[] = { {"toggle_reftrace_printer", toggle_reftrace_printer, METH_O}, {"test_interp_refcount", test_interp_refcount, METH_NOARGS}, {"test_interp_lookup", test_interp_lookup, METH_NOARGS}, + {"test_interp_ensure", test_interp_ensure, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/pystate.c b/Python/pystate.c index 862fb6a488f282..cd7e7918edb5c6 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3230,12 +3230,19 @@ _PyInterpreterState_Refcount(PyInterpreterState *interp) return refcount; } +void +_PyInterpreterState_Incref(PyInterpreterState *interp) +{ + assert(interp != NULL); + assert(_Py_atomic_load_ssize_relaxed(&interp->threads.finalizing.countdown) >= 0); + _Py_atomic_add_ssize(&interp->threads.finalizing.countdown, 1); +} + PyInterpreterState * PyInterpreterState_Hold(void) { PyInterpreterState *interp = PyInterpreterState_Get(); - assert(_Py_atomic_load_ssize_relaxed(&interp->threads.finalizing.countdown) >= 0); - _Py_atomic_add_ssize(&interp->threads.finalizing.countdown, 1); + _PyInterpreterState_Incref(interp); return interp; } From bc6063076e12c46920645253001e9f178414d72f Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 28 Apr 2025 16:12:50 -0400 Subject: [PATCH 17/85] Remove an artifact from old approach. --- Python/pystate.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index cd7e7918edb5c6..084e9f4afe5c85 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2152,7 +2152,7 @@ tstate_wait_attach(PyThreadState *tstate) _PyParkingLot_Park(&tstate->state, &state, sizeof(tstate->state), /*timeout=*/-1, NULL, /*detach=*/0); } - else if (state == _Py_THREAD_SHUTTING_DOWN && tstate->daemon) { + else if (state == _Py_THREAD_SHUTTING_DOWN) { // We're shutting down, so we can't attach. _PyThreadState_HangThread(tstate); } @@ -3092,9 +3092,6 @@ _PyThreadState_CheckConsistency(PyThreadState *tstate) int _PyThreadState_MustExit(PyThreadState *tstate) { - if (!tstate->daemon) { - return 0; - } int state = _Py_atomic_load_int_relaxed(&tstate->state); return state == _Py_THREAD_SHUTTING_DOWN; } From 9d8d526274978303f3c04cce5c3b1db49ea49baa Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 28 Apr 2025 16:43:12 -0400 Subject: [PATCH 18/85] Fix test from earlier semantics. --- Modules/_testcapimodule.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 3d71445cef5138..06243ba225366e 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2549,21 +2549,15 @@ toggle_reftrace_printer(PyObject *ob, PyObject *arg) static PyObject * test_interp_refcount(PyObject *self, PyObject *unused) { - PyThreadState *save = PyThreadState_Get(); - PyThreadState *tstate = Py_NewInterpreter(); - assert(tstate == PyThreadState_Get()); - PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate); - assert(interp != NULL); + PyInterpreterState *interp = PyInterpreterState_Get(); + // Reference counts are technically 0 by default assert(_PyInterpreterState_Refcount(interp) == 0); PyInterpreterState *held = PyInterpreterState_Hold(); assert(_PyInterpreterState_Refcount(interp) == 1); - PyInterpreterState_Release(held); - assert(_PyInterpreterState_Refcount(interp) == 0); - held = PyInterpreterState_Hold(); - Py_EndInterpreter(tstate); - PyThreadState_Swap(save); + assert(_PyInterpreterState_Refcount(interp) == 2); + PyInterpreterState_Release(held); assert(_PyInterpreterState_Refcount(interp) == 1); PyInterpreterState_Release(held); assert(_PyInterpreterState_Refcount(interp) == 0); From 54b0ce027eade2b2a5f9c74ddf4eb2a032feb224 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 22 May 2025 15:45:24 -0400 Subject: [PATCH 19/85] Remove 'daemonness' as a property of a thread. --- Python/pylifecycle.c | 4 +- Python/pystate.c | 108 ++----------------------------------------- 2 files changed, 5 insertions(+), 107 deletions(-) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index cd89ca2c3386d2..c31509f74f3dc1 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2014,7 +2014,7 @@ _Py_Finalize(_PyRuntimeState *runtime) // Wrap up existing "threading"-module-created, non-daemon threads. wait_for_thread_shutdown(tstate); - // Wrap up non-daemon native threads + // Wait for the interpreter's reference count to reach zero wait_for_native_shutdown(tstate->interp); // Make any remaining pending calls. @@ -2433,7 +2433,7 @@ Py_EndInterpreter(PyThreadState *tstate) // Wrap up existing "threading"-module-created, non-daemon threads. wait_for_thread_shutdown(tstate); - // Wrap up non-daemon native threads + // Wait for the interpreter's reference count to reach zero wait_for_native_shutdown(tstate->interp); // Make any remaining pending calls. diff --git a/Python/pystate.c b/Python/pystate.c index 084e9f4afe5c85..2faebbc2f284c5 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1610,8 +1610,6 @@ new_threadstate(PyInterpreterState *interp, int whence) return NULL; } #endif - ((PyThreadState *)tstate)->daemon = 1; - /* We serialize concurrent creation to protect global state. */ HEAD_LOCK(interp->runtime); @@ -1823,7 +1821,7 @@ shutting_down_natives(PyInterpreterState *interp) } static void -decrement_daemon_count(PyInterpreterState *interp) +decref_interpreter(PyInterpreterState *interp) { assert(interp != NULL); struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; @@ -1869,10 +1867,6 @@ tstate_delete_common(PyThreadState *tstate, int release_gil) decrement_stoptheworld_countdown(&runtime->stoptheworld); } } - if (tstate->daemon == 0 - && tstate != (PyThreadState *)&interp->_initial_thread) { - decrement_daemon_count(interp); - } #if defined(Py_REF_DEBUG) && defined(Py_GIL_DISABLED) // Add our portion of the total refcount to the interpreter's total. @@ -3270,113 +3264,17 @@ void PyInterpreterState_Release(PyInterpreterState *interp) { assert(interp != NULL); - decrement_daemon_count(interp); -} - -static int -tstate_set_daemon(PyThreadState *tstate, PyInterpreterState *interp, int daemon) -{ - assert(tstate != NULL); - assert(interp != NULL); - assert(tstate->interp == interp); - assert(daemon == 1 || daemon == 0); - struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; - PyMutex_Lock(&finalizing->mutex); - if (_Py_atomic_load_ssize_relaxed(&finalizing->countdown) == 0) { - PyMutex_Unlock(&finalizing->mutex); - return -1; - } - if (_PyEvent_IsSet(&finalizing->finished)) { - /* Native threads have already finalized */ - PyMutex_Unlock(&finalizing->mutex); - return -1; - } - _Py_atomic_add_ssize(&finalizing->countdown, 1); - PyMutex_Unlock(&finalizing->mutex); - tstate->daemon = daemon; - return 1; -} - -int -PyThreadState_SetDaemon(int daemon) -{ - PyThreadState *tstate = PyThreadState_Get(); - if (daemon != 0 && daemon != 1) { - Py_FatalError("daemon must be 0 or 1"); - } - PyInterpreterState *interp = tstate->interp; - assert(interp != NULL); - if (tstate == (PyThreadState *)&interp->_initial_thread) { - Py_FatalError("thread cannot be the main thread"); - } - if (tstate->daemon == daemon) { - return 0; - } - - return tstate_set_daemon(tstate, interp, daemon); + decref_interpreter(interp); } int PyThreadState_Ensure(PyInterpreterState *interp) { - assert(interp != NULL); - _Py_ensured_tstate *entry = PyMem_RawMalloc(sizeof(_Py_ensured_tstate)); - if (entry == NULL) { - decrement_daemon_count(interp); - return -1; - } - PyThreadState *save = _PyThreadState_GET(); - if (save != NULL && save->interp == interp) { - entry->was_daemon = save->daemon; - entry->next = save->ensured; - entry->prior_tstate = NULL; - save->ensured = entry; - // Setting 'daemon' to 0 passes off the interpreter's reference - save->daemon = 0; - return 0; - } - - PyThreadState *tstate = PyThreadState_New(interp); - if (tstate == NULL) { - PyMem_RawFree(entry); - return -1; - } - tstate->daemon = 0; - entry->was_daemon = 0; - entry->prior_tstate = save; - entry->next = NULL; - tstate->ensured = entry; - PyThreadState_Swap(tstate); - return 0; } void PyThreadState_Release(void) { - PyThreadState *tstate = _PyThreadState_GET(); - _Py_EnsureTstateNotNULL(tstate); - _Py_ensured_tstate *ensured = tstate->ensured; - if (ensured == NULL) { - Py_FatalError("PyThreadState_Release() called without PyThreadState_Ensure()"); - } - if (ensured->prior_tstate != NULL) { - assert(ensured->was_daemon == 0); - PyThreadState_Clear(tstate); - PyThreadState_Swap(ensured->prior_tstate); - PyMem_RawFree(ensured); - PyThreadState_Delete(tstate); - return; - } - - tstate->ensured = ensured->next; - tstate->daemon = ensured->was_daemon; - PyMem_RawFree(ensured); - if (tstate->ensured == NULL) { - PyThreadState_Clear(tstate); - PyThreadState_Swap(NULL); - PyThreadState_Delete(tstate); - } else { - decrement_daemon_count(tstate->interp); - } + return 0; } From 5955de6df592ed8d683b1ff2435220713d3bda4d Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 22 May 2025 15:58:29 -0400 Subject: [PATCH 20/85] Add strong interpreter reference functions. --- Include/cpython/pystate.h | 33 +++++++++++++++++++++++---------- Python/pystate.c | 39 +++++++++++++++++++++++++++++---------- 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index a72eb038328aa3..ab15f73b4d3b59 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -277,23 +277,36 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( PyInterpreterState *interp, _PyFrameEvalFunction eval_frame); -/* Similar to PyInterpreterState_Get(), but returns the interpreter with an - * incremented reference count. PyInterpreterState_Delete() won't delete the - * full interpreter structure until the reference is released by - * PyThreadState_Ensure() or PyInterpreterState_Release(). */ -PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_Hold(void); +/* Strong interpreter references */ -PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_Lookup(int64_t interp_id); +typedef uintptr_t PyInterpreterRef; -/* Release a reference to an interpreter incremented by PyInterpreterState_Hold() */ -PyAPI_FUNC(void) PyInterpreterState_Release(PyInterpreterState *interp); +PyAPI_FUNC(PyInterpreterRef) PyInterpreterRef_Get(void); +PyAPI_FUNC(PyInterpreterRef) PyInterpreterRef_Dup(PyInterpreterRef ref); +PyAPI_FUNC(PyInterpreterRef) PyInterpreterState_AsStrong(PyInterpreterState *interp); +PyAPI_FUNC(void) PyInterpreterRef_Close(PyInterpreterRef ref); + +#define PyInterpreterRef_Close(ref) do { \ + PyInterpreterRef_Close(ref); \ + ref = 0; \ +} while (0); \ + +/* Weak interpreter references */ + +typedef struct _interpreter_weakref { + int64_t id; + Py_ssize_t refcount; +} PyInterpreterWeakRef; + +PyAPI_FUNC(PyInterpreterWeakRef) PyInterpreterWeakRef_Get(void); +PyAPI_FUNC(PyInterpreterWeakRef) PyInterpreterWeakRef_Dup(PyInterpreterWeakRef wref); +PyAPI_FUNC(PyInterpreterRef) PyInterpreterWeakRef_AsStrong(PyInterpreterWeakRef wref); +PyAPI_FUNC(void) PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref); // Exports for '_testcapi' shared extension PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp); PyAPI_FUNC(void) _PyInterpreterState_Incref(PyInterpreterState *interp); -PyAPI_FUNC(int) PyThreadState_SetDaemon(int daemon); - PyAPI_FUNC(int) PyThreadState_Ensure(PyInterpreterState *interp); PyAPI_FUNC(void) PyThreadState_Release(void); diff --git a/Python/pystate.c b/Python/pystate.c index 2faebbc2f284c5..4d118423d204dd 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3229,12 +3229,38 @@ _PyInterpreterState_Incref(PyInterpreterState *interp) _Py_atomic_add_ssize(&interp->threads.finalizing.countdown, 1); } -PyInterpreterState * -PyInterpreterState_Hold(void) +static PyInterpreterState * +ref_as_interp(PyInterpreterRef ref) +{ + PyInterpreterState *interp = (PyInterpreterState *)ref; + if (interp == NULL) { + Py_FatalError("Got a null interpreter reference, likely due to use after close."); + } + + return interp; +} + +PyInterpreterRef +PyInterpreterRef_Get(void) { PyInterpreterState *interp = PyInterpreterState_Get(); _PyInterpreterState_Incref(interp); - return interp; + return (PyInterpreterRef)interp; +} + +PyInterpreterRef +PyInterpreterRef_Dup(PyInterpreterRef ref) +{ + PyInterpreterState *interp = ref_as_interp(ref); + _PyInterpreterState_Incref(interp); + return (PyInterpreterRef)interp; +} + +void +PyInterpreterRef_Close(PyInterpreterRef ref) +{ + PyInterpreterState *interp = ref_as_interp(ref); + decref_interpreter(ref); } PyInterpreterState * @@ -3260,13 +3286,6 @@ PyInterpreterState_Lookup(int64_t interp_id) return interp; } -void -PyInterpreterState_Release(PyInterpreterState *interp) -{ - assert(interp != NULL); - decref_interpreter(interp); -} - int PyThreadState_Ensure(PyInterpreterState *interp) { From 92cf90603e7910b7f59f1ab36a677cb17b76aa36 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 22 May 2025 16:05:48 -0400 Subject: [PATCH 21/85] Implement weak references. --- Include/cpython/pystate.h | 2 +- Python/pystate.c | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index ab15f73b4d3b59..8e2327bc0fc027 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -300,7 +300,7 @@ typedef struct _interpreter_weakref { PyAPI_FUNC(PyInterpreterWeakRef) PyInterpreterWeakRef_Get(void); PyAPI_FUNC(PyInterpreterWeakRef) PyInterpreterWeakRef_Dup(PyInterpreterWeakRef wref); -PyAPI_FUNC(PyInterpreterRef) PyInterpreterWeakRef_AsStrong(PyInterpreterWeakRef wref); +PyAPI_FUNC(int) PyInterpreterWeakRef_AsStrong(PyInterpreterWeakRef wref, PyInterpreterRef *strong_ptr); PyAPI_FUNC(void) PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref); // Exports for '_testcapi' shared extension diff --git a/Python/pystate.c b/Python/pystate.c index 4d118423d204dd..72963801d644ef 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3256,6 +3256,7 @@ PyInterpreterRef_Dup(PyInterpreterRef ref) return (PyInterpreterRef)interp; } +#undef PyInterpreterRef_Close void PyInterpreterRef_Close(PyInterpreterRef ref) { @@ -3263,12 +3264,36 @@ PyInterpreterRef_Close(PyInterpreterRef ref) decref_interpreter(ref); } -PyInterpreterState * -PyInterpreterState_Lookup(int64_t interp_id) + +PyInterpreterWeakRef +PyInterpreterWeakRef_Get(void) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + PyInterpreterWeakRef wref = { interp->id }; + return wref; +} + +PyInterpreterWeakRef +PyInterpreterWeakRef_Dup(PyInterpreterWeakRef wref) +{ + return wref; +} + +void +PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref) +{ + return; +} + +int +PyInterpreterWeakRef_AsStrong(PyInterpreterWeakRef wref, PyInterpreterRef *strong_ptr) { + assert(strong_ptr != NULL); + int64_t interp_id = wref.id; PyInterpreterState *interp = _PyInterpreterState_LookUpIDNoErr(interp_id); if (interp == NULL) { - return NULL; + *strong_ptr = 0; + return -1; } HEAD_LOCK(&_PyRuntime); // Prevent deletion struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; @@ -3282,8 +3307,9 @@ PyInterpreterState_Lookup(int64_t interp_id) } PyMutex_Unlock(mutex); HEAD_UNLOCK(&_PyRuntime); + *strong_ptr = (PyInterpreterRef)interp; - return interp; + return 0; } int From b0d0673ef43295f2d183e5366b97b7cdf349b803 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 22 May 2025 16:23:43 -0400 Subject: [PATCH 22/85] Fix some thread safety issues regarding interpreter deletion. --- Include/cpython/pystate.h | 2 +- Python/pystate.c | 55 +++++++++++++++++++++++++++------------ 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 8e2327bc0fc027..cb400820eade97 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -283,7 +283,7 @@ typedef uintptr_t PyInterpreterRef; PyAPI_FUNC(PyInterpreterRef) PyInterpreterRef_Get(void); PyAPI_FUNC(PyInterpreterRef) PyInterpreterRef_Dup(PyInterpreterRef ref); -PyAPI_FUNC(PyInterpreterRef) PyInterpreterState_AsStrong(PyInterpreterState *interp); +PyAPI_FUNC(int) PyInterpreterState_AsStrong(PyInterpreterState *interp, PyInterpreterRef *strong_ptr); PyAPI_FUNC(void) PyInterpreterRef_Close(PyInterpreterRef ref); #define PyInterpreterRef_Close(ref) do { \ diff --git a/Python/pystate.c b/Python/pystate.c index 72963801d644ef..aa680a77757502 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3261,10 +3261,9 @@ void PyInterpreterRef_Close(PyInterpreterRef ref) { PyInterpreterState *interp = ref_as_interp(ref); - decref_interpreter(ref); + decref_interpreter(interp); } - PyInterpreterWeakRef PyInterpreterWeakRef_Get(void) { @@ -3285,33 +3284,57 @@ PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref) return; } -int -PyInterpreterWeakRef_AsStrong(PyInterpreterWeakRef wref, PyInterpreterRef *strong_ptr) +static int +try_acquire_strong_ref(PyInterpreterState *interp, PyInterpreterRef *strong_ptr) { - assert(strong_ptr != NULL); - int64_t interp_id = wref.id; - PyInterpreterState *interp = _PyInterpreterState_LookUpIDNoErr(interp_id); - if (interp == NULL) { - *strong_ptr = 0; - return -1; - } - HEAD_LOCK(&_PyRuntime); // Prevent deletion struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; PyMutex *mutex = &finalizing->mutex; PyMutex_Lock(mutex); // Synchronize TOCTOU with the event flag if (_PyEvent_IsSet(&finalizing->finished)) { /* Interpreter has already finished threads */ - interp = NULL; + *strong_ptr = 0; + return -1; } else { _Py_atomic_add_ssize(&finalizing->countdown, 1); } PyMutex_Unlock(mutex); - HEAD_UNLOCK(&_PyRuntime); *strong_ptr = (PyInterpreterRef)interp; - return 0; } +int +PyInterpreterWeakRef_AsStrong(PyInterpreterWeakRef wref, PyInterpreterRef *strong_ptr) +{ + assert(strong_ptr != NULL); + int64_t interp_id = wref.id; + /* Interpreters cannot be deleted while we hold the runtime lock. */ + _PyRuntimeState *runtime = &_PyRuntime; + HEAD_LOCK(runtime); + PyInterpreterState *interp = interp_look_up_id(runtime, interp_id); + if (interp == NULL) { + HEAD_UNLOCK(runtime); + *strong_ptr = 0; + return -1; + } + + int res = try_acquire_strong_ref(interp, strong_ptr); + HEAD_UNLOCK(runtime); + return res; +} + +int +PyInterpreterState_AsStrong(PyInterpreterState *interp, PyInterpreterRef *strong_ptr) +{ + assert(interp != NULL); + assert(strong_ptr != NULL); + _PyRuntimeState *runtime = &_PyRuntime; + HEAD_LOCK(runtime); + int res = try_acquire_strong_ref(interp, strong_ptr); + HEAD_UNLOCK(runtime); + + return res; +} + int PyThreadState_Ensure(PyInterpreterState *interp) { @@ -3321,5 +3344,5 @@ PyThreadState_Ensure(PyInterpreterState *interp) void PyThreadState_Release(void) { - return 0; + return; } From 16d79de65d59ef67dcd3fac00539d90f9b7d2b21 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 22 May 2025 16:42:19 -0400 Subject: [PATCH 23/85] Implement new version of PyThreadState_Ensure() and PyThreadState_Release() --- Include/cpython/pystate.h | 16 +++++------- Python/pystate.c | 52 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 85479d2c2f5017..28b488e333a90b 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -61,12 +61,6 @@ typedef struct _stack_chunk { PyObject * data[1]; /* Variable sized */ } _PyStackChunk; -typedef struct _ensured_tstate { - struct _ensured_tstate *next; - PyThreadState *prior_tstate; - uint8_t was_daemon; -} _Py_ensured_tstate; - struct _ts { /* See Python/ceval.c for comments explaining most fields */ @@ -213,10 +207,11 @@ struct _ts { PyObject *threading_local_sentinel; _PyRemoteDebuggerSupport remote_debugger_support; - /* Whether this thread hangs when the interpreter is finalizing. */ - uint8_t daemon; + /* Number of nested PyThreadState_Ensure() calls on this thread state */ + Py_ssize_t ensure_counter; - _Py_ensured_tstate *ensured; + /* Thread state that was active before PyThreadState_Ensure() was called. */ + PyThreadState *prior_ensure; }; /* other API */ @@ -279,6 +274,7 @@ PyAPI_FUNC(PyInterpreterRef) PyInterpreterRef_Get(void); PyAPI_FUNC(PyInterpreterRef) PyInterpreterRef_Dup(PyInterpreterRef ref); PyAPI_FUNC(int) PyInterpreterState_AsStrong(PyInterpreterState *interp, PyInterpreterRef *strong_ptr); PyAPI_FUNC(void) PyInterpreterRef_Close(PyInterpreterRef ref); +PyAPI_FUNC(PyInterpreterState *) PyInterpreterRef_AsInterpreter(PyInterpreterRef ref); #define PyInterpreterRef_Close(ref) do { \ PyInterpreterRef_Close(ref); \ @@ -301,6 +297,6 @@ PyAPI_FUNC(void) PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref); PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp); PyAPI_FUNC(void) _PyInterpreterState_Incref(PyInterpreterState *interp); -PyAPI_FUNC(int) PyThreadState_Ensure(PyInterpreterState *interp); +PyAPI_FUNC(int) PyThreadState_Ensure(PyInterpreterRef interp_ref); PyAPI_FUNC(void) PyThreadState_Release(void); diff --git a/Python/pystate.c b/Python/pystate.c index 3599fd9053f9d8..9dc8c845318690 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3188,6 +3188,13 @@ PyInterpreterRef_Close(PyInterpreterRef ref) decref_interpreter(interp); } +PyInterpreterState * +PyInterpreterRef_AsInterpreter(PyInterpreterRef ref) +{ + PyInterpreterState *interp = ref_as_interp(ref); + return interp; +} + PyInterpreterWeakRef PyInterpreterWeakRef_Get(void) { @@ -3260,13 +3267,56 @@ PyInterpreterState_AsStrong(PyInterpreterState *interp, PyInterpreterRef *strong } int -PyThreadState_Ensure(PyInterpreterState *interp) +PyThreadState_Ensure(PyInterpreterRef interp_ref) { + PyInterpreterState *interp = ref_as_interp(interp_ref); + PyThreadState *attached_tstate = current_fast_get(); + if (attached_tstate != NULL && attached_tstate->interp == interp) { + /* Yay! We already have an attached thread state that matches. */ + ++attached_tstate->ensure_counter; + return 0; + } + + PyThreadState *detached_gilstate = gilstate_get(); + if (detached_gilstate != NULL && detached_gilstate->interp == interp) { + /* There's a detached thread state that works. */ + assert(attached_tstate == NULL); + ++detached_gilstate->ensure_counter; + _PyThreadState_Attach(detached_gilstate); + return 0; + } + + PyThreadState *fresh_tstate = _PyThreadState_NewBound(interp, + _PyThreadState_WHENCE_GILSTATE); + if (fresh_tstate == NULL) { + return -1; + } + + if (attached_tstate != NULL) { + fresh_tstate->ensure_counter = 1; + fresh_tstate->prior_ensure = PyThreadState_Swap(fresh_tstate); + } else { + _PyThreadState_Attach(fresh_tstate); + } + return 0; } void PyThreadState_Release(void) { + PyThreadState *tstate = current_fast_get(); + _Py_EnsureTstateNotNULL(tstate); + Py_ssize_t remaining = --tstate->ensure_counter; + if (remaining < 0) { + Py_FatalError("PyThreadState_Release() called more times than PyThreadState_Ensure()"); + } + if (remaining == 0) { + PyThreadState *to_restore = tstate->prior_ensure; + PyThreadState_Clear(tstate); + PyThreadState_Swap(to_restore); + PyThreadState_Delete(tstate); + } + return; } From c2bffcd4b95961a2e9ef7aa17d1e7b9ddd308a77 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 22 May 2025 17:00:52 -0400 Subject: [PATCH 24/85] Use the new APIs in the tests. --- Modules/_testcapimodule.c | 52 ++++++++++++++++++++++++--------------- Programs/_testembed.c | 18 ++++++++------ 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 06243ba225366e..b7f13dfe97fbc6 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2550,37 +2550,46 @@ static PyObject * test_interp_refcount(PyObject *self, PyObject *unused) { PyInterpreterState *interp = PyInterpreterState_Get(); + PyInterpreterRef ref1; + PyInterpreterRef ref2; // Reference counts are technically 0 by default assert(_PyInterpreterState_Refcount(interp) == 0); - PyInterpreterState *held = PyInterpreterState_Hold(); + ref1 = PyInterpreterRef_Get(); assert(_PyInterpreterState_Refcount(interp) == 1); - held = PyInterpreterState_Hold(); + ref2 = PyInterpreterRef_Get(); assert(_PyInterpreterState_Refcount(interp) == 2); - PyInterpreterState_Release(held); + PyInterpreterRef_Close(ref1); assert(_PyInterpreterState_Refcount(interp) == 1); - PyInterpreterState_Release(held); + PyInterpreterRef_Close(ref2); + assert(_PyInterpreterState_Refcount(interp) == 0); + + ref1 = PyInterpreterRef_Get(); + ref2 = PyInterpreterRef_Dup(ref1); + assert(_PyInterpreterState_Refcount(interp) == 2); + assert(PyInterpreterRef_AsInterpreter(ref1) == interp); + assert(PyInterpreterRef_AsInterpreter(ref2) == interp); + PyInterpreterRef_Close(ref1); + PyInterpreterRef_Close(ref2); assert(_PyInterpreterState_Refcount(interp) == 0); Py_RETURN_NONE; } static PyObject * -test_interp_lookup(PyObject *self, PyObject *unused) +test_interp_weak_ref(PyObject *self, PyObject *unused) { PyInterpreterState *interp = PyInterpreterState_Get(); + PyInterpreterWeakRef wref = PyInterpreterWeakRef_Get(); assert(_PyInterpreterState_Refcount(interp) == 0); - int64_t interp_id = PyInterpreterState_GetID(interp); - PyInterpreterState *ref = PyInterpreterState_Lookup(interp_id); - assert(ref == interp); + + PyInterpreterRef ref; + int res = PyInterpreterWeakRef_AsStrong(wref, &ref); + assert(res == 0); + assert(PyInterpreterRef_AsInterpreter(ref) == interp); assert(_PyInterpreterState_Refcount(interp) == 1); - PyInterpreterState_Release(ref); - assert(PyInterpreterState_Lookup(10000) == NULL); - Py_BEGIN_ALLOW_THREADS; - ref = PyInterpreterState_Lookup(interp_id); - assert(ref == interp); - PyInterpreterState_Release(ref); - Py_END_ALLOW_THREADS; + PyInterpreterWeakRef_Close(wref); + PyInterpreterRef_Close(ref); Py_RETURN_NONE; } @@ -2589,20 +2598,20 @@ static PyObject * test_interp_ensure(PyObject *self, PyObject *unused) { PyInterpreterState *interp = PyInterpreterState_Get(); + PyInterpreterRef ref = PyInterpreterRef_Get(); PyThreadState *save_tstate = PyThreadState_Swap(NULL); PyThreadState *tstate = Py_NewInterpreter(); + PyInterpreterRef sub_ref = PyInterpreterRef_Get(); PyInterpreterState *subinterp = PyThreadState_GetInterpreter(tstate); for (int i = 0; i < 10; ++i) { - _PyInterpreterState_Incref(interp); - int res = PyThreadState_Ensure(interp); + int res = PyThreadState_Ensure(sub_ref); assert(res == 0); assert(PyInterpreterState_Get() == interp); } for (int i = 0; i < 10; ++i) { - _PyInterpreterState_Incref(subinterp); - int res = PyThreadState_Ensure(subinterp); + int res = PyThreadState_Ensure(sub_ref); assert(res == 0); assert(PyInterpreterState_Get() == subinterp); } @@ -2611,6 +2620,9 @@ test_interp_ensure(PyObject *self, PyObject *unused) PyThreadState_Release(); } + PyInterpreterRef_Close(ref); + PyInterpreterRef_Close(sub_ref); + PyThreadState_Swap(save_tstate); Py_RETURN_NONE; } @@ -2710,7 +2722,7 @@ static PyMethodDef TestMethods[] = { {"code_offset_to_line", _PyCFunction_CAST(code_offset_to_line), METH_FASTCALL}, {"toggle_reftrace_printer", toggle_reftrace_printer, METH_O}, {"test_interp_refcount", test_interp_refcount, METH_NOARGS}, - {"test_interp_lookup", test_interp_lookup, METH_NOARGS}, + {"test_interp_weak_ref", test_interp_weak_ref, METH_NOARGS}, {"test_interp_ensure", test_interp_ensure, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 1768f8cffd65d6..98745b65db843f 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2323,7 +2323,7 @@ const char *THREAD_CODE = "import time\n" "fib(10)"; typedef struct { - PyInterpreterState *interp; + PyInterpreterRef ref; int done; } ThreadData; @@ -2331,12 +2331,12 @@ static void do_tstate_ensure(void *arg) { ThreadData *data = (ThreadData *)arg; - int res = PyThreadState_Ensure(data->interp); + int res = PyThreadState_Ensure(data->ref); assert(res == 0); - PyThreadState_Ensure(PyInterpreterState_Hold()); - PyThreadState_Ensure(PyInterpreterState_Hold()); + PyThreadState_Ensure(data->ref); + PyThreadState_Ensure(data->ref); PyGILState_STATE gstate = PyGILState_Ensure(); - PyThreadState_Ensure(PyInterpreterState_Hold()); + PyThreadState_Ensure(data->ref); res = PyRun_SimpleString(THREAD_CODE); PyThreadState_Release(); PyGILState_Release(gstate); @@ -2344,19 +2344,21 @@ do_tstate_ensure(void *arg) PyThreadState_Release(); assert(res == 0); PyThreadState_Release(); + PyInterpreterRef_Close(data->ref); data->done = 1; } static int test_thread_state_ensure(void) { - _testembed_Py_InitializeFromConfig(); + _testembed_initialize(); PyThread_handle_t handle; PyThread_ident_t ident; - ThreadData data = { PyInterpreterState_Hold() }; + PyInterpreterRef ref = PyInterpreterRef_Get(); + ThreadData data = { ref }; if (PyThread_start_joinable_thread(do_tstate_ensure, &data, &ident, &handle) < 0) { - PyInterpreterState_Release(data.interp); + PyInterpreterRef_Close(ref); return -1; } Py_Finalize(); From 911c6b5e0ee9a399773cd245265056a623c26955 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 22 May 2025 17:03:32 -0400 Subject: [PATCH 25/85] Fix _testcapi. --- Modules/_testcapimodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index b7f13dfe97fbc6..c6c90808631f2c 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2605,7 +2605,7 @@ test_interp_ensure(PyObject *self, PyObject *unused) PyInterpreterState *subinterp = PyThreadState_GetInterpreter(tstate); for (int i = 0; i < 10; ++i) { - int res = PyThreadState_Ensure(sub_ref); + int res = PyThreadState_Ensure(ref); assert(res == 0); assert(PyInterpreterState_Get() == interp); } From b84fa9006a885105de65c3476f2d4aeafdc5baea Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 22 May 2025 17:09:55 -0400 Subject: [PATCH 26/85] Fix the ensure counter. --- Python/pystate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/pystate.c b/Python/pystate.c index 9dc8c845318690..62085b5ccb04e5 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3291,9 +3291,9 @@ PyThreadState_Ensure(PyInterpreterRef interp_ref) if (fresh_tstate == NULL) { return -1; } + fresh_tstate->ensure_counter = 1; if (attached_tstate != NULL) { - fresh_tstate->ensure_counter = 1; fresh_tstate->prior_ensure = PyThreadState_Swap(fresh_tstate); } else { _PyThreadState_Attach(fresh_tstate); From 9ccf6bd4515ad414d03cc1319b4d319659089456 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 22 May 2025 17:12:12 -0400 Subject: [PATCH 27/85] Add the test to test_embed. --- Lib/test/test_embed.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 89f4aebe28f4a1..24d7b99ff0ff31 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -1919,6 +1919,10 @@ def test_gilstate_after_finalization(self): self.run_embedded_interpreter("test_gilstate_after_finalization") + def test_thread_state_ensure(self): + self.run_embedded_interpreter("test_thread_state_ensure") + + class MiscTests(EmbeddingTestsMixin, unittest.TestCase): def test_unicode_id_init(self): # bpo-42882: Test that _PyUnicode_FromId() works From 481caf5c2260f05ed3dcbef1974b39acb2ed07d8 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 22 May 2025 17:16:33 -0400 Subject: [PATCH 28/85] Allow the wait to be interrupted by CTRL+C. --- Python/pylifecycle.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 8d39a0e1bcaadd..9ae2c921eefbb5 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -3488,7 +3488,24 @@ wait_for_native_shutdown(PyInterpreterState *interp) } PyMutex_Unlock(&finalizing->mutex); - PyEvent_Wait(&finalizing->finished); + PyTime_t wait_ns = 1000 * 1000; // 1 millisecond + + while (true) { + if (PyEvent_WaitTimed(&finalizing->finished, wait_ns, 1)) { + // Event set + break; + } + + if (PyErr_CheckSignals()) { + // The user CTRL+C'd us, bail out without waiting for a reference + // count of zero. + // + // This will probably cause threads to crash, but maybe that's + // better than a deadlock. It might be worth intentionally + // leaking subinterpreters to prevent some crashes here. + break; + } + } } int Py_AtExit(void (*func)(void)) From 71e1aec58b583166d894ede39165b08b4127342a Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 22 May 2025 17:18:52 -0400 Subject: [PATCH 29/85] Print the error before bailing out. --- Python/pylifecycle.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 9ae2c921eefbb5..bb6cd9afd03505 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -3497,6 +3497,7 @@ wait_for_native_shutdown(PyInterpreterState *interp) } if (PyErr_CheckSignals()) { + PyErr_Print(); // The user CTRL+C'd us, bail out without waiting for a reference // count of zero. // From d66157883d854eabef0d555d2d8f5b15ae905312 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Tue, 27 May 2025 21:02:39 -0400 Subject: [PATCH 30/85] Updates for the new proposal. --- Include/cpython/pystate.h | 19 +++++++--- Modules/_testcapimodule.c | 25 +++++++++--- Programs/_testembed.c | 5 ++- Python/pystate.c | 80 ++++++++++++++++++++++++++++++--------- 4 files changed, 98 insertions(+), 31 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 28b488e333a90b..884bb026e45372 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -270,32 +270,39 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( typedef uintptr_t PyInterpreterRef; -PyAPI_FUNC(PyInterpreterRef) PyInterpreterRef_Get(void); +PyAPI_FUNC(int) PyInterpreterRef_Get(PyInterpreterRef *ptr); PyAPI_FUNC(PyInterpreterRef) PyInterpreterRef_Dup(PyInterpreterRef ref); -PyAPI_FUNC(int) PyInterpreterState_AsStrong(PyInterpreterState *interp, PyInterpreterRef *strong_ptr); +PyAPI_FUNC(int) PyInterpreterRef_Main(PyInterpreterRef *strong_ptr); PyAPI_FUNC(void) PyInterpreterRef_Close(PyInterpreterRef ref); PyAPI_FUNC(PyInterpreterState *) PyInterpreterRef_AsInterpreter(PyInterpreterRef ref); #define PyInterpreterRef_Close(ref) do { \ PyInterpreterRef_Close(ref); \ ref = 0; \ -} while (0); \ +} while (0); /* Weak interpreter references */ typedef struct _interpreter_weakref { int64_t id; Py_ssize_t refcount; -} PyInterpreterWeakRef; +} _PyInterpreterWeakRef; -PyAPI_FUNC(PyInterpreterWeakRef) PyInterpreterWeakRef_Get(void); +typedef _PyInterpreterWeakRef *PyInterpreterWeakRef; + +PyAPI_FUNC(int) PyInterpreterWeakRef_Get(PyInterpreterWeakRef *ptr); PyAPI_FUNC(PyInterpreterWeakRef) PyInterpreterWeakRef_Dup(PyInterpreterWeakRef wref); PyAPI_FUNC(int) PyInterpreterWeakRef_AsStrong(PyInterpreterWeakRef wref, PyInterpreterRef *strong_ptr); PyAPI_FUNC(void) PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref); +#define PyInterpreterWeakRef_Close(ref) do { \ + PyInterpreterWeakRef_Close(ref); \ + ref = 0; \ +} while (0); + // Exports for '_testcapi' shared extension PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp); -PyAPI_FUNC(void) _PyInterpreterState_Incref(PyInterpreterState *interp); +PyAPI_FUNC(int) _PyInterpreterState_Incref(PyInterpreterState *interp); PyAPI_FUNC(int) PyThreadState_Ensure(PyInterpreterRef interp_ref); diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index c6c90808631f2c..971bdadd6b8065 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2546,6 +2546,16 @@ toggle_reftrace_printer(PyObject *ob, PyObject *arg) Py_RETURN_NONE; } +static PyInterpreterRef +get_strong_ref(void) +{ + PyInterpreterRef ref; + if (PyInterpreterRef_Get(&ref) < 0) { + Py_FatalError("strong reference should not have failed"); + } + return ref; +} + static PyObject * test_interp_refcount(PyObject *self, PyObject *unused) { @@ -2555,16 +2565,16 @@ test_interp_refcount(PyObject *self, PyObject *unused) // Reference counts are technically 0 by default assert(_PyInterpreterState_Refcount(interp) == 0); - ref1 = PyInterpreterRef_Get(); + ref1 = get_strong_ref(); assert(_PyInterpreterState_Refcount(interp) == 1); - ref2 = PyInterpreterRef_Get(); + ref2 = get_strong_ref(); assert(_PyInterpreterState_Refcount(interp) == 2); PyInterpreterRef_Close(ref1); assert(_PyInterpreterState_Refcount(interp) == 1); PyInterpreterRef_Close(ref2); assert(_PyInterpreterState_Refcount(interp) == 0); - ref1 = PyInterpreterRef_Get(); + ref1 = get_strong_ref(); ref2 = PyInterpreterRef_Dup(ref1); assert(_PyInterpreterState_Refcount(interp) == 2); assert(PyInterpreterRef_AsInterpreter(ref1) == interp); @@ -2580,7 +2590,10 @@ static PyObject * test_interp_weak_ref(PyObject *self, PyObject *unused) { PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterWeakRef wref = PyInterpreterWeakRef_Get(); + PyInterpreterWeakRef wref; + if (PyInterpreterWeakRef_Get(&wref) < 0) { + return NULL; + } assert(_PyInterpreterState_Refcount(interp) == 0); PyInterpreterRef ref; @@ -2598,10 +2611,10 @@ static PyObject * test_interp_ensure(PyObject *self, PyObject *unused) { PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterRef ref = PyInterpreterRef_Get(); + PyInterpreterRef ref = get_strong_ref(); PyThreadState *save_tstate = PyThreadState_Swap(NULL); PyThreadState *tstate = Py_NewInterpreter(); - PyInterpreterRef sub_ref = PyInterpreterRef_Get(); + PyInterpreterRef sub_ref = get_strong_ref(); PyInterpreterState *subinterp = PyThreadState_GetInterpreter(tstate); for (int i = 0; i < 10; ++i) { diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 98745b65db843f..6e60765e32afeb 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2354,7 +2354,10 @@ test_thread_state_ensure(void) _testembed_initialize(); PyThread_handle_t handle; PyThread_ident_t ident; - PyInterpreterRef ref = PyInterpreterRef_Get(); + PyInterpreterRef ref; + if (PyInterpreterRef_Get(&ref) < 0) { + return -1; + }; ThreadData data = { ref }; if (PyThread_start_joinable_thread(do_tstate_ensure, &data, &ident, &handle) < 0) { diff --git a/Python/pystate.c b/Python/pystate.c index 62085b5ccb04e5..00cf426e727430 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3145,12 +3145,22 @@ _PyInterpreterState_Refcount(PyInterpreterState *interp) return refcount; } -void +int _PyInterpreterState_Incref(PyInterpreterState *interp) { assert(interp != NULL); - assert(_Py_atomic_load_ssize_relaxed(&interp->threads.finalizing.countdown) >= 0); + struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; + assert(_Py_atomic_load_ssize_relaxed(&finalizing->countdown) >= 0); + PyMutex *mutex = &finalizing->mutex; + PyMutex_Lock(mutex); + if (_PyEvent_IsSet(&finalizing->finished)) { + PyMutex_Unlock(mutex); + return -1; + } + _Py_atomic_add_ssize(&interp->threads.finalizing.countdown, 1); + PyMutex_Unlock(mutex); + return 0; } static PyInterpreterState * @@ -3164,19 +3174,29 @@ ref_as_interp(PyInterpreterRef ref) return interp; } -PyInterpreterRef -PyInterpreterRef_Get(void) +int +PyInterpreterRef_Get(PyInterpreterRef *ref_ptr) { + assert(ref_ptr != NULL); PyInterpreterState *interp = PyInterpreterState_Get(); - _PyInterpreterState_Incref(interp); - return (PyInterpreterRef)interp; + if (_PyInterpreterState_Incref(interp) < 0) { + PyErr_SetString(PyExc_PythonFinalizationError, + "Cannot acquire strong interpreter references anymore"); + return -1; + } + *ref_ptr = (PyInterpreterRef)interp; + return 0; } PyInterpreterRef PyInterpreterRef_Dup(PyInterpreterRef ref) { PyInterpreterState *interp = ref_as_interp(ref); - _PyInterpreterState_Incref(interp); + int res = _PyInterpreterState_Incref(interp); + (void)res; + // We already hold a strong reference, so it shouldn't be possible + // for the interpreter to be at a point where references don't work anymore + assert(res == 0); return (PyInterpreterRef)interp; } @@ -3195,24 +3215,48 @@ PyInterpreterRef_AsInterpreter(PyInterpreterRef ref) return interp; } -PyInterpreterWeakRef -PyInterpreterWeakRef_Get(void) +int +PyInterpreterWeakRef_Get(PyInterpreterWeakRef *wref_ptr) { PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterWeakRef wref = { interp->id }; + _PyInterpreterWeakRef *wref = PyMem_RawMalloc(sizeof(_PyInterpreterWeakRef)); + if (wref == NULL) { + PyErr_NoMemory(); + return -1; + } + wref->refcount = 1; + wref->id = interp->id; + *wref_ptr = (PyInterpreterWeakRef)wref; + return 0; +} + +static _PyInterpreterWeakRef * +wref_handle_as_ptr(PyInterpreterWeakRef wref_handle) +{ + _PyInterpreterWeakRef *wref = (_PyInterpreterWeakRef *)wref_handle; + if (wref == NULL) { + Py_FatalError("Got a null weak interpreter reference, likely due to use after close."); + } + return wref; } PyInterpreterWeakRef -PyInterpreterWeakRef_Dup(PyInterpreterWeakRef wref) +PyInterpreterWeakRef_Dup(PyInterpreterWeakRef wref_handle) { + _PyInterpreterWeakRef *wref = wref_handle_as_ptr(wref_handle); + ++wref->refcount; return wref; } +#undef PyInterpreterWeakRef_Close void -PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref) +PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref_handle) { - return; + _PyInterpreterWeakRef *wref = wref_handle_as_ptr(wref_handle); + if (--wref->refcount == 0) { + PyMem_RawFree(wref); + } } static int @@ -3234,10 +3278,11 @@ try_acquire_strong_ref(PyInterpreterState *interp, PyInterpreterRef *strong_ptr) } int -PyInterpreterWeakRef_AsStrong(PyInterpreterWeakRef wref, PyInterpreterRef *strong_ptr) +PyInterpreterWeakRef_AsStrong(PyInterpreterWeakRef wref_handle, PyInterpreterRef *strong_ptr) { assert(strong_ptr != NULL); - int64_t interp_id = wref.id; + _PyInterpreterWeakRef *wref = wref_handle_as_ptr(wref_handle); + int64_t interp_id = wref->id; /* Interpreters cannot be deleted while we hold the runtime lock. */ _PyRuntimeState *runtime = &_PyRuntime; HEAD_LOCK(runtime); @@ -3254,13 +3299,12 @@ PyInterpreterWeakRef_AsStrong(PyInterpreterWeakRef wref, PyInterpreterRef *stron } int -PyInterpreterState_AsStrong(PyInterpreterState *interp, PyInterpreterRef *strong_ptr) +PyInterpreterRef_Main(PyInterpreterRef *strong_ptr) { - assert(interp != NULL); assert(strong_ptr != NULL); _PyRuntimeState *runtime = &_PyRuntime; HEAD_LOCK(runtime); - int res = try_acquire_strong_ref(interp, strong_ptr); + int res = try_acquire_strong_ref(&runtime->_main_interpreter, strong_ptr); HEAD_UNLOCK(runtime); return res; From 4249c5d6cd0188bb719b506dcf62fe6d848a5e08 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Tue, 27 May 2025 21:19:28 -0400 Subject: [PATCH 31/85] Bikeshedding. --- Python/pylifecycle.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index bb6cd9afd03505..8cd630fc9aab8a 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -98,7 +98,7 @@ static PyStatus init_android_streams(PyThreadState *tstate); static PyStatus init_apple_streams(PyThreadState *tstate); #endif static void wait_for_thread_shutdown(PyThreadState *tstate); -static void wait_for_native_shutdown(PyInterpreterState *interp); +static void wait_for_interp_references(PyInterpreterState *interp); static void finalize_subinterpreters(void); static void call_ll_exitfuncs(_PyRuntimeState *runtime); @@ -2025,7 +2025,7 @@ _Py_Finalize(_PyRuntimeState *runtime) wait_for_thread_shutdown(tstate); // Wait for the interpreter's reference count to reach zero - wait_for_native_shutdown(tstate->interp); + wait_for_interp_references(tstate->interp); // Make any remaining pending calls. _Py_FinishPendingCalls(tstate); @@ -2444,7 +2444,7 @@ Py_EndInterpreter(PyThreadState *tstate) wait_for_thread_shutdown(tstate); // Wait for the interpreter's reference count to reach zero - wait_for_native_shutdown(tstate->interp); + wait_for_interp_references(tstate->interp); // Make any remaining pending calls. _Py_FinishPendingCalls(tstate); @@ -3472,10 +3472,10 @@ wait_for_thread_shutdown(PyThreadState *tstate) Py_DECREF(threading); } -/* Wait for all non-daemon native threads to finish. +/* Wait for the interpreter's reference count to reach zero. See PEP 788. */ static void -wait_for_native_shutdown(PyInterpreterState *interp) +wait_for_interp_references(PyInterpreterState *interp) { assert(interp != NULL); struct _Py_finalizing_threads *finalizing = &interp->threads.finalizing; From 71f2fd7d46831209a31dc18b22f493ba58852e9d Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 28 May 2025 12:43:18 -0400 Subject: [PATCH 32/85] Apply suggestions from code review Co-authored-by: Victor Stinner --- Include/cpython/pystate.h | 6 +++--- Programs/_testembed.c | 4 +++- Python/pystate.c | 10 +++++----- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 884bb026e45372..4b03691d80587c 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -279,11 +279,11 @@ PyAPI_FUNC(PyInterpreterState *) PyInterpreterRef_AsInterpreter(PyInterpreterRef #define PyInterpreterRef_Close(ref) do { \ PyInterpreterRef_Close(ref); \ ref = 0; \ -} while (0); +} while (0) /* Weak interpreter references */ -typedef struct _interpreter_weakref { +typedef struct _PyInterpreterWeakRef { int64_t id; Py_ssize_t refcount; } _PyInterpreterWeakRef; @@ -298,7 +298,7 @@ PyAPI_FUNC(void) PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref); #define PyInterpreterWeakRef_Close(ref) do { \ PyInterpreterWeakRef_Close(ref); \ ref = 0; \ -} while (0); +} while (0) // Exports for '_testcapi' shared extension PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp); diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 6e60765e32afeb..c5bb0246ce3c2b 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2313,7 +2313,9 @@ test_get_incomplete_frame(void) return result; } -const char *THREAD_CODE = "import time\n" +const char *THREAD_CODE = \ + "import time\n" + ... "time.sleep(0.2)\n" "def fib(n):\n" " if n <= 1:\n" diff --git a/Python/pystate.c b/Python/pystate.c index 00cf426e727430..af867e88858b05 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3141,8 +3141,7 @@ Py_ssize_t _PyInterpreterState_Refcount(PyInterpreterState *interp) { assert(interp != NULL); - Py_ssize_t refcount = _Py_atomic_load_ssize_relaxed(&interp->threads.finalizing.countdown); - return refcount; + return _Py_atomic_load_ssize_relaxed(&interp->threads.finalizing.countdown); } int @@ -3168,7 +3167,7 @@ ref_as_interp(PyInterpreterRef ref) { PyInterpreterState *interp = (PyInterpreterState *)ref; if (interp == NULL) { - Py_FatalError("Got a null interpreter reference, likely due to use after close."); + Py_FatalError("Got a null interpreter reference, likely due to use after PyInterpreterRef_Close()"); } return interp; @@ -3235,7 +3234,7 @@ wref_handle_as_ptr(PyInterpreterWeakRef wref_handle) { _PyInterpreterWeakRef *wref = (_PyInterpreterWeakRef *)wref_handle; if (wref == NULL) { - Py_FatalError("Got a null weak interpreter reference, likely due to use after close."); + Py_FatalError("Got a null weak interpreter reference, likely due to use after PyInterpreterWeakRef_Close()"); } return wref; @@ -3269,7 +3268,8 @@ try_acquire_strong_ref(PyInterpreterState *interp, PyInterpreterRef *strong_ptr) /* Interpreter has already finished threads */ *strong_ptr = 0; return -1; - } else { + } + else { _Py_atomic_add_ssize(&finalizing->countdown, 1); } PyMutex_Unlock(mutex); From 03ccb38c85c1f24df3411d5baee4ebe9a7b7ce5a Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 28 May 2025 20:24:42 -0400 Subject: [PATCH 33/85] Fix failing build. --- Programs/_testembed.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Programs/_testembed.c b/Programs/_testembed.c index c5bb0246ce3c2b..8fff40910d2fa1 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2314,15 +2314,14 @@ test_get_incomplete_frame(void) } const char *THREAD_CODE = \ - "import time\n" - ... - "time.sleep(0.2)\n" - "def fib(n):\n" - " if n <= 1:\n" - " return n\n" - " else:\n" - " return fib(n - 1) + fib(n - 2)\n" - "fib(10)"; + "import time\n" + "time.sleep(0.2)\n" + "def fib(n):\n" + " if n <= 1:\n" + " return n\n" + " else:\n" + " return fib(n - 1) + fib(n - 2)\n" + "fib(10)"; typedef struct { PyInterpreterRef ref; From bca65fb2f810aa4e7f981c5a4078078d702af688 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 28 May 2025 20:36:21 -0400 Subject: [PATCH 34/85] Rename parameter. --- Python/pystate.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index af867e88858b05..a55f22f020256b 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3174,16 +3174,16 @@ ref_as_interp(PyInterpreterRef ref) } int -PyInterpreterRef_Get(PyInterpreterRef *ref_ptr) +PyInterpreterRef_Get(PyInterpreterRef *ref) { - assert(ref_ptr != NULL); + assert(ref != NULL); PyInterpreterState *interp = PyInterpreterState_Get(); if (_PyInterpreterState_Incref(interp) < 0) { PyErr_SetString(PyExc_PythonFinalizationError, "Cannot acquire strong interpreter references anymore"); return -1; } - *ref_ptr = (PyInterpreterRef)interp; + *ref = (PyInterpreterRef)interp; return 0; } From 510ade14df1dc082640448ab1ff22529360d93a8 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 28 May 2025 20:37:06 -0400 Subject: [PATCH 35/85] Fix formatting. --- Lib/test/test_embed.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 24d7b99ff0ff31..7e663c035b9a2e 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -1914,11 +1914,9 @@ def test_audit_run_stdin(self): def test_get_incomplete_frame(self): self.run_embedded_interpreter("test_get_incomplete_frame") - def test_gilstate_after_finalization(self): self.run_embedded_interpreter("test_gilstate_after_finalization") - def test_thread_state_ensure(self): self.run_embedded_interpreter("test_thread_state_ensure") From 39714088c16a0c7af9b64a05e52123f9b210c0a3 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 28 May 2025 20:38:16 -0400 Subject: [PATCH 36/85] Add tstate check. --- Modules/_testcapimodule.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 971bdadd6b8065..96ab2ef53984ed 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2617,9 +2617,11 @@ test_interp_ensure(PyObject *self, PyObject *unused) PyInterpreterRef sub_ref = get_strong_ref(); PyInterpreterState *subinterp = PyThreadState_GetInterpreter(tstate); + assert(PyThreadState_GetUnchecked() == NULL); for (int i = 0; i < 10; ++i) { int res = PyThreadState_Ensure(ref); assert(res == 0); + assert(PyThreadState_GetUnchecked() != NULL); assert(PyInterpreterState_Get() == interp); } From 6c4c52bff7371f2a160d3fcc730114269f7e6894 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 28 May 2025 20:39:36 -0400 Subject: [PATCH 37/85] Move to pycore_pystate.h --- Include/cpython/pystate.h | 4 ---- Include/internal/pycore_pystate.h | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 4b03691d80587c..56ec268586c5e4 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -300,10 +300,6 @@ PyAPI_FUNC(void) PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref); ref = 0; \ } while (0) -// Exports for '_testcapi' shared extension -PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp); -PyAPI_FUNC(int) _PyInterpreterState_Incref(PyInterpreterState *interp); - PyAPI_FUNC(int) PyThreadState_Ensure(PyInterpreterRef interp_ref); PyAPI_FUNC(void) PyThreadState_Release(void); diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 633e5cf77db918..c8610e200d05f5 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -328,6 +328,10 @@ _Py_RecursionLimit_GetMargin(PyThreadState *tstate) return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, here_addr - (intptr_t)_tstate->c_stack_soft_limit, PYOS_STACK_MARGIN_SHIFT); } +// Exports for '_testcapi' shared extension +PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp); +PyAPI_FUNC(int) _PyInterpreterState_Incref(PyInterpreterState *interp); + #ifdef __cplusplus } #endif From 64920a80338e4fef5dfc14801f02ad50d7299bb1 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 28 May 2025 20:41:10 -0400 Subject: [PATCH 38/85] Revert "Move to pycore_pystate.h" This reverts commit 6c4c52bff7371f2a160d3fcc730114269f7e6894. --- Include/cpython/pystate.h | 4 ++++ Include/internal/pycore_pystate.h | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 56ec268586c5e4..4b03691d80587c 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -300,6 +300,10 @@ PyAPI_FUNC(void) PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref); ref = 0; \ } while (0) +// Exports for '_testcapi' shared extension +PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp); +PyAPI_FUNC(int) _PyInterpreterState_Incref(PyInterpreterState *interp); + PyAPI_FUNC(int) PyThreadState_Ensure(PyInterpreterRef interp_ref); PyAPI_FUNC(void) PyThreadState_Release(void); diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index c8610e200d05f5..633e5cf77db918 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -328,10 +328,6 @@ _Py_RecursionLimit_GetMargin(PyThreadState *tstate) return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, here_addr - (intptr_t)_tstate->c_stack_soft_limit, PYOS_STACK_MARGIN_SHIFT); } -// Exports for '_testcapi' shared extension -PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp); -PyAPI_FUNC(int) _PyInterpreterState_Incref(PyInterpreterState *interp); - #ifdef __cplusplus } #endif From 05436f3a2ccdf0fba03200a8b30ae613147e1e1a Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 28 May 2025 20:46:17 -0400 Subject: [PATCH 39/85] Use an exponential wait time for the event. --- Python/pylifecycle.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 8cd630fc9aab8a..f2714611611c74 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -3488,7 +3488,8 @@ wait_for_interp_references(PyInterpreterState *interp) } PyMutex_Unlock(&finalizing->mutex); - PyTime_t wait_ns = 1000 * 1000; // 1 millisecond + PyTime_t wait_max = 1000 * 1000 * 100; // 100 milliseconds + PyTime_t wait_ns = 1000; // 1 microsecond while (true) { if (PyEvent_WaitTimed(&finalizing->finished, wait_ns, 1)) { @@ -3496,14 +3497,17 @@ wait_for_interp_references(PyInterpreterState *interp) break; } + wait_ns *= 2; + wait_ns = Py_MIN(wait_ns, wait_max); + if (PyErr_CheckSignals()) { PyErr_Print(); - // The user CTRL+C'd us, bail out without waiting for a reference - // count of zero. - // - // This will probably cause threads to crash, but maybe that's - // better than a deadlock. It might be worth intentionally - // leaking subinterpreters to prevent some crashes here. + /* The user CTRL+C'd us, bail out without waiting for a reference + count of zero. + + This will probably cause threads to crash, but maybe that's + better than a deadlock. It might be worth intentionally + leaking subinterpreters to prevent some crashes here. */ break; } } From 02bc2d79b468114c49c17cef36f45da4e1a92cde Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 28 May 2025 20:47:56 -0400 Subject: [PATCH 40/85] Mark function as static. --- Python/pystate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/pystate.c b/Python/pystate.c index a55f22f020256b..771611d968d1a0 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1294,7 +1294,7 @@ interp_look_up_id(_PyRuntimeState *runtime, int64_t requested_id) return NULL; } -PyInterpreterState * +static PyInterpreterState * _PyInterpreterState_LookUpIDNoErr(int64_t requested_id) { PyInterpreterState *interp = NULL; From 03fa2af3596020941ee55f690bfe8b43246548ca Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 28 May 2025 20:49:41 -0400 Subject: [PATCH 41/85] Update fatal error message. --- Python/pystate.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Python/pystate.c b/Python/pystate.c index 771611d968d1a0..60a40e91860367 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1749,7 +1749,8 @@ decref_interpreter(PyInterpreterState *interp) if (old == 1 && shutting_down_natives(interp)) { _PyEvent_Notify(&finalizing->finished); } else if (old <= 0) { - Py_FatalError("interpreter has negative reference count"); + Py_FatalError("interpreter has negative reference count, likely due" + " to an extra PyInterpreterRef_Close()"); } } From b955d8564d00ecc27adbf01a8ee51b9094e447b0 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 28 May 2025 20:51:33 -0400 Subject: [PATCH 42/85] Remove incorrect assertion. --- Modules/_testcapimodule.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 96ab2ef53984ed..e9451b684a14c7 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2617,7 +2617,6 @@ test_interp_ensure(PyObject *self, PyObject *unused) PyInterpreterRef sub_ref = get_strong_ref(); PyInterpreterState *subinterp = PyThreadState_GetInterpreter(tstate); - assert(PyThreadState_GetUnchecked() == NULL); for (int i = 0; i < 10; ++i) { int res = PyThreadState_Ensure(ref); assert(res == 0); From 523670075c05e339fe5bc420f96ee2b6f3bbf718 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 29 May 2025 06:39:36 -0400 Subject: [PATCH 43/85] Add a comment regarding PyMem_RawMalloc() --- Python/pystate.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python/pystate.c b/Python/pystate.c index 60a40e91860367..7c2078ecc61818 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3219,6 +3219,8 @@ int PyInterpreterWeakRef_Get(PyInterpreterWeakRef *wref_ptr) { PyInterpreterState *interp = PyInterpreterState_Get(); + /* PyInterpreterWeakRef_Close() can be called without an attached thread + state, so we have to use the raw allocator. */ _PyInterpreterWeakRef *wref = PyMem_RawMalloc(sizeof(_PyInterpreterWeakRef)); if (wref == NULL) { PyErr_NoMemory(); From a2771309fb3727c3a5581c2950d6a58adf2f8bc6 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 10:33:34 -0400 Subject: [PATCH 44/85] Add a comment. --- Programs/_testembed.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 8fff40910d2fa1..7da334b54242dd 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2365,6 +2365,9 @@ test_thread_state_ensure(void) PyInterpreterRef_Close(ref); return -1; } + // We hold a strong interpreter reference, so we don't + // have to worry about the interpreter shutting down before + // we finalize. Py_Finalize(); assert(data.done == 1); return 0; From dac0c1a1b61682d547d9f0e1abc05606b8f81d87 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 10:34:38 -0400 Subject: [PATCH 45/85] Update Include/cpython/pystate.h Co-authored-by: Victor Stinner --- Include/cpython/pystate.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 4b03691d80587c..82f3886363a630 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -270,9 +270,9 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( typedef uintptr_t PyInterpreterRef; -PyAPI_FUNC(int) PyInterpreterRef_Get(PyInterpreterRef *ptr); +PyAPI_FUNC(int) PyInterpreterRef_Get(PyInterpreterRef *ref); PyAPI_FUNC(PyInterpreterRef) PyInterpreterRef_Dup(PyInterpreterRef ref); -PyAPI_FUNC(int) PyInterpreterRef_Main(PyInterpreterRef *strong_ptr); +PyAPI_FUNC(int) PyInterpreterRef_Main(PyInterpreterRef *ref); PyAPI_FUNC(void) PyInterpreterRef_Close(PyInterpreterRef ref); PyAPI_FUNC(PyInterpreterState *) PyInterpreterRef_AsInterpreter(PyInterpreterRef ref); From 79a1852e4e7e7ea245ce3918461eeda5a2fd6095 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 10:40:45 -0400 Subject: [PATCH 46/85] Move some tests around to prevent exposure of the private API. --- Include/cpython/pystate.h | 3 --- Include/internal/pycore_pystate.h | 4 +++ Modules/_testcapimodule.c | 32 ------------------------ Modules/_testinternalcapi.c | 41 +++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 35 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 82f3886363a630..76793816af7fa9 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -300,9 +300,6 @@ PyAPI_FUNC(void) PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref); ref = 0; \ } while (0) -// Exports for '_testcapi' shared extension -PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp); -PyAPI_FUNC(int) _PyInterpreterState_Incref(PyInterpreterState *interp); PyAPI_FUNC(int) PyThreadState_Ensure(PyInterpreterRef interp_ref); diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 633e5cf77db918..9f6ec120317592 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -328,6 +328,10 @@ _Py_RecursionLimit_GetMargin(PyThreadState *tstate) return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, here_addr - (intptr_t)_tstate->c_stack_soft_limit, PYOS_STACK_MARGIN_SHIFT); } +// Exports for '_testinternalcapi' shared extension +PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp); +PyAPI_FUNC(int) _PyInterpreterState_Incref(PyInterpreterState *interp); + #ifdef __cplusplus } #endif diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index e9451b684a14c7..cc2888a1bcffd3 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2556,36 +2556,6 @@ get_strong_ref(void) return ref; } -static PyObject * -test_interp_refcount(PyObject *self, PyObject *unused) -{ - PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterRef ref1; - PyInterpreterRef ref2; - - // Reference counts are technically 0 by default - assert(_PyInterpreterState_Refcount(interp) == 0); - ref1 = get_strong_ref(); - assert(_PyInterpreterState_Refcount(interp) == 1); - ref2 = get_strong_ref(); - assert(_PyInterpreterState_Refcount(interp) == 2); - PyInterpreterRef_Close(ref1); - assert(_PyInterpreterState_Refcount(interp) == 1); - PyInterpreterRef_Close(ref2); - assert(_PyInterpreterState_Refcount(interp) == 0); - - ref1 = get_strong_ref(); - ref2 = PyInterpreterRef_Dup(ref1); - assert(_PyInterpreterState_Refcount(interp) == 2); - assert(PyInterpreterRef_AsInterpreter(ref1) == interp); - assert(PyInterpreterRef_AsInterpreter(ref2) == interp); - PyInterpreterRef_Close(ref1); - PyInterpreterRef_Close(ref2); - assert(_PyInterpreterState_Refcount(interp) == 0); - - Py_RETURN_NONE; -} - static PyObject * test_interp_weak_ref(PyObject *self, PyObject *unused) { @@ -2600,7 +2570,6 @@ test_interp_weak_ref(PyObject *self, PyObject *unused) int res = PyInterpreterWeakRef_AsStrong(wref, &ref); assert(res == 0); assert(PyInterpreterRef_AsInterpreter(ref) == interp); - assert(_PyInterpreterState_Refcount(interp) == 1); PyInterpreterWeakRef_Close(wref); PyInterpreterRef_Close(ref); @@ -2735,7 +2704,6 @@ static PyMethodDef TestMethods[] = { {"test_atexit", test_atexit, METH_NOARGS}, {"code_offset_to_line", _PyCFunction_CAST(code_offset_to_line), METH_FASTCALL}, {"toggle_reftrace_printer", toggle_reftrace_printer, METH_O}, - {"test_interp_refcount", test_interp_refcount, METH_NOARGS}, {"test_interp_weak_ref", test_interp_weak_ref, METH_NOARGS}, {"test_interp_ensure", test_interp_ensure, METH_NOARGS}, {NULL, NULL} /* sentinel */ diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 136e6a7a015049..a8d0f4e1e0b38e 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2342,6 +2342,46 @@ incref_decref_delayed(PyObject *self, PyObject *op) Py_RETURN_NONE; } +static PyInterpreterRef +get_strong_ref(void) +{ + PyInterpreterRef ref; + if (PyInterpreterRef_Get(&ref) < 0) { + Py_FatalError("strong reference should not have failed"); + } + return ref; +} + +static PyObject * +test_interp_refcount(PyObject *self, PyObject *unused) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + PyInterpreterRef ref1; + PyInterpreterRef ref2; + + // Reference counts are technically 0 by default + assert(_PyInterpreterState_Refcount(interp) == 0); + ref1 = get_strong_ref(); + assert(_PyInterpreterState_Refcount(interp) == 1); + ref2 = get_strong_ref(); + assert(_PyInterpreterState_Refcount(interp) == 2); + PyInterpreterRef_Close(ref1); + assert(_PyInterpreterState_Refcount(interp) == 1); + PyInterpreterRef_Close(ref2); + assert(_PyInterpreterState_Refcount(interp) == 0); + + ref1 = get_strong_ref(); + ref2 = PyInterpreterRef_Dup(ref1); + assert(_PyInterpreterState_Refcount(interp) == 2); + assert(PyInterpreterRef_AsInterpreter(ref1) == interp); + assert(PyInterpreterRef_AsInterpreter(ref2) == interp); + PyInterpreterRef_Close(ref1); + PyInterpreterRef_Close(ref2); + assert(_PyInterpreterState_Refcount(interp) == 0); + + Py_RETURN_NONE; +} + static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, @@ -2444,6 +2484,7 @@ static PyMethodDef module_functions[] = { {"is_static_immortal", is_static_immortal, METH_O}, {"incref_decref_delayed", incref_decref_delayed, METH_O}, GET_NEXT_DICT_KEYS_VERSION_METHODDEF + {"test_interp_refcount", test_interp_refcount, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; From 47957b8c890b463214c5600d6305a3c54040d7ce Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 10:42:48 -0400 Subject: [PATCH 47/85] Move weakref test to internal C API. --- Modules/_testcapimodule.c | 21 --------------------- Modules/_testinternalcapi.c | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index cc2888a1bcffd3..66368828112611 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2556,26 +2556,6 @@ get_strong_ref(void) return ref; } -static PyObject * -test_interp_weak_ref(PyObject *self, PyObject *unused) -{ - PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterWeakRef wref; - if (PyInterpreterWeakRef_Get(&wref) < 0) { - return NULL; - } - assert(_PyInterpreterState_Refcount(interp) == 0); - - PyInterpreterRef ref; - int res = PyInterpreterWeakRef_AsStrong(wref, &ref); - assert(res == 0); - assert(PyInterpreterRef_AsInterpreter(ref) == interp); - PyInterpreterWeakRef_Close(wref); - PyInterpreterRef_Close(ref); - - Py_RETURN_NONE; -} - static PyObject * test_interp_ensure(PyObject *self, PyObject *unused) { @@ -2704,7 +2684,6 @@ static PyMethodDef TestMethods[] = { {"test_atexit", test_atexit, METH_NOARGS}, {"code_offset_to_line", _PyCFunction_CAST(code_offset_to_line), METH_FASTCALL}, {"toggle_reftrace_printer", toggle_reftrace_printer, METH_O}, - {"test_interp_weak_ref", test_interp_weak_ref, METH_NOARGS}, {"test_interp_ensure", test_interp_ensure, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index a8d0f4e1e0b38e..6e2a311b6ce57d 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2382,6 +2382,27 @@ test_interp_refcount(PyObject *self, PyObject *unused) Py_RETURN_NONE; } +static PyObject * +test_interp_weakref_incref(PyObject *self, PyObject *unused) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + PyInterpreterWeakRef wref; + if (PyInterpreterWeakRef_Get(&wref) < 0) { + return NULL; + } + assert(_PyInterpreterState_Refcount(interp) == 0); + + PyInterpreterRef ref; + int res = PyInterpreterWeakRef_AsStrong(wref, &ref); + assert(res == 0); + assert(PyInterpreterRef_AsInterpreter(ref) == interp); + assert(_PyInterpreterState_Refcount(interp) == 1); + PyInterpreterWeakRef_Close(wref); + PyInterpreterRef_Close(ref); + + Py_RETURN_NONE; +} + static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, @@ -2485,6 +2506,7 @@ static PyMethodDef module_functions[] = { {"incref_decref_delayed", incref_decref_delayed, METH_O}, GET_NEXT_DICT_KEYS_VERSION_METHODDEF {"test_interp_refcount", test_interp_refcount, METH_NOARGS}, + {"test_interp_weakref_incref", test_interp_weakref_incref, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; From 771d7edf0a92afcb6200998f31d7738e6fa9d32d Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 10:48:21 -0400 Subject: [PATCH 48/85] Improve reference counting tests. --- Modules/_testinternalcapi.c | 54 ++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 6e2a311b6ce57d..4a545bec8ff4e1 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2352,32 +2352,23 @@ get_strong_ref(void) return ref; } +#define NUM_REFS 100 + static PyObject * test_interp_refcount(PyObject *self, PyObject *unused) { PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterRef ref1; - PyInterpreterRef ref2; - - // Reference counts are technically 0 by default - assert(_PyInterpreterState_Refcount(interp) == 0); - ref1 = get_strong_ref(); - assert(_PyInterpreterState_Refcount(interp) == 1); - ref2 = get_strong_ref(); - assert(_PyInterpreterState_Refcount(interp) == 2); - PyInterpreterRef_Close(ref1); - assert(_PyInterpreterState_Refcount(interp) == 1); - PyInterpreterRef_Close(ref2); assert(_PyInterpreterState_Refcount(interp) == 0); + PyInterpreterRef refs[NUM_REFS]; + for (int i = 0; i < NUM_REFS; ++i) { + int res = PyInterpreterRef_Get(&refs[i]); + assert(_PyInterpreterState_Refcount(interp) == i + 1); + } - ref1 = get_strong_ref(); - ref2 = PyInterpreterRef_Dup(ref1); - assert(_PyInterpreterState_Refcount(interp) == 2); - assert(PyInterpreterRef_AsInterpreter(ref1) == interp); - assert(PyInterpreterRef_AsInterpreter(ref2) == interp); - PyInterpreterRef_Close(ref1); - PyInterpreterRef_Close(ref2); - assert(_PyInterpreterState_Refcount(interp) == 0); + for (int i = 0; i < NUM_REFS; ++i) { + PyInterpreterRef_Close(refs[i]); + assert(_PyInterpreterState_Refcount(interp) == (NUM_REFS - i)); + } Py_RETURN_NONE; } @@ -2392,17 +2383,26 @@ test_interp_weakref_incref(PyObject *self, PyObject *unused) } assert(_PyInterpreterState_Refcount(interp) == 0); - PyInterpreterRef ref; - int res = PyInterpreterWeakRef_AsStrong(wref, &ref); - assert(res == 0); - assert(PyInterpreterRef_AsInterpreter(ref) == interp); - assert(_PyInterpreterState_Refcount(interp) == 1); - PyInterpreterWeakRef_Close(wref); - PyInterpreterRef_Close(ref); + PyInterpreterRef refs[NUM_REFS]; + + for (int i = 0; i < NUM_REFS; ++i) { + int res = PyInterpreterWeakRef_AsStrong(wref, &refs[i]); + assert(res == 0); + assert(PyInterpreterRef_AsInterpreter(refs[i]) == interp); + assert(_PyInterpreterState_Refcount(interp) == i + 1); + } + for (int i = 0; i < NUM_REFS; ++i) { + PyInterpreterRef_Close(refs[i]); + assert(_PyInterpreterState_Refcount(interp) == (NUM_REFS - i)); + } + + PyInterpreterWeakRef_Close(wref); Py_RETURN_NONE; } +#undef NUM_REFS + static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, From 0c3c1c7f06791149e44568758cda4dd9458f34ed Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 10:48:39 -0400 Subject: [PATCH 49/85] Remove dead function. --- Modules/_testinternalcapi.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 4a545bec8ff4e1..35f9a56d226aea 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2342,16 +2342,6 @@ incref_decref_delayed(PyObject *self, PyObject *op) Py_RETURN_NONE; } -static PyInterpreterRef -get_strong_ref(void) -{ - PyInterpreterRef ref; - if (PyInterpreterRef_Get(&ref) < 0) { - Py_FatalError("strong reference should not have failed"); - } - return ref; -} - #define NUM_REFS 100 static PyObject * From 531928eb2a586e33f1b5ae495afcc650e989089e Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 11:11:03 -0400 Subject: [PATCH 50/85] Add some more tests. --- Modules/_testcapimodule.c | 96 +++++++++++++++++++++++++++++++-------- 1 file changed, 78 insertions(+), 18 deletions(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 66368828112611..0e7118363ecab7 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -14,6 +14,8 @@ #include "frameobject.h" // PyFrame_New() #include "marshal.h" // PyMarshal_WriteLongToFile() +#include "pylifecycle.h" +#include "pystate.h" #include // FLT_MAX #include @@ -2556,38 +2558,95 @@ get_strong_ref(void) return ref; } -static PyObject * -test_interp_ensure(PyObject *self, PyObject *unused) +static void +test_interp_ref_common(void) { PyInterpreterState *interp = PyInterpreterState_Get(); PyInterpreterRef ref = get_strong_ref(); + assert(PyInterpreterRef_AsInterpreter(ref) == interp); + + PyInterpreterRef ref_2 = PyInterpreterRef_Dup(ref); + assert(PyInterpreterRef_AsInterpreter(ref_2) == interp); + + // We can close the references in any order + PyInterpreterRef_Close(ref); + PyInterpreterRef_Close(ref_2); +} + +static PyObject * +test_interpreter_refs(PyObject *self, PyObject *unused) +{ + // Test the main interpreter + test_interp_ref_common(); + + // Test a (legacy) subinterpreter PyThreadState *save_tstate = PyThreadState_Swap(NULL); - PyThreadState *tstate = Py_NewInterpreter(); - PyInterpreterRef sub_ref = get_strong_ref(); - PyInterpreterState *subinterp = PyThreadState_GetInterpreter(tstate); + PyThreadState *interp_tstate = Py_NewInterpreter(); + test_interp_ref_common(); + Py_EndInterpreter(interp_tstate); + + // Test an isolated subinterpreter + PyInterpreterConfig config = { + .gil = PyInterpreterConfig_OWN_GIL, + .check_multi_interp_extensions = 1 + }; + + PyThreadState *isolated_interp_tstate; + PyStatus status = Py_NewInterpreterFromConfig(&isolated_interp_tstate, &config); + if (PyStatus_Exception(status)) { + PyErr_SetString(PyExc_RuntimeError, "interpreter creation failed"); + return NULL; + } + + test_interp_ref_common(); + Py_EndInterpreter(isolated_interp_tstate); + PyThreadState_Swap(save_tstate); + Py_RETURN_NONE; +} + +static PyObject * +test_thread_state_ensure_nested(PyObject *self, PyObject *unused) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + PyThreadState *save_tstate = PyThreadState_Swap(NULL); + assert(PyGILState_GetThisThreadState() == save_tstate); + PyInterpreterRef ref = get_strong_ref(); for (int i = 0; i < 10; ++i) { - int res = PyThreadState_Ensure(ref); - assert(res == 0); - assert(PyThreadState_GetUnchecked() != NULL); - assert(PyInterpreterState_Get() == interp); + // Test reactivation of the detached tstate. + if (PyThreadState_Ensure(ref) < 0) { + PyInterpreterRef_Close(ref); + return PyErr_NoMemory(); + } + + // No new thread state should've been created. + assert(PyThreadState_Get() == save_tstate); + PyThreadState_Release(); } + assert(PyThreadState_GetUnchecked() == NULL); + + // Similarly, test ensuring with deep nesting and *then* releasing. + // If the (detached) gilstate matches the interpreter, then it shouldn't + // create a new thread state. for (int i = 0; i < 10; ++i) { - int res = PyThreadState_Ensure(sub_ref); - assert(res == 0); - assert(PyInterpreterState_Get() == subinterp); + if (PyThreadState_Ensure(ref) < 0) { + // This will technically leak other thread states, but it doesn't + // matter because this is a test. + PyInterpreterRef_Close(ref); + return PyErr_NoMemory(); + } + + assert(PyThreadState_Get() == save_tstate); } - for (int i = 0; i < 20; ++i) { + for (int i = 0; i < 10; ++i) { + assert(PyThreadState_Get() == save_tstate); PyThreadState_Release(); } + assert(PyThreadState_GetUnchecked() == NULL); PyInterpreterRef_Close(ref); - PyInterpreterRef_Close(sub_ref); - - PyThreadState_Swap(save_tstate); - Py_RETURN_NONE; } static PyMethodDef TestMethods[] = { @@ -2684,7 +2743,8 @@ static PyMethodDef TestMethods[] = { {"test_atexit", test_atexit, METH_NOARGS}, {"code_offset_to_line", _PyCFunction_CAST(code_offset_to_line), METH_FASTCALL}, {"toggle_reftrace_printer", toggle_reftrace_printer, METH_O}, - {"test_interp_ensure", test_interp_ensure, METH_NOARGS}, + {"test_interpreter_refs", test_interpreter_refs, METH_NOARGS}, + {"test_thread_state_ensure_nested", test_thread_state_ensure_nested, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; From 08a8af6257c1f9a1b0a22aa3d43a10196e187046 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 11:13:33 -0400 Subject: [PATCH 51/85] Remove unused variables. --- Modules/_testcapimodule.c | 2 +- Modules/_testinternalcapi.c | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 0e7118363ecab7..e4d548a76f63fc 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2607,7 +2607,6 @@ test_interpreter_refs(PyObject *self, PyObject *unused) static PyObject * test_thread_state_ensure_nested(PyObject *self, PyObject *unused) { - PyInterpreterState *interp = PyInterpreterState_Get(); PyThreadState *save_tstate = PyThreadState_Swap(NULL); assert(PyGILState_GetThisThreadState() == save_tstate); PyInterpreterRef ref = get_strong_ref(); @@ -2647,6 +2646,7 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) assert(PyThreadState_GetUnchecked() == NULL); PyInterpreterRef_Close(ref); + Py_RETURN_NONE; } static PyMethodDef TestMethods[] = { diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 35f9a56d226aea..d5f983b14c4d76 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2352,6 +2352,8 @@ test_interp_refcount(PyObject *self, PyObject *unused) PyInterpreterRef refs[NUM_REFS]; for (int i = 0; i < NUM_REFS; ++i) { int res = PyInterpreterRef_Get(&refs[i]); + (void)res; + assert(res == 0); assert(_PyInterpreterState_Refcount(interp) == i + 1); } @@ -2377,6 +2379,7 @@ test_interp_weakref_incref(PyObject *self, PyObject *unused) for (int i = 0; i < NUM_REFS; ++i) { int res = PyInterpreterWeakRef_AsStrong(wref, &refs[i]); + (void)res; assert(res == 0); assert(PyInterpreterRef_AsInterpreter(refs[i]) == interp); assert(_PyInterpreterState_Refcount(interp) == i + 1); From 02f93bccfe18a492e1f27718c061972f6cc24863 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 11:17:48 -0400 Subject: [PATCH 52/85] Fix some thread state attachment problems. --- Modules/_testcapimodule.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index e4d548a76f63fc..2a5d8146a5946c 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2607,9 +2607,9 @@ test_interpreter_refs(PyObject *self, PyObject *unused) static PyObject * test_thread_state_ensure_nested(PyObject *self, PyObject *unused) { + PyInterpreterRef ref = get_strong_ref(); PyThreadState *save_tstate = PyThreadState_Swap(NULL); assert(PyGILState_GetThisThreadState() == save_tstate); - PyInterpreterRef ref = get_strong_ref(); for (int i = 0; i < 10; ++i) { // Test reactivation of the detached tstate. @@ -2646,6 +2646,7 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) assert(PyThreadState_GetUnchecked() == NULL); PyInterpreterRef_Close(ref); + PyThreadState_Swap(save_tstate); Py_RETURN_NONE; } From d6c82bd7071f13649d618891b79f209f1610cf70 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 11:40:26 -0400 Subject: [PATCH 53/85] Only delete thread states created by PyThreadState_Ensure() --- Include/cpython/pystate.h | 16 ++++++++++++---- Python/pystate.c | 23 ++++++++++++++--------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 76793816af7fa9..abfe137e5905b4 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -207,11 +207,19 @@ struct _ts { PyObject *threading_local_sentinel; _PyRemoteDebuggerSupport remote_debugger_support; - /* Number of nested PyThreadState_Ensure() calls on this thread state */ - Py_ssize_t ensure_counter; + struct { + /* Number of nested PyThreadState_Ensure() calls on this thread state */ + Py_ssize_t counter; + + /* Thread state that was active before PyThreadState_Ensure() was called. */ + PyThreadState *prior_tstate; + + /* Should this thread state be deleted upon calling + PyThreadState_Release() (with the counter at 1)? - /* Thread state that was active before PyThreadState_Ensure() was called. */ - PyThreadState *prior_ensure; + This is only true for thread states created by PyThreadState_Ensure() */ + int delete_on_release; + } ensure; }; /* other API */ diff --git a/Python/pystate.c b/Python/pystate.c index 7c2078ecc61818..e31a58f0a36a84 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3320,7 +3320,7 @@ PyThreadState_Ensure(PyInterpreterRef interp_ref) PyThreadState *attached_tstate = current_fast_get(); if (attached_tstate != NULL && attached_tstate->interp == interp) { /* Yay! We already have an attached thread state that matches. */ - ++attached_tstate->ensure_counter; + ++attached_tstate->ensure.counter; return 0; } @@ -3328,7 +3328,7 @@ PyThreadState_Ensure(PyInterpreterRef interp_ref) if (detached_gilstate != NULL && detached_gilstate->interp == interp) { /* There's a detached thread state that works. */ assert(attached_tstate == NULL); - ++detached_gilstate->ensure_counter; + ++detached_gilstate->ensure.counter; _PyThreadState_Attach(detached_gilstate); return 0; } @@ -3338,10 +3338,11 @@ PyThreadState_Ensure(PyInterpreterRef interp_ref) if (fresh_tstate == NULL) { return -1; } - fresh_tstate->ensure_counter = 1; + fresh_tstate->ensure.counter = 1; + fresh_tstate->ensure.delete_on_release = 1; if (attached_tstate != NULL) { - fresh_tstate->prior_ensure = PyThreadState_Swap(fresh_tstate); + fresh_tstate->ensure.prior_tstate = PyThreadState_Swap(fresh_tstate); } else { _PyThreadState_Attach(fresh_tstate); } @@ -3354,15 +3355,19 @@ PyThreadState_Release(void) { PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); - Py_ssize_t remaining = --tstate->ensure_counter; + Py_ssize_t remaining = --tstate->ensure.counter; if (remaining < 0) { Py_FatalError("PyThreadState_Release() called more times than PyThreadState_Ensure()"); } + PyThreadState *to_restore = tstate->ensure.prior_tstate; if (remaining == 0) { - PyThreadState *to_restore = tstate->prior_ensure; - PyThreadState_Clear(tstate); - PyThreadState_Swap(to_restore); - PyThreadState_Delete(tstate); + if (tstate->ensure.delete_on_release) { + PyThreadState_Clear(tstate); + PyThreadState_Swap(to_restore); + PyThreadState_Delete(tstate); + } else { + PyThreadState_Swap(to_restore); + } } return; From 082fd69b89bdba32c7b8547125c6c62f33fddc36 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 12:16:58 -0400 Subject: [PATCH 54/85] Add a test for crossinterpreter ensures. --- Modules/_testcapimodule.c | 53 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 2a5d8146a5946c..e2768d7d816bf6 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2650,6 +2650,58 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) Py_RETURN_NONE; } +static PyObject * +test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) +{ + PyInterpreterRef ref = get_strong_ref(); + PyThreadState *save_tstate = PyThreadState_Swap(NULL); + PyThreadState *interp_tstate = Py_NewInterpreter(); + + /* This should create a new thread state for the calling interpreter, *not* + reactivate the old one. In a real-world scenario, this would arise in + something like this: + + def some_func(): + import something + # This re-enters the main interpreter, but we + # shouldn't have access to prior thread-locals. + something.call_something() + + interp = interpreters.create() + interp.exec(some_func) + */ + if (PyThreadState_Ensure(ref) < 0) { + PyInterpreterRef_Close(ref); + return PyErr_NoMemory(); + } + + PyThreadState *ensured_tstate = PyThreadState_Get(); + assert(ensured_tstate != save_tstate); + assert(PyInterpreterState_Get() == PyInterpreterRef_AsInterpreter(ref)); + assert(PyGILState_GetThisThreadState() == ensured_tstate); + + // Now though, we should reactivate the thread state + if (PyThreadState_Ensure(ref) < 0) { + PyInterpreterRef_Close(ref); + return PyErr_NoMemory(); + } + + assert(PyThreadState_Get() == ensured_tstate); + PyThreadState_Release(); + + // Ensure that we're restoring the prior thread state + PyThreadState_Release(); + assert(PyThreadState_Get() == interp_tstate); + assert(PyGILState_GetThisThreadState() == interp_tstate); + + PyThreadState_Swap(interp_tstate); + Py_EndInterpreter(interp_tstate); + + PyInterpreterRef_Close(ref); + PyThreadState_Swap(save_tstate); + Py_RETURN_NONE; +} + static PyMethodDef TestMethods[] = { {"set_errno", set_errno, METH_VARARGS}, {"test_config", test_config, METH_NOARGS}, @@ -2746,6 +2798,7 @@ static PyMethodDef TestMethods[] = { {"toggle_reftrace_printer", toggle_reftrace_printer, METH_O}, {"test_interpreter_refs", test_interpreter_refs, METH_NOARGS}, {"test_thread_state_ensure_nested", test_thread_state_ensure_nested, METH_NOARGS}, + {"test_thread_state_ensure_crossinterp", test_thread_state_ensure_crossinterp, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; From 61f70aed0bd9c3efd1e02b1ac166e672d20f01a7 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 12:23:54 -0400 Subject: [PATCH 55/85] Add a test for weak interpreter references. --- Modules/_testcapimodule.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index e2768d7d816bf6..7aab3468e0e2e8 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -14,6 +14,7 @@ #include "frameobject.h" // PyFrame_New() #include "marshal.h" // PyMarshal_WriteLongToFile() +#include "pyerrors.h" #include "pylifecycle.h" #include "pystate.h" @@ -2656,6 +2657,10 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) PyInterpreterRef ref = get_strong_ref(); PyThreadState *save_tstate = PyThreadState_Swap(NULL); PyThreadState *interp_tstate = Py_NewInterpreter(); + if (interp_tstate == NULL) { + PyInterpreterRef_Close(ref); + return PyErr_NoMemory(); + } /* This should create a new thread state for the calling interpreter, *not* reactivate the old one. In a real-world scenario, this would arise in @@ -2702,6 +2707,36 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) Py_RETURN_NONE; } +static PyObject * +test_weak_interpreter_ref_after_shutdown(PyObject *self, PyObject *unused) +{ + PyThreadState *save_tstate = PyThreadState_Swap(NULL); + PyInterpreterWeakRef wref; + PyThreadState *interp_tstate = Py_NewInterpreter(); + if (interp_tstate == NULL) { + return PyErr_NoMemory(); + } + + int res = PyInterpreterWeakRef_Get(&wref); + (void)res; + assert(res == 0); + + // As a sanity check, ensure that the weakref actually works + PyInterpreterRef ref; + res = PyInterpreterWeakRef_AsStrong(wref, &ref); + assert(res == 0); + PyInterpreterRef_Close(ref); + + // Now, destroy the interpreter and try to acquire a weak reference. + // It should fail. + Py_EndInterpreter(interp_tstate); + res = PyInterpreterWeakRef_AsStrong(wref, &ref); + assert(res == -1); + + PyThreadState_Swap(save_tstate); + Py_RETURN_NONE; +} + static PyMethodDef TestMethods[] = { {"set_errno", set_errno, METH_VARARGS}, {"test_config", test_config, METH_NOARGS}, @@ -2799,6 +2834,7 @@ static PyMethodDef TestMethods[] = { {"test_interpreter_refs", test_interpreter_refs, METH_NOARGS}, {"test_thread_state_ensure_nested", test_thread_state_ensure_nested, METH_NOARGS}, {"test_thread_state_ensure_crossinterp", test_thread_state_ensure_crossinterp, METH_NOARGS}, + {"test_weak_interpreter_ref_after_shutdown", test_weak_interpreter_ref_after_shutdown, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; From fa961e9191e1014fe0338789dd2c750d3bbd0103 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 12:30:09 -0400 Subject: [PATCH 56/85] Fix concurrent shutdown races in PyGILState_Ensure(). --- Python/pystate.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index e31a58f0a36a84..9255e79dcfacaa 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2759,30 +2759,24 @@ PyGILState_Check(void) PyGILState_STATE PyGILState_Ensure(void) { - _PyRuntimeState *runtime = &_PyRuntime; - /* Note that we do not auto-init Python here - apart from potential races with 2 threads auto-initializing, pep-311 spells out other issues. Embedders are expected to have called Py_Initialize(). */ - /* Ensure that _PyEval_InitThreads() and _PyGILState_Init() have been - called by Py_Initialize() - - TODO: This isn't thread-safe. There's no protection here against - concurrent finalization of the interpreter; it's simply a guard - for *after* the interpreter has finalized. - */ - if (!_PyEval_ThreadsInitialized() || runtime->gilstate.autoInterpreterState == NULL) { - PyThread_hang_thread(); - } - PyThreadState *tcur = gilstate_get(); int has_gil; + PyInterpreterRef ref; if (tcur == NULL) { /* Create a new Python thread state for this thread */ // XXX Use PyInterpreterState_EnsureThreadState()? - tcur = new_threadstate(runtime->gilstate.autoInterpreterState, + if (PyInterpreterRef_Main(&ref) < 0) { + // The main interpreter has finished, so we don't have + // any intepreter to make a thread state for. Hang the + // thread to act as failure. + PyThread_hang_thread(); + } + tcur = new_threadstate(PyInterpreterRef_AsInterpreter(ref), _PyThreadState_WHENCE_GILSTATE); if (tcur == NULL) { Py_FatalError("Couldn't create thread-state for new thread"); @@ -2804,6 +2798,10 @@ PyGILState_Ensure(void) PyEval_RestoreThread(tcur); } + if (tcur == NULL) { + PyInterpreterRef_Close(ref); + } + /* Update our counter in the thread-state - no need for locks: - tcur will remain valid as we hold the GIL. - the counter is safe as we are the only thread "allowed" From ea1da77fce1f14f42cf1e38ce4b95fbdf5dd8a8a Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 12:36:59 -0400 Subject: [PATCH 57/85] Add a test for PyInterpreterRef_Main(). --- Lib/test/test_embed.py | 3 +++ Programs/_testembed.c | 26 ++++++++++++++++++++++++++ Python/pystate.c | 6 ++++++ 3 files changed, 35 insertions(+) diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 7e663c035b9a2e..124213b9ca36e6 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -1920,6 +1920,9 @@ def test_gilstate_after_finalization(self): def test_thread_state_ensure(self): self.run_embedded_interpreter("test_thread_state_ensure") + def test_main_interpreter_ref(self): + self.run_embedded_interpreter("test_main_interpreter_ref") + class MiscTests(EmbeddingTestsMixin, unittest.TestCase): def test_unicode_id_init(self): diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 7da334b54242dd..3d98e8f0b79d28 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2400,6 +2400,31 @@ test_gilstate_after_finalization(void) return PyThread_detach_thread(handle); } +static int +test_main_interpreter_ref(void) +{ + // It should not work before the runtime has started. + PyInterpreterRef ref; + int res = PyInterpreterRef_Main(&ref); + (void)res; + assert(res == -1); + + _testembed_initialize(); + + // Main interpreter is initialized and ready. + res = PyInterpreterRef_Main(&ref); + assert(res == 0); + assert(PyInterpreterRef_AsInterpreter(ref) == PyInterpreterState_Main()); + PyInterpreterRef_Close(ref); + + Py_Finalize(); + + // Main interpreter is dead, we can no longer acquire references to it. + res = PyInterpreterRef_Main(&ref); + assert(res == -1); + return 0; +} + /* ********************************************************* * List of test cases and the function that implements it. * @@ -2491,6 +2516,7 @@ static struct TestCase TestCases[] = { {"test_get_incomplete_frame", test_get_incomplete_frame}, {"test_thread_state_ensure", test_thread_state_ensure}, {"test_gilstate_after_finalization", test_gilstate_after_finalization}, + {"test_main_interpreter_ref", test_main_interpreter_ref}, {NULL, NULL} }; diff --git a/Python/pystate.c b/Python/pystate.c index 9255e79dcfacaa..95a74664a8948e 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3305,6 +3305,12 @@ PyInterpreterRef_Main(PyInterpreterRef *strong_ptr) assert(strong_ptr != NULL); _PyRuntimeState *runtime = &_PyRuntime; HEAD_LOCK(runtime); + if (runtime->initialized == 0) { + // Main interpreter is not initialized. + // This can be the case before Py_Initialize(), or after Py_Finalize(). + HEAD_UNLOCK(runtime); + return -1; + } int res = try_acquire_strong_ref(&runtime->_main_interpreter, strong_ptr); HEAD_UNLOCK(runtime); From 6f193841b778a931e8df5c0a3df1c05a1ddd5ece Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 12:53:27 -0400 Subject: [PATCH 58/85] Use PyErr_FormatUnraisable to show signals. --- Python/pylifecycle.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index f2714611611c74..a0a5617c2c39ad 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -37,6 +37,7 @@ #include "pycore_weakref.h" // _PyWeakref_GET_REF() #include "opcode.h" +#include "pyerrors.h" #include // setlocale() #include // getenv() @@ -3501,7 +3502,7 @@ wait_for_interp_references(PyInterpreterState *interp) wait_ns = Py_MIN(wait_ns, wait_max); if (PyErr_CheckSignals()) { - PyErr_Print(); + PyErr_FormatUnraisable("Exception ignored while waiting on interpreter shutdown"); /* The user CTRL+C'd us, bail out without waiting for a reference count of zero. From b702da2d22b19cd79c4e34e0650be2b87906a6b2 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 12:59:43 -0400 Subject: [PATCH 59/85] Fix a re-entrancy deadlock. --- Python/pystate.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index 95a74664a8948e..25782e78f8a074 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2789,19 +2789,17 @@ PyGILState_Ensure(void) assert(tcur->gilstate_counter == 1); tcur->gilstate_counter = 0; has_gil = 0; /* new thread state is never current */ + PyInterpreterRef_Close(ref); } else { has_gil = holds_gil(tcur); } if (!has_gil) { + // XXX Do we need to protect this against finalization? PyEval_RestoreThread(tcur); } - if (tcur == NULL) { - PyInterpreterRef_Close(ref); - } - /* Update our counter in the thread-state - no need for locks: - tcur will remain valid as we hold the GIL. - the counter is safe as we are the only thread "allowed" From 40e7e68dad88082f02d1a95c935fc021213ea57a Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 1 Jun 2025 13:12:14 -0400 Subject: [PATCH 60/85] Remove stupid IDE imports. --- Modules/_testcapimodule.c | 3 --- Python/pylifecycle.c | 2 -- 2 files changed, 5 deletions(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 7aab3468e0e2e8..dad49b0f1ba8ea 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -14,9 +14,6 @@ #include "frameobject.h" // PyFrame_New() #include "marshal.h" // PyMarshal_WriteLongToFile() -#include "pyerrors.h" -#include "pylifecycle.h" -#include "pystate.h" #include // FLT_MAX #include diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index a0a5617c2c39ad..2eede55ff7115d 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -13,7 +13,6 @@ #include "pycore_freelist.h" // _PyObject_ClearFreeLists() #include "pycore_global_objects_fini_generated.h" // _PyStaticObjects_CheckRefcnt() #include "pycore_initconfig.h" // _PyStatus_OK() -#include "pycore_interp_structs.h" #include "pycore_interpolation.h" // _PyInterpolation_InitTypes() #include "pycore_long.h" // _PyLong_InitTypes() #include "pycore_object.h" // _PyDebug_PrintTotalRefs() @@ -37,7 +36,6 @@ #include "pycore_weakref.h" // _PyWeakref_GET_REF() #include "opcode.h" -#include "pyerrors.h" #include // setlocale() #include // getenv() From 2c52cdcb45e913f1ee1a9e55ffc4c5c2fa006cb1 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Jun 2025 11:48:58 -0400 Subject: [PATCH 61/85] Fix interpreter reference count tests. --- Modules/_testinternalcapi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index adc16dca931b4a..de0c179ae30b07 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2362,7 +2362,7 @@ test_interp_refcount(PyObject *self, PyObject *unused) for (int i = 0; i < NUM_REFS; ++i) { PyInterpreterRef_Close(refs[i]); - assert(_PyInterpreterState_Refcount(interp) == (NUM_REFS - i)); + assert(_PyInterpreterState_Refcount(interp) == (NUM_REFS - i - 1)); } Py_RETURN_NONE; @@ -2390,7 +2390,7 @@ test_interp_weakref_incref(PyObject *self, PyObject *unused) for (int i = 0; i < NUM_REFS; ++i) { PyInterpreterRef_Close(refs[i]); - assert(_PyInterpreterState_Refcount(interp) == (NUM_REFS - i)); + assert(_PyInterpreterState_Refcount(interp) == (NUM_REFS - i - 1)); } PyInterpreterWeakRef_Close(wref); From 588364cf30aa4e292307e58b2c8948b67ce8811e Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 9 Jul 2025 16:25:38 -0400 Subject: [PATCH 62/85] Add thread state references to PyThreadState_Ensure() and PyThreadState_Release() Per the new updates to PEP-788. --- Include/cpython/pystate.h | 11 ++++++----- Modules/_testcapimodule.c | 19 +++++++++++-------- Programs/_testembed.c | 17 +++++++++-------- Python/pystate.c | 11 +++++++---- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index d737bbef31dbef..72e1c2e10b53ff 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -213,9 +213,6 @@ struct _ts { /* Number of nested PyThreadState_Ensure() calls on this thread state */ Py_ssize_t counter; - /* Thread state that was active before PyThreadState_Ensure() was called. */ - PyThreadState *prior_tstate; - /* Should this thread state be deleted upon calling PyThreadState_Release() (with the counter at 1)? @@ -311,6 +308,10 @@ PyAPI_FUNC(void) PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref); } while (0) -PyAPI_FUNC(int) PyThreadState_Ensure(PyInterpreterRef interp_ref); +/* Thread references */ + +typedef uintptr_t PyThreadRef; + +PyAPI_FUNC(int) PyThreadState_Ensure(PyInterpreterRef interp_ref, PyThreadRef *thread_ref); -PyAPI_FUNC(void) PyThreadState_Release(void); +PyAPI_FUNC(void) PyThreadState_Release(PyThreadRef thread_ref); diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index f85667f91f01ff..8cf558d668b0d1 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2608,17 +2608,18 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) PyInterpreterRef ref = get_strong_ref(); PyThreadState *save_tstate = PyThreadState_Swap(NULL); assert(PyGILState_GetThisThreadState() == save_tstate); + PyThreadRef refs[10]; for (int i = 0; i < 10; ++i) { // Test reactivation of the detached tstate. - if (PyThreadState_Ensure(ref) < 0) { + if (PyThreadState_Ensure(ref, &refs[i]) < 0) { PyInterpreterRef_Close(ref); return PyErr_NoMemory(); } // No new thread state should've been created. assert(PyThreadState_Get() == save_tstate); - PyThreadState_Release(); + PyThreadState_Release(refs[i]); } assert(PyThreadState_GetUnchecked() == NULL); @@ -2627,7 +2628,7 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) // If the (detached) gilstate matches the interpreter, then it shouldn't // create a new thread state. for (int i = 0; i < 10; ++i) { - if (PyThreadState_Ensure(ref) < 0) { + if (PyThreadState_Ensure(ref, &refs[i]) < 0) { // This will technically leak other thread states, but it doesn't // matter because this is a test. PyInterpreterRef_Close(ref); @@ -2639,7 +2640,7 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) for (int i = 0; i < 10; ++i) { assert(PyThreadState_Get() == save_tstate); - PyThreadState_Release(); + PyThreadState_Release(refs[i]); } assert(PyThreadState_GetUnchecked() == NULL); @@ -2672,7 +2673,9 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) interp = interpreters.create() interp.exec(some_func) */ - if (PyThreadState_Ensure(ref) < 0) { + PyThreadRef thread_ref; + PyThreadRef other_thread_ref; + if (PyThreadState_Ensure(ref, &thread_ref) < 0) { PyInterpreterRef_Close(ref); return PyErr_NoMemory(); } @@ -2683,16 +2686,16 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) assert(PyGILState_GetThisThreadState() == ensured_tstate); // Now though, we should reactivate the thread state - if (PyThreadState_Ensure(ref) < 0) { + if (PyThreadState_Ensure(ref, &other_thread_ref) < 0) { PyInterpreterRef_Close(ref); return PyErr_NoMemory(); } assert(PyThreadState_Get() == ensured_tstate); - PyThreadState_Release(); + PyThreadState_Release(other_thread_ref); // Ensure that we're restoring the prior thread state - PyThreadState_Release(); + PyThreadState_Release(thread_ref); assert(PyThreadState_Get() == interp_tstate); assert(PyGILState_GetThisThreadState() == interp_tstate); diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 3d98e8f0b79d28..50235b21d5b6f4 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2332,19 +2332,20 @@ static void do_tstate_ensure(void *arg) { ThreadData *data = (ThreadData *)arg; - int res = PyThreadState_Ensure(data->ref); + PyThreadRef refs[4]; + int res = PyThreadState_Ensure(data->ref, &refs[0]); assert(res == 0); - PyThreadState_Ensure(data->ref); - PyThreadState_Ensure(data->ref); + PyThreadState_Ensure(data->ref, &refs[1]); + PyThreadState_Ensure(data->ref, &refs[2]); PyGILState_STATE gstate = PyGILState_Ensure(); - PyThreadState_Ensure(data->ref); + PyThreadState_Ensure(data->ref, &refs[3]); res = PyRun_SimpleString(THREAD_CODE); - PyThreadState_Release(); + PyThreadState_Release(refs[3]); PyGILState_Release(gstate); - PyThreadState_Release(); - PyThreadState_Release(); + PyThreadState_Release(refs[2]); + PyThreadState_Release(refs[1]); assert(res == 0); - PyThreadState_Release(); + PyThreadState_Release(refs[0]); PyInterpreterRef_Close(data->ref); data->done = 1; } diff --git a/Python/pystate.c b/Python/pystate.c index bbc0710a5a45a3..cf72125a0d88f7 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3319,10 +3319,11 @@ PyInterpreterRef_Main(PyInterpreterRef *strong_ptr) } int -PyThreadState_Ensure(PyInterpreterRef interp_ref) +PyThreadState_Ensure(PyInterpreterRef interp_ref, PyThreadRef *thread_ref) { PyInterpreterState *interp = ref_as_interp(interp_ref); PyThreadState *attached_tstate = current_fast_get(); + *thread_ref = 0; if (attached_tstate != NULL && attached_tstate->interp == interp) { /* Yay! We already have an attached thread state that matches. */ ++attached_tstate->ensure.counter; @@ -3347,7 +3348,7 @@ PyThreadState_Ensure(PyInterpreterRef interp_ref) fresh_tstate->ensure.delete_on_release = 1; if (attached_tstate != NULL) { - fresh_tstate->ensure.prior_tstate = PyThreadState_Swap(fresh_tstate); + *thread_ref = (PyThreadRef)PyThreadState_Swap(fresh_tstate); } else { _PyThreadState_Attach(fresh_tstate); } @@ -3356,7 +3357,7 @@ PyThreadState_Ensure(PyInterpreterRef interp_ref) } void -PyThreadState_Release(void) +PyThreadState_Release(PyThreadRef thread_ref) { PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); @@ -3364,7 +3365,9 @@ PyThreadState_Release(void) if (remaining < 0) { Py_FatalError("PyThreadState_Release() called more times than PyThreadState_Ensure()"); } - PyThreadState *to_restore = tstate->ensure.prior_tstate; + // The thread reference might be NULL + assert(thread_ref >= 0); + PyThreadState *to_restore = (PyThreadState *)thread_ref; if (remaining == 0) { if (tstate->ensure.delete_on_release) { PyThreadState_Clear(tstate); From c3d09d2c1112d1e8fb3ff9b35e8d856212c3fac0 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 19 Sep 2025 06:31:36 -0400 Subject: [PATCH 63/85] Fix incorrect condition. --- Python/pystate.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index f71471a66cd020..7708a48a983edb 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3158,7 +3158,7 @@ static int try_acquire_strong_ref(PyInterpreterState *interp, PyInterpreterRef *strong_ptr) { _PyRWMutex_RLock(&interp->references.lock); - if (_PyInterpreterState_GetFinalizing(interp) == NULL) { + if (_PyInterpreterState_GetFinalizing(interp) != NULL) { *strong_ptr = 0; _PyRWMutex_RUnlock(&interp->references.lock); return -1; @@ -3356,7 +3356,6 @@ PyThreadState_Release(PyThreadRef thread_ref) Py_FatalError("PyThreadState_Release() called more times than PyThreadState_Ensure()"); } // The thread reference might be NULL - assert(thread_ref >= 0); PyThreadState *to_restore = (PyThreadState *)thread_ref; if (remaining == 0) { if (tstate->ensure.delete_on_release) { From d1b3d8073d09c881d0104f1947e37355a1cb8321 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 19 Sep 2025 06:53:00 -0400 Subject: [PATCH 64/85] Hand off the GIL when waiting on interpreter references. We should probably use an event instead of a silly loop like this, but I don't care at the moment. --- Modules/_testcapimodule.c | 10 ++++++++++ Python/pylifecycle.c | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index d58ea4d7de491b..ff9b1ed235b475 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2763,6 +2763,15 @@ test_weak_interpreter_ref_after_shutdown(PyObject *self, PyObject *unused) Py_RETURN_NONE; } +static PyObject * +foo(PyObject *self, PyObject *foo) +{ + PyInterpreterRef ref; + PyInterpreterRef_Get(&ref); + PyInterpreterRef_Close(ref); + Py_RETURN_NONE; +} + static PyMethodDef TestMethods[] = { {"set_errno", set_errno, METH_VARARGS}, {"test_config", test_config, METH_NOARGS}, @@ -2861,6 +2870,7 @@ static PyMethodDef TestMethods[] = { {"test_thread_state_ensure_nested", test_thread_state_ensure_nested, METH_NOARGS}, {"test_thread_state_ensure_crossinterp", test_thread_state_ensure_crossinterp, METH_NOARGS}, {"test_weak_interpreter_ref_after_shutdown", test_weak_interpreter_ref_after_shutdown, METH_NOARGS}, + {"foo", foo, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 7243db883d38ab..866c1ac21b39c0 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2127,6 +2127,7 @@ make_pre_finalization_calls(PyThreadState *tstate, int subinterpreters) int has_subinterpreters = subinterpreters ? runtime_has_subinterpreters(interp->runtime) : 0; + // TODO: The interpreter reference countdown probably isn't very efficient. int should_continue = (interp_has_threads(interp) || interp_has_atexit_callbacks(interp) || interp_has_pending_calls(interp) @@ -2135,9 +2136,12 @@ make_pre_finalization_calls(PyThreadState *tstate, int subinterpreters) if (!should_continue) { break; } + // Temporarily let other threads execute + _PyThreadState_Detach(tstate); _PyRWMutex_Unlock(&interp->references.lock); _PyEval_StartTheWorldAll(interp->runtime); PyMutex_Unlock(&interp->ceval.pending.mutex); + _PyThreadState_Attach(tstate); } assert(PyMutex_IsLocked(&interp->ceval.pending.mutex)); ASSERT_WORLD_STOPPED(interp); From f9b7040a33e775f048b1af5e7b2c447c08293d87 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 19 Sep 2025 06:55:59 -0400 Subject: [PATCH 65/85] Use new names from the PEP. --- Include/cpython/pystate.h | 10 +++++----- Modules/_testcapimodule.c | 16 ++++++++-------- Modules/_testinternalcapi.c | 8 ++++---- Programs/_testembed.c | 10 +++++----- Python/pystate.c | 14 +++++++------- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index dbacb1edd26b4e..4f3529de635aac 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -286,11 +286,11 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( typedef uintptr_t PyInterpreterRef; -PyAPI_FUNC(int) PyInterpreterRef_Get(PyInterpreterRef *ref); +PyAPI_FUNC(int) PyInterpreterRef_FromCurrent(PyInterpreterRef *ref); PyAPI_FUNC(PyInterpreterRef) PyInterpreterRef_Dup(PyInterpreterRef ref); -PyAPI_FUNC(int) PyInterpreterRef_Main(PyInterpreterRef *ref); +PyAPI_FUNC(int) PyUnstable_GetDefaultInterpreterRef(PyInterpreterRef *ref); PyAPI_FUNC(void) PyInterpreterRef_Close(PyInterpreterRef ref); -PyAPI_FUNC(PyInterpreterState *) PyInterpreterRef_AsInterpreter(PyInterpreterRef ref); +PyAPI_FUNC(PyInterpreterState *) PyInterpreterRef_GetInterpreter(PyInterpreterRef ref); #define PyInterpreterRef_Close(ref) do { \ PyInterpreterRef_Close(ref); \ @@ -306,9 +306,9 @@ typedef struct _PyInterpreterWeakRef { typedef _PyInterpreterWeakRef *PyInterpreterWeakRef; -PyAPI_FUNC(int) PyInterpreterWeakRef_Get(PyInterpreterWeakRef *ptr); +PyAPI_FUNC(int) PyInterpreterWeakRef_FromCurrent(PyInterpreterWeakRef *ptr); PyAPI_FUNC(PyInterpreterWeakRef) PyInterpreterWeakRef_Dup(PyInterpreterWeakRef wref); -PyAPI_FUNC(int) PyInterpreterWeakRef_AsStrong(PyInterpreterWeakRef wref, PyInterpreterRef *strong_ptr); +PyAPI_FUNC(int) PyInterpreterWeakRef_Promote(PyInterpreterWeakRef wref, PyInterpreterRef *strong_ptr); PyAPI_FUNC(void) PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref); #define PyInterpreterWeakRef_Close(ref) do { \ diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index ff9b1ed235b475..8eda47b7dd7477 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2576,7 +2576,7 @@ static PyInterpreterRef get_strong_ref(void) { PyInterpreterRef ref; - if (PyInterpreterRef_Get(&ref) < 0) { + if (PyInterpreterRef_FromCurrent(&ref) < 0) { Py_FatalError("strong reference should not have failed"); } return ref; @@ -2587,10 +2587,10 @@ test_interp_ref_common(void) { PyInterpreterState *interp = PyInterpreterState_Get(); PyInterpreterRef ref = get_strong_ref(); - assert(PyInterpreterRef_AsInterpreter(ref) == interp); + assert(PyInterpreterRef_GetInterpreter(ref) == interp); PyInterpreterRef ref_2 = PyInterpreterRef_Dup(ref); - assert(PyInterpreterRef_AsInterpreter(ref_2) == interp); + assert(PyInterpreterRef_GetInterpreter(ref_2) == interp); // We can close the references in any order PyInterpreterRef_Close(ref); @@ -2708,7 +2708,7 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) PyThreadState *ensured_tstate = PyThreadState_Get(); assert(ensured_tstate != save_tstate); - assert(PyInterpreterState_Get() == PyInterpreterRef_AsInterpreter(ref)); + assert(PyInterpreterState_Get() == PyInterpreterRef_GetInterpreter(ref)); assert(PyGILState_GetThisThreadState() == ensured_tstate); // Now though, we should reactivate the thread state @@ -2743,20 +2743,20 @@ test_weak_interpreter_ref_after_shutdown(PyObject *self, PyObject *unused) return PyErr_NoMemory(); } - int res = PyInterpreterWeakRef_Get(&wref); + int res = PyInterpreterWeakRef_FromCurrent(&wref); (void)res; assert(res == 0); // As a sanity check, ensure that the weakref actually works PyInterpreterRef ref; - res = PyInterpreterWeakRef_AsStrong(wref, &ref); + res = PyInterpreterWeakRef_Promote(wref, &ref); assert(res == 0); PyInterpreterRef_Close(ref); // Now, destroy the interpreter and try to acquire a weak reference. // It should fail. Py_EndInterpreter(interp_tstate); - res = PyInterpreterWeakRef_AsStrong(wref, &ref); + res = PyInterpreterWeakRef_Promote(wref, &ref); assert(res == -1); PyThreadState_Swap(save_tstate); @@ -2767,7 +2767,7 @@ static PyObject * foo(PyObject *self, PyObject *foo) { PyInterpreterRef ref; - PyInterpreterRef_Get(&ref); + PyInterpreterRef_FromCurrent(&ref); PyInterpreterRef_Close(ref); Py_RETURN_NONE; } diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 175222601def90..a8c01f49021587 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2395,7 +2395,7 @@ test_interp_refcount(PyObject *self, PyObject *unused) assert(_PyInterpreterState_Refcount(interp) == 0); PyInterpreterRef refs[NUM_REFS]; for (int i = 0; i < NUM_REFS; ++i) { - int res = PyInterpreterRef_Get(&refs[i]); + int res = PyInterpreterRef_FromCurrent(&refs[i]); (void)res; assert(res == 0); assert(_PyInterpreterState_Refcount(interp) == i + 1); @@ -2414,7 +2414,7 @@ test_interp_weakref_incref(PyObject *self, PyObject *unused) { PyInterpreterState *interp = PyInterpreterState_Get(); PyInterpreterWeakRef wref; - if (PyInterpreterWeakRef_Get(&wref) < 0) { + if (PyInterpreterWeakRef_FromCurrent(&wref) < 0) { return NULL; } assert(_PyInterpreterState_Refcount(interp) == 0); @@ -2422,10 +2422,10 @@ test_interp_weakref_incref(PyObject *self, PyObject *unused) PyInterpreterRef refs[NUM_REFS]; for (int i = 0; i < NUM_REFS; ++i) { - int res = PyInterpreterWeakRef_AsStrong(wref, &refs[i]); + int res = PyInterpreterWeakRef_Promote(wref, &refs[i]); (void)res; assert(res == 0); - assert(PyInterpreterRef_AsInterpreter(refs[i]) == interp); + assert(PyInterpreterRef_GetInterpreter(refs[i]) == interp); assert(_PyInterpreterState_Refcount(interp) == i + 1); } diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 61a19f1dc51d1b..41f70a06672837 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2352,7 +2352,7 @@ test_thread_state_ensure(void) PyThread_handle_t handle; PyThread_ident_t ident; PyInterpreterRef ref; - if (PyInterpreterRef_Get(&ref) < 0) { + if (PyInterpreterRef_FromCurrent(&ref) < 0) { return -1; }; ThreadData data = { ref }; @@ -2401,22 +2401,22 @@ test_main_interpreter_ref(void) { // It should not work before the runtime has started. PyInterpreterRef ref; - int res = PyInterpreterRef_Main(&ref); + int res = PyUnstable_GetDefaultInterpreterRef(&ref); (void)res; assert(res == -1); _testembed_initialize(); // Main interpreter is initialized and ready. - res = PyInterpreterRef_Main(&ref); + res = PyUnstable_GetDefaultInterpreterRef(&ref); assert(res == 0); - assert(PyInterpreterRef_AsInterpreter(ref) == PyInterpreterState_Main()); + assert(PyInterpreterRef_GetInterpreter(ref) == PyInterpreterState_Main()); PyInterpreterRef_Close(ref); Py_Finalize(); // Main interpreter is dead, we can no longer acquire references to it. - res = PyInterpreterRef_Main(&ref); + res = PyUnstable_GetDefaultInterpreterRef(&ref); assert(res == -1); return 0; } diff --git a/Python/pystate.c b/Python/pystate.c index 7708a48a983edb..74880f86196ce4 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2783,13 +2783,13 @@ PyGILState_Ensure(void) if (tcur == NULL) { /* Create a new Python thread state for this thread */ // XXX Use PyInterpreterState_EnsureThreadState()? - if (PyInterpreterRef_Main(&ref) < 0) { + if (PyUnstable_GetDefaultInterpreterRef(&ref) < 0) { // The main interpreter has finished, so we don't have // any intepreter to make a thread state for. Hang the // thread to act as failure. PyThread_hang_thread(); } - tcur = new_threadstate(PyInterpreterRef_AsInterpreter(ref), + tcur = new_threadstate(PyInterpreterRef_GetInterpreter(ref), _PyThreadState_WHENCE_GILSTATE); if (tcur == NULL) { Py_FatalError("Couldn't create thread-state for new thread"); @@ -3182,7 +3182,7 @@ ref_as_interp(PyInterpreterRef ref) } int -PyInterpreterRef_Get(PyInterpreterRef *ref) +PyInterpreterRef_FromCurrent(PyInterpreterRef *ref) { assert(ref != NULL); PyInterpreterState *interp = PyInterpreterState_Get(); @@ -3217,14 +3217,14 @@ PyInterpreterRef_Close(PyInterpreterRef ref) } PyInterpreterState * -PyInterpreterRef_AsInterpreter(PyInterpreterRef ref) +PyInterpreterRef_GetInterpreter(PyInterpreterRef ref) { PyInterpreterState *interp = ref_as_interp(ref); return interp; } int -PyInterpreterWeakRef_Get(PyInterpreterWeakRef *wref_ptr) +PyInterpreterWeakRef_FromCurrent(PyInterpreterWeakRef *wref_ptr) { PyInterpreterState *interp = PyInterpreterState_Get(); /* PyInterpreterWeakRef_Close() can be called without an attached thread @@ -3270,7 +3270,7 @@ PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref_handle) } int -PyInterpreterWeakRef_AsStrong(PyInterpreterWeakRef wref_handle, PyInterpreterRef *strong_ptr) +PyInterpreterWeakRef_Promote(PyInterpreterWeakRef wref_handle, PyInterpreterRef *strong_ptr) { assert(strong_ptr != NULL); _PyInterpreterWeakRef *wref = wref_handle_as_ptr(wref_handle); @@ -3291,7 +3291,7 @@ PyInterpreterWeakRef_AsStrong(PyInterpreterWeakRef wref_handle, PyInterpreterRef } int -PyInterpreterRef_Main(PyInterpreterRef *strong_ptr) +PyUnstable_GetDefaultInterpreterRef(PyInterpreterRef *strong_ptr) { assert(strong_ptr != NULL); _PyRuntimeState *runtime = &_PyRuntime; From cbd72541bbe1d392d627effc5adb8542abd6734f Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 4 Oct 2025 10:40:31 -0400 Subject: [PATCH 66/85] Use the new names for the API. --- Include/cpython/pystate.h | 40 ++++++++++----------- Modules/_testcapimodule.c | 62 ++++++++++++++++---------------- Modules/_testinternalcapi.c | 20 +++++------ Programs/_testembed.c | 24 ++++++------- Python/pystate.c | 72 ++++++++++++++++++------------------- 5 files changed, 109 insertions(+), 109 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 4f3529de635aac..d828633436b3c7 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -284,43 +284,43 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( /* Strong interpreter references */ -typedef uintptr_t PyInterpreterRef; +typedef uintptr_t PyInterpreterLock; -PyAPI_FUNC(int) PyInterpreterRef_FromCurrent(PyInterpreterRef *ref); -PyAPI_FUNC(PyInterpreterRef) PyInterpreterRef_Dup(PyInterpreterRef ref); -PyAPI_FUNC(int) PyUnstable_GetDefaultInterpreterRef(PyInterpreterRef *ref); -PyAPI_FUNC(void) PyInterpreterRef_Close(PyInterpreterRef ref); -PyAPI_FUNC(PyInterpreterState *) PyInterpreterRef_GetInterpreter(PyInterpreterRef ref); +PyAPI_FUNC(int) PyInterpreterLock_FromCurrent(PyInterpreterLock *ref); +PyAPI_FUNC(PyInterpreterLock) PyInterpreterLock_Copy(PyInterpreterLock ref); +PyAPI_FUNC(int) PyUnstable_InterpreterView_FromDefault(PyInterpreterLock *ref); +PyAPI_FUNC(void) PyInterpreterLock_Release(PyInterpreterLock ref); +PyAPI_FUNC(PyInterpreterState *) PyInterpreterLock_GetInterpreter(PyInterpreterLock ref); -#define PyInterpreterRef_Close(ref) do { \ - PyInterpreterRef_Close(ref); \ +#define PyInterpreterLock_Release(ref) do { \ + PyInterpreterLock_Release(ref); \ ref = 0; \ } while (0) /* Weak interpreter references */ -typedef struct _PyInterpreterWeakRef { +typedef struct _PyInterpreterView { int64_t id; Py_ssize_t refcount; -} _PyInterpreterWeakRef; +} _PyInterpreterView; -typedef _PyInterpreterWeakRef *PyInterpreterWeakRef; +typedef _PyInterpreterView *PyInterpreterView; -PyAPI_FUNC(int) PyInterpreterWeakRef_FromCurrent(PyInterpreterWeakRef *ptr); -PyAPI_FUNC(PyInterpreterWeakRef) PyInterpreterWeakRef_Dup(PyInterpreterWeakRef wref); -PyAPI_FUNC(int) PyInterpreterWeakRef_Promote(PyInterpreterWeakRef wref, PyInterpreterRef *strong_ptr); -PyAPI_FUNC(void) PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref); +PyAPI_FUNC(int) PyInterpreterView_FromCurrent(PyInterpreterView *ptr); +PyAPI_FUNC(PyInterpreterView) PyInterpreterView_Copy(PyInterpreterView wref); +PyAPI_FUNC(int) PyInterpreterLock_FromView(PyInterpreterView wref, PyInterpreterLock *strong_ptr); +PyAPI_FUNC(void) PyInterpreterView_Close(PyInterpreterView wref); -#define PyInterpreterWeakRef_Close(ref) do { \ - PyInterpreterWeakRef_Close(ref); \ +#define PyInterpreterView_Close(ref) do { \ + PyInterpreterView_Close(ref); \ ref = 0; \ } while (0) /* Thread references */ -typedef uintptr_t PyThreadRef; +typedef uintptr_t PyThreadView; -PyAPI_FUNC(int) PyThreadState_Ensure(PyInterpreterRef interp_ref, PyThreadRef *thread_ref); +PyAPI_FUNC(int) PyThreadState_Ensure(PyInterpreterLock interp_ref, PyThreadView *thread_ref); -PyAPI_FUNC(void) PyThreadState_Release(PyThreadRef thread_ref); +PyAPI_FUNC(void) PyThreadState_Release(PyThreadView thread_ref); diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 981e0fb91f26b7..ab9dffa0b67b94 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2562,11 +2562,11 @@ toggle_reftrace_printer(PyObject *ob, PyObject *arg) Py_RETURN_NONE; } -static PyInterpreterRef +static PyInterpreterLock get_strong_ref(void) { - PyInterpreterRef ref; - if (PyInterpreterRef_FromCurrent(&ref) < 0) { + PyInterpreterLock ref; + if (PyInterpreterLock_FromCurrent(&ref) < 0) { Py_FatalError("strong reference should not have failed"); } return ref; @@ -2576,15 +2576,15 @@ static void test_interp_ref_common(void) { PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterRef ref = get_strong_ref(); - assert(PyInterpreterRef_GetInterpreter(ref) == interp); + PyInterpreterLock ref = get_strong_ref(); + assert(PyInterpreterLock_GetInterpreter(ref) == interp); - PyInterpreterRef ref_2 = PyInterpreterRef_Dup(ref); - assert(PyInterpreterRef_GetInterpreter(ref_2) == interp); + PyInterpreterLock ref_2 = PyInterpreterLock_Copy(ref); + assert(PyInterpreterLock_GetInterpreter(ref_2) == interp); // We can close the references in any order - PyInterpreterRef_Close(ref); - PyInterpreterRef_Close(ref_2); + PyInterpreterLock_Release(ref); + PyInterpreterLock_Release(ref_2); } static PyObject * @@ -2621,15 +2621,15 @@ test_interpreter_refs(PyObject *self, PyObject *unused) static PyObject * test_thread_state_ensure_nested(PyObject *self, PyObject *unused) { - PyInterpreterRef ref = get_strong_ref(); + PyInterpreterLock ref = get_strong_ref(); PyThreadState *save_tstate = PyThreadState_Swap(NULL); assert(PyGILState_GetThisThreadState() == save_tstate); - PyThreadRef refs[10]; + PyThreadView refs[10]; for (int i = 0; i < 10; ++i) { // Test reactivation of the detached tstate. if (PyThreadState_Ensure(ref, &refs[i]) < 0) { - PyInterpreterRef_Close(ref); + PyInterpreterLock_Release(ref); return PyErr_NoMemory(); } @@ -2647,7 +2647,7 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) if (PyThreadState_Ensure(ref, &refs[i]) < 0) { // This will technically leak other thread states, but it doesn't // matter because this is a test. - PyInterpreterRef_Close(ref); + PyInterpreterLock_Release(ref); return PyErr_NoMemory(); } @@ -2660,7 +2660,7 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) } assert(PyThreadState_GetUnchecked() == NULL); - PyInterpreterRef_Close(ref); + PyInterpreterLock_Release(ref); PyThreadState_Swap(save_tstate); Py_RETURN_NONE; } @@ -2668,11 +2668,11 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) static PyObject * test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) { - PyInterpreterRef ref = get_strong_ref(); + PyInterpreterLock ref = get_strong_ref(); PyThreadState *save_tstate = PyThreadState_Swap(NULL); PyThreadState *interp_tstate = Py_NewInterpreter(); if (interp_tstate == NULL) { - PyInterpreterRef_Close(ref); + PyInterpreterLock_Release(ref); return PyErr_NoMemory(); } @@ -2689,21 +2689,21 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) interp = interpreters.create() interp.exec(some_func) */ - PyThreadRef thread_ref; - PyThreadRef other_thread_ref; + PyThreadView thread_ref; + PyThreadView other_thread_ref; if (PyThreadState_Ensure(ref, &thread_ref) < 0) { - PyInterpreterRef_Close(ref); + PyInterpreterLock_Release(ref); return PyErr_NoMemory(); } PyThreadState *ensured_tstate = PyThreadState_Get(); assert(ensured_tstate != save_tstate); - assert(PyInterpreterState_Get() == PyInterpreterRef_GetInterpreter(ref)); + assert(PyInterpreterState_Get() == PyInterpreterLock_GetInterpreter(ref)); assert(PyGILState_GetThisThreadState() == ensured_tstate); // Now though, we should reactivate the thread state if (PyThreadState_Ensure(ref, &other_thread_ref) < 0) { - PyInterpreterRef_Close(ref); + PyInterpreterLock_Release(ref); return PyErr_NoMemory(); } @@ -2718,7 +2718,7 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) PyThreadState_Swap(interp_tstate); Py_EndInterpreter(interp_tstate); - PyInterpreterRef_Close(ref); + PyInterpreterLock_Release(ref); PyThreadState_Swap(save_tstate); Py_RETURN_NONE; } @@ -2727,26 +2727,26 @@ static PyObject * test_weak_interpreter_ref_after_shutdown(PyObject *self, PyObject *unused) { PyThreadState *save_tstate = PyThreadState_Swap(NULL); - PyInterpreterWeakRef wref; + PyInterpreterView wref; PyThreadState *interp_tstate = Py_NewInterpreter(); if (interp_tstate == NULL) { return PyErr_NoMemory(); } - int res = PyInterpreterWeakRef_FromCurrent(&wref); + int res = PyInterpreterView_FromCurrent(&wref); (void)res; assert(res == 0); // As a sanity check, ensure that the weakref actually works - PyInterpreterRef ref; - res = PyInterpreterWeakRef_Promote(wref, &ref); + PyInterpreterLock ref; + res = PyInterpreterLock_FromView(wref, &ref); assert(res == 0); - PyInterpreterRef_Close(ref); + PyInterpreterLock_Release(ref); // Now, destroy the interpreter and try to acquire a weak reference. // It should fail. Py_EndInterpreter(interp_tstate); - res = PyInterpreterWeakRef_Promote(wref, &ref); + res = PyInterpreterLock_FromView(wref, &ref); assert(res == -1); PyThreadState_Swap(save_tstate); @@ -2756,9 +2756,9 @@ test_weak_interpreter_ref_after_shutdown(PyObject *self, PyObject *unused) static PyObject * foo(PyObject *self, PyObject *foo) { - PyInterpreterRef ref; - PyInterpreterRef_FromCurrent(&ref); - PyInterpreterRef_Close(ref); + PyInterpreterLock ref; + PyInterpreterLock_FromCurrent(&ref); + PyInterpreterLock_Release(ref); Py_RETURN_NONE; } diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 3728bc569ae42d..bab83a5d718628 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2425,16 +2425,16 @@ test_interp_refcount(PyObject *self, PyObject *unused) { PyInterpreterState *interp = PyInterpreterState_Get(); assert(_PyInterpreterState_Refcount(interp) == 0); - PyInterpreterRef refs[NUM_REFS]; + PyInterpreterLock refs[NUM_REFS]; for (int i = 0; i < NUM_REFS; ++i) { - int res = PyInterpreterRef_FromCurrent(&refs[i]); + int res = PyInterpreterLock_FromCurrent(&refs[i]); (void)res; assert(res == 0); assert(_PyInterpreterState_Refcount(interp) == i + 1); } for (int i = 0; i < NUM_REFS; ++i) { - PyInterpreterRef_Close(refs[i]); + PyInterpreterLock_Release(refs[i]); assert(_PyInterpreterState_Refcount(interp) == (NUM_REFS - i - 1)); } @@ -2445,28 +2445,28 @@ static PyObject * test_interp_weakref_incref(PyObject *self, PyObject *unused) { PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterWeakRef wref; - if (PyInterpreterWeakRef_FromCurrent(&wref) < 0) { + PyInterpreterView wref; + if (PyInterpreterView_FromCurrent(&wref) < 0) { return NULL; } assert(_PyInterpreterState_Refcount(interp) == 0); - PyInterpreterRef refs[NUM_REFS]; + PyInterpreterLock refs[NUM_REFS]; for (int i = 0; i < NUM_REFS; ++i) { - int res = PyInterpreterWeakRef_Promote(wref, &refs[i]); + int res = PyInterpreterLock_FromView(wref, &refs[i]); (void)res; assert(res == 0); - assert(PyInterpreterRef_GetInterpreter(refs[i]) == interp); + assert(PyInterpreterLock_GetInterpreter(refs[i]) == interp); assert(_PyInterpreterState_Refcount(interp) == i + 1); } for (int i = 0; i < NUM_REFS; ++i) { - PyInterpreterRef_Close(refs[i]); + PyInterpreterLock_Release(refs[i]); assert(_PyInterpreterState_Refcount(interp) == (NUM_REFS - i - 1)); } - PyInterpreterWeakRef_Close(wref); + PyInterpreterView_Close(wref); Py_RETURN_NONE; } diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 26548448307e4c..27ea3e22667f31 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2291,7 +2291,7 @@ const char *THREAD_CODE = \ "fib(10)"; typedef struct { - PyInterpreterRef ref; + PyInterpreterLock ref; int done; } ThreadData; @@ -2299,7 +2299,7 @@ static void do_tstate_ensure(void *arg) { ThreadData *data = (ThreadData *)arg; - PyThreadRef refs[4]; + PyThreadView refs[4]; int res = PyThreadState_Ensure(data->ref, &refs[0]); assert(res == 0); PyThreadState_Ensure(data->ref, &refs[1]); @@ -2313,7 +2313,7 @@ do_tstate_ensure(void *arg) PyThreadState_Release(refs[1]); assert(res == 0); PyThreadState_Release(refs[0]); - PyInterpreterRef_Close(data->ref); + PyInterpreterLock_Release(data->ref); data->done = 1; } @@ -2323,14 +2323,14 @@ test_thread_state_ensure(void) _testembed_initialize(); PyThread_handle_t handle; PyThread_ident_t ident; - PyInterpreterRef ref; - if (PyInterpreterRef_FromCurrent(&ref) < 0) { + PyInterpreterLock ref; + if (PyInterpreterLock_FromCurrent(&ref) < 0) { return -1; }; ThreadData data = { ref }; if (PyThread_start_joinable_thread(do_tstate_ensure, &data, &ident, &handle) < 0) { - PyInterpreterRef_Close(ref); + PyInterpreterLock_Release(ref); return -1; } // We hold a strong interpreter reference, so we don't @@ -2372,23 +2372,23 @@ static int test_main_interpreter_ref(void) { // It should not work before the runtime has started. - PyInterpreterRef ref; - int res = PyUnstable_GetDefaultInterpreterRef(&ref); + PyInterpreterLock ref; + int res = PyUnstable_InterpreterView_FromDefault(&ref); (void)res; assert(res == -1); _testembed_initialize(); // Main interpreter is initialized and ready. - res = PyUnstable_GetDefaultInterpreterRef(&ref); + res = PyUnstable_InterpreterView_FromDefault(&ref); assert(res == 0); - assert(PyInterpreterRef_GetInterpreter(ref) == PyInterpreterState_Main()); - PyInterpreterRef_Close(ref); + assert(PyInterpreterLock_GetInterpreter(ref) == PyInterpreterState_Main()); + PyInterpreterLock_Release(ref); Py_Finalize(); // Main interpreter is dead, we can no longer acquire references to it. - res = PyUnstable_GetDefaultInterpreterRef(&ref); + res = PyUnstable_InterpreterView_FromDefault(&ref); assert(res == -1); return 0; } diff --git a/Python/pystate.c b/Python/pystate.c index 114240a4330433..6b8736bfc83963 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1765,7 +1765,7 @@ decref_interpreter(PyInterpreterState *interp) _PyRWMutex_RUnlock(&interp->references.lock); if (old <= 0) { Py_FatalError("interpreter has negative reference count, likely due" - " to an extra PyInterpreterRef_Close()"); + " to an extra PyInterpreterLock_Release()"); } } @@ -2783,17 +2783,17 @@ PyGILState_Ensure(void) PyThreadState *tcur = gilstate_get(); int has_gil; - PyInterpreterRef ref; + PyInterpreterLock ref; if (tcur == NULL) { /* Create a new Python thread state for this thread */ // XXX Use PyInterpreterState_EnsureThreadState()? - if (PyUnstable_GetDefaultInterpreterRef(&ref) < 0) { + if (PyUnstable_InterpreterView_FromDefault(&ref) < 0) { // The main interpreter has finished, so we don't have // any intepreter to make a thread state for. Hang the // thread to act as failure. PyThread_hang_thread(); } - tcur = new_threadstate(PyInterpreterRef_GetInterpreter(ref), + tcur = new_threadstate(PyInterpreterLock_GetInterpreter(ref), _PyThreadState_WHENCE_GILSTATE); if (tcur == NULL) { Py_FatalError("Couldn't create thread-state for new thread"); @@ -2806,7 +2806,7 @@ PyGILState_Ensure(void) assert(tcur->gilstate_counter == 1); tcur->gilstate_counter = 0; has_gil = 0; /* new thread state is never current */ - PyInterpreterRef_Close(ref); + PyInterpreterLock_Release(ref); } else { has_gil = holds_gil(tcur); @@ -3159,7 +3159,7 @@ _PyInterpreterState_Refcount(PyInterpreterState *interp) } static int -try_acquire_strong_ref(PyInterpreterState *interp, PyInterpreterRef *strong_ptr) +try_acquire_strong_ref(PyInterpreterState *interp, PyInterpreterLock *strong_ptr) { _PyRWMutex_RLock(&interp->references.lock); if (_PyInterpreterState_GetFinalizing(interp) != NULL) { @@ -3169,24 +3169,24 @@ try_acquire_strong_ref(PyInterpreterState *interp, PyInterpreterRef *strong_ptr) } _Py_atomic_add_ssize(&interp->references.refcount, 1); _PyRWMutex_RUnlock(&interp->references.lock); - *strong_ptr = (PyInterpreterRef)interp; + *strong_ptr = (PyInterpreterLock)interp; return 0; } static PyInterpreterState * -ref_as_interp(PyInterpreterRef ref) +ref_as_interp(PyInterpreterLock ref) { PyInterpreterState *interp = (PyInterpreterState *)ref; if (interp == NULL) { - Py_FatalError("Got a null interpreter reference, likely due to use after PyInterpreterRef_Close()"); + Py_FatalError("Got a null interpreter reference, likely due to use after PyInterpreterLock_Release()"); } return interp; } int -PyInterpreterRef_FromCurrent(PyInterpreterRef *ref) +PyInterpreterLock_FromCurrent(PyInterpreterLock *ref) { assert(ref != NULL); PyInterpreterState *interp = PyInterpreterState_Get(); @@ -3198,11 +3198,11 @@ PyInterpreterRef_FromCurrent(PyInterpreterRef *ref) return 0; } -PyInterpreterRef -PyInterpreterRef_Dup(PyInterpreterRef ref) +PyInterpreterLock +PyInterpreterLock_Copy(PyInterpreterLock ref) { PyInterpreterState *interp = ref_as_interp(ref); - PyInterpreterRef new_ref; + PyInterpreterLock new_ref; int res = try_acquire_strong_ref(interp, &new_ref); (void)res; // We already hold a strong reference, so it shouldn't be possible @@ -3212,72 +3212,72 @@ PyInterpreterRef_Dup(PyInterpreterRef ref) return new_ref; } -#undef PyInterpreterRef_Close +#undef PyInterpreterLock_Release void -PyInterpreterRef_Close(PyInterpreterRef ref) +PyInterpreterLock_Release(PyInterpreterLock ref) { PyInterpreterState *interp = ref_as_interp(ref); decref_interpreter(interp); } PyInterpreterState * -PyInterpreterRef_GetInterpreter(PyInterpreterRef ref) +PyInterpreterLock_GetInterpreter(PyInterpreterLock ref) { PyInterpreterState *interp = ref_as_interp(ref); return interp; } int -PyInterpreterWeakRef_FromCurrent(PyInterpreterWeakRef *wref_ptr) +PyInterpreterView_FromCurrent(PyInterpreterView *wref_ptr) { PyInterpreterState *interp = PyInterpreterState_Get(); - /* PyInterpreterWeakRef_Close() can be called without an attached thread + /* PyInterpreterView_Close() can be called without an attached thread state, so we have to use the raw allocator. */ - _PyInterpreterWeakRef *wref = PyMem_RawMalloc(sizeof(_PyInterpreterWeakRef)); + _PyInterpreterView *wref = PyMem_RawMalloc(sizeof(_PyInterpreterView)); if (wref == NULL) { PyErr_NoMemory(); return -1; } wref->refcount = 1; wref->id = interp->id; - *wref_ptr = (PyInterpreterWeakRef)wref; + *wref_ptr = (PyInterpreterView)wref; return 0; } -static _PyInterpreterWeakRef * -wref_handle_as_ptr(PyInterpreterWeakRef wref_handle) +static _PyInterpreterView * +wref_handle_as_ptr(PyInterpreterView wref_handle) { - _PyInterpreterWeakRef *wref = (_PyInterpreterWeakRef *)wref_handle; + _PyInterpreterView *wref = (_PyInterpreterView *)wref_handle; if (wref == NULL) { - Py_FatalError("Got a null weak interpreter reference, likely due to use after PyInterpreterWeakRef_Close()"); + Py_FatalError("Got a null weak interpreter reference, likely due to use after PyInterpreterView_Close()"); } return wref; } -PyInterpreterWeakRef -PyInterpreterWeakRef_Dup(PyInterpreterWeakRef wref_handle) +PyInterpreterView +PyInterpreterView_Copy(PyInterpreterView wref_handle) { - _PyInterpreterWeakRef *wref = wref_handle_as_ptr(wref_handle); + _PyInterpreterView *wref = wref_handle_as_ptr(wref_handle); ++wref->refcount; return wref; } -#undef PyInterpreterWeakRef_Close +#undef PyInterpreterView_Close void -PyInterpreterWeakRef_Close(PyInterpreterWeakRef wref_handle) +PyInterpreterView_Close(PyInterpreterView wref_handle) { - _PyInterpreterWeakRef *wref = wref_handle_as_ptr(wref_handle); + _PyInterpreterView *wref = wref_handle_as_ptr(wref_handle); if (--wref->refcount == 0) { PyMem_RawFree(wref); } } int -PyInterpreterWeakRef_Promote(PyInterpreterWeakRef wref_handle, PyInterpreterRef *strong_ptr) +PyInterpreterLock_FromView(PyInterpreterView wref_handle, PyInterpreterLock *strong_ptr) { assert(strong_ptr != NULL); - _PyInterpreterWeakRef *wref = wref_handle_as_ptr(wref_handle); + _PyInterpreterView *wref = wref_handle_as_ptr(wref_handle); int64_t interp_id = wref->id; /* Interpreters cannot be deleted while we hold the runtime lock. */ _PyRuntimeState *runtime = &_PyRuntime; @@ -3295,7 +3295,7 @@ PyInterpreterWeakRef_Promote(PyInterpreterWeakRef wref_handle, PyInterpreterRef } int -PyUnstable_GetDefaultInterpreterRef(PyInterpreterRef *strong_ptr) +PyUnstable_InterpreterView_FromDefault(PyInterpreterLock *strong_ptr) { assert(strong_ptr != NULL); _PyRuntimeState *runtime = &_PyRuntime; @@ -3313,7 +3313,7 @@ PyUnstable_GetDefaultInterpreterRef(PyInterpreterRef *strong_ptr) } int -PyThreadState_Ensure(PyInterpreterRef interp_ref, PyThreadRef *thread_ref) +PyThreadState_Ensure(PyInterpreterLock interp_ref, PyThreadView *thread_ref) { PyInterpreterState *interp = ref_as_interp(interp_ref); PyThreadState *attached_tstate = current_fast_get(); @@ -3342,7 +3342,7 @@ PyThreadState_Ensure(PyInterpreterRef interp_ref, PyThreadRef *thread_ref) fresh_tstate->ensure.delete_on_release = 1; if (attached_tstate != NULL) { - *thread_ref = (PyThreadRef)PyThreadState_Swap(fresh_tstate); + *thread_ref = (PyThreadView)PyThreadState_Swap(fresh_tstate); } else { _PyThreadState_Attach(fresh_tstate); } @@ -3351,7 +3351,7 @@ PyThreadState_Ensure(PyInterpreterRef interp_ref, PyThreadRef *thread_ref) } void -PyThreadState_Release(PyThreadRef thread_ref) +PyThreadState_Release(PyThreadView thread_ref) { PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); From a9da6b6909472431650efae148b7e57c20b0e9b0 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 4 Oct 2025 11:07:46 -0400 Subject: [PATCH 67/85] Update signatures for the new revision. --- Include/cpython/pystate.h | 41 ++++++++++++------------ Include/internal/pycore_interp_structs.h | 4 +-- Include/internal/pycore_pystate.h | 2 +- Modules/_testinternalcapi.c | 12 +++---- 4 files changed, 30 insertions(+), 29 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index d828633436b3c7..f1fe4b0488c7d1 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -286,41 +286,42 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( typedef uintptr_t PyInterpreterLock; -PyAPI_FUNC(int) PyInterpreterLock_FromCurrent(PyInterpreterLock *ref); -PyAPI_FUNC(PyInterpreterLock) PyInterpreterLock_Copy(PyInterpreterLock ref); -PyAPI_FUNC(int) PyUnstable_InterpreterView_FromDefault(PyInterpreterLock *ref); -PyAPI_FUNC(void) PyInterpreterLock_Release(PyInterpreterLock ref); -PyAPI_FUNC(PyInterpreterState *) PyInterpreterLock_GetInterpreter(PyInterpreterLock ref); - -#define PyInterpreterLock_Release(ref) do { \ - PyInterpreterLock_Release(ref); \ - ref = 0; \ +PyAPI_FUNC(PyInterpreterLock) PyInterpreterLock_FromCurrent(void); +PyAPI_FUNC(PyInterpreterLock) PyInterpreterLock_Copy(PyInterpreterLock lock); +PyAPI_FUNC(void) PyInterpreterLock_Release(PyInterpreterLock lock); +PyAPI_FUNC(PyInterpreterState *) PyInterpreterLock_GetInterpreter(PyInterpreterLock lock); +PyAPI_FUNC(PyInterpreterLock) PyInterpreterLock_FromView(PyInterpreterView view); + +#define PyInterpreterLock_Release(lock) do { \ + PyInterpreterLock_Release(lock); \ + ref = 0; \ } while (0) -/* Weak interpreter references */ +/* Interpreter views */ typedef struct _PyInterpreterView { int64_t id; Py_ssize_t refcount; } _PyInterpreterView; -typedef _PyInterpreterView *PyInterpreterView; +typedef uintptr_t PyInterpreterView; -PyAPI_FUNC(int) PyInterpreterView_FromCurrent(PyInterpreterView *ptr); -PyAPI_FUNC(PyInterpreterView) PyInterpreterView_Copy(PyInterpreterView wref); -PyAPI_FUNC(int) PyInterpreterLock_FromView(PyInterpreterView wref, PyInterpreterLock *strong_ptr); -PyAPI_FUNC(void) PyInterpreterView_Close(PyInterpreterView wref); +PyAPI_FUNC(PyInterpreterView) PyInterpreterView_FromCurrent(void); +PyAPI_FUNC(PyInterpreterView) PyInterpreterView_Copy(PyInterpreterView view); +PyAPI_FUNC(void) PyInterpreterView_Close(PyInterpreterView view); +PyAPI_FUNC(PyInterpreterView) PyUnstable_InterpreterView_FromDefault(void); -#define PyInterpreterView_Close(ref) do { \ - PyInterpreterView_Close(ref); \ - ref = 0; \ + +#define PyInterpreterView_Close(view) do { \ + PyInterpreterView_Close(view); \ + ref = 0; \ } while (0) -/* Thread references */ +/* Thread views */ typedef uintptr_t PyThreadView; -PyAPI_FUNC(int) PyThreadState_Ensure(PyInterpreterLock interp_ref, PyThreadView *thread_ref); +PyAPI_FUNC(PyThreadView) PyThreadState_Ensure(PyInterpreterLock lock); PyAPI_FUNC(void) PyThreadState_Release(PyThreadView thread_ref); diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 18ce34ab62b9a4..4a49ee4bc6a18c 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -973,8 +973,8 @@ struct _is { struct { _PyRWMutex lock; - Py_ssize_t refcount; - } references; + Py_ssize_t countdown; + } finalization_locks; /* the initial PyInterpreterState.threads.head */ _PyThreadStateImpl _initial_thread; diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 483b1b911098ce..5c4f0146bcfeb5 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -330,7 +330,7 @@ _Py_RecursionLimit_GetMargin(PyThreadState *tstate) } // Exports for '_testinternalcapi' shared extension -PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_Refcount(PyInterpreterState *interp); +PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_LockCountdown(PyInterpreterState *interp); #ifdef __cplusplus } diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index bab83a5d718628..8e87641ee9e673 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2424,18 +2424,18 @@ static PyObject * test_interp_refcount(PyObject *self, PyObject *unused) { PyInterpreterState *interp = PyInterpreterState_Get(); - assert(_PyInterpreterState_Refcount(interp) == 0); + assert(_PyInterpreterState_LockCountdown(interp) == 0); PyInterpreterLock refs[NUM_REFS]; for (int i = 0; i < NUM_REFS; ++i) { int res = PyInterpreterLock_FromCurrent(&refs[i]); (void)res; assert(res == 0); - assert(_PyInterpreterState_Refcount(interp) == i + 1); + assert(_PyInterpreterState_LockCountdown(interp) == i + 1); } for (int i = 0; i < NUM_REFS; ++i) { PyInterpreterLock_Release(refs[i]); - assert(_PyInterpreterState_Refcount(interp) == (NUM_REFS - i - 1)); + assert(_PyInterpreterState_LockCountdown(interp) == (NUM_REFS - i - 1)); } Py_RETURN_NONE; @@ -2449,7 +2449,7 @@ test_interp_weakref_incref(PyObject *self, PyObject *unused) if (PyInterpreterView_FromCurrent(&wref) < 0) { return NULL; } - assert(_PyInterpreterState_Refcount(interp) == 0); + assert(_PyInterpreterState_LockCountdown(interp) == 0); PyInterpreterLock refs[NUM_REFS]; @@ -2458,12 +2458,12 @@ test_interp_weakref_incref(PyObject *self, PyObject *unused) (void)res; assert(res == 0); assert(PyInterpreterLock_GetInterpreter(refs[i]) == interp); - assert(_PyInterpreterState_Refcount(interp) == i + 1); + assert(_PyInterpreterState_LockCountdown(interp) == i + 1); } for (int i = 0; i < NUM_REFS; ++i) { PyInterpreterLock_Release(refs[i]); - assert(_PyInterpreterState_Refcount(interp) == (NUM_REFS - i - 1)); + assert(_PyInterpreterState_LockCountdown(interp) == (NUM_REFS - i - 1)); } PyInterpreterView_Close(wref); From aaf3cef9f07c69386c7c2c06b644324fc8cb60b1 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 4 Oct 2025 11:35:39 -0400 Subject: [PATCH 68/85] Finish migration to the new API. --- Include/cpython/pystate.h | 10 +- Modules/_testcapimodule.c | 110 +++++++++---------- Modules/_testinternalcapi.c | 50 +++++---- Programs/_testembed.c | 111 ++++++++++--------- Python/pylifecycle.c | 10 +- Python/pystate.c | 205 ++++++++++++++++++------------------ 6 files changed, 242 insertions(+), 254 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index f1fe4b0488c7d1..5b5da1826ae889 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -282,9 +282,11 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( PyInterpreterState *interp, _PyFrameEvalFunction eval_frame); -/* Strong interpreter references */ +/* Interpreter locks */ typedef uintptr_t PyInterpreterLock; +typedef uintptr_t PyInterpreterView; + PyAPI_FUNC(PyInterpreterLock) PyInterpreterLock_FromCurrent(void); PyAPI_FUNC(PyInterpreterLock) PyInterpreterLock_Copy(PyInterpreterLock lock); @@ -294,7 +296,7 @@ PyAPI_FUNC(PyInterpreterLock) PyInterpreterLock_FromView(PyInterpreterView view) #define PyInterpreterLock_Release(lock) do { \ PyInterpreterLock_Release(lock); \ - ref = 0; \ + lock = 0; \ } while (0) /* Interpreter views */ @@ -304,8 +306,6 @@ typedef struct _PyInterpreterView { Py_ssize_t refcount; } _PyInterpreterView; -typedef uintptr_t PyInterpreterView; - PyAPI_FUNC(PyInterpreterView) PyInterpreterView_FromCurrent(void); PyAPI_FUNC(PyInterpreterView) PyInterpreterView_Copy(PyInterpreterView view); PyAPI_FUNC(void) PyInterpreterView_Close(PyInterpreterView view); @@ -314,7 +314,7 @@ PyAPI_FUNC(PyInterpreterView) PyUnstable_InterpreterView_FromDefault(void); #define PyInterpreterView_Close(view) do { \ PyInterpreterView_Close(view); \ - ref = 0; \ + view = 0; \ } while (0) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index ab9dffa0b67b94..491554e2344522 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2562,24 +2562,16 @@ toggle_reftrace_printer(PyObject *ob, PyObject *arg) Py_RETURN_NONE; } -static PyInterpreterLock -get_strong_ref(void) -{ - PyInterpreterLock ref; - if (PyInterpreterLock_FromCurrent(&ref) < 0) { - Py_FatalError("strong reference should not have failed"); - } - return ref; -} - static void -test_interp_ref_common(void) +test_interp_locks_common(void) { PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterLock ref = get_strong_ref(); + PyInterpreterLock ref = PyInterpreterLock_FromCurrent(); + assert(ref != 0); assert(PyInterpreterLock_GetInterpreter(ref) == interp); PyInterpreterLock ref_2 = PyInterpreterLock_Copy(ref); + assert(ref_2 != 0); assert(PyInterpreterLock_GetInterpreter(ref_2) == interp); // We can close the references in any order @@ -2588,15 +2580,15 @@ test_interp_ref_common(void) } static PyObject * -test_interpreter_refs(PyObject *self, PyObject *unused) +test_interpreter_locks(PyObject *self, PyObject *unused) { // Test the main interpreter - test_interp_ref_common(); + test_interp_locks_common(); // Test a (legacy) subinterpreter PyThreadState *save_tstate = PyThreadState_Swap(NULL); PyThreadState *interp_tstate = Py_NewInterpreter(); - test_interp_ref_common(); + test_interp_locks_common(); Py_EndInterpreter(interp_tstate); // Test an isolated subinterpreter @@ -2612,7 +2604,7 @@ test_interpreter_refs(PyObject *self, PyObject *unused) return NULL; } - test_interp_ref_common(); + test_interp_locks_common(); Py_EndInterpreter(isolated_interp_tstate); PyThreadState_Swap(save_tstate); Py_RETURN_NONE; @@ -2621,21 +2613,25 @@ test_interpreter_refs(PyObject *self, PyObject *unused) static PyObject * test_thread_state_ensure_nested(PyObject *self, PyObject *unused) { - PyInterpreterLock ref = get_strong_ref(); + PyInterpreterLock lock = PyInterpreterLock_FromCurrent(); + if (lock == 0) { + return NULL; + } PyThreadState *save_tstate = PyThreadState_Swap(NULL); assert(PyGILState_GetThisThreadState() == save_tstate); - PyThreadView refs[10]; + PyThreadView thread_views[10]; for (int i = 0; i < 10; ++i) { // Test reactivation of the detached tstate. - if (PyThreadState_Ensure(ref, &refs[i]) < 0) { - PyInterpreterLock_Release(ref); + thread_views[i] = PyThreadState_Ensure(lock); + if (thread_views[i] == 0) { + PyInterpreterLock_Release(lock); return PyErr_NoMemory(); } // No new thread state should've been created. assert(PyThreadState_Get() == save_tstate); - PyThreadState_Release(refs[i]); + PyThreadState_Release(thread_views[i]); } assert(PyThreadState_GetUnchecked() == NULL); @@ -2644,10 +2640,11 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) // If the (detached) gilstate matches the interpreter, then it shouldn't // create a new thread state. for (int i = 0; i < 10; ++i) { - if (PyThreadState_Ensure(ref, &refs[i]) < 0) { + thread_views[i] = PyThreadState_Ensure(lock); + if (thread_views[i] == 0) { // This will technically leak other thread states, but it doesn't // matter because this is a test. - PyInterpreterLock_Release(ref); + PyInterpreterLock_Release(lock); return PyErr_NoMemory(); } @@ -2656,11 +2653,11 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) for (int i = 0; i < 10; ++i) { assert(PyThreadState_Get() == save_tstate); - PyThreadState_Release(refs[i]); + PyThreadState_Release(thread_views[i]); } assert(PyThreadState_GetUnchecked() == NULL); - PyInterpreterLock_Release(ref); + PyInterpreterLock_Release(lock); PyThreadState_Swap(save_tstate); Py_RETURN_NONE; } @@ -2668,11 +2665,11 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) static PyObject * test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) { - PyInterpreterLock ref = get_strong_ref(); + PyInterpreterLock lock = PyInterpreterLock_FromCurrent(); PyThreadState *save_tstate = PyThreadState_Swap(NULL); PyThreadState *interp_tstate = Py_NewInterpreter(); if (interp_tstate == NULL) { - PyInterpreterLock_Release(ref); + PyInterpreterLock_Release(lock); return PyErr_NoMemory(); } @@ -2689,79 +2686,69 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) interp = interpreters.create() interp.exec(some_func) */ - PyThreadView thread_ref; - PyThreadView other_thread_ref; - if (PyThreadState_Ensure(ref, &thread_ref) < 0) { - PyInterpreterLock_Release(ref); + PyThreadView thread_view = PyThreadState_Ensure(lock); + if (thread_view == 0) { + PyInterpreterLock_Release(lock); return PyErr_NoMemory(); } PyThreadState *ensured_tstate = PyThreadState_Get(); assert(ensured_tstate != save_tstate); - assert(PyInterpreterState_Get() == PyInterpreterLock_GetInterpreter(ref)); + assert(PyInterpreterState_Get() == PyInterpreterLock_GetInterpreter(lock)); assert(PyGILState_GetThisThreadState() == ensured_tstate); // Now though, we should reactivate the thread state - if (PyThreadState_Ensure(ref, &other_thread_ref) < 0) { - PyInterpreterLock_Release(ref); + PyThreadView other_thread_view = PyThreadState_Ensure(lock); + if (other_thread_view == 0) { + PyThreadState_Release(thread_view); + PyInterpreterLock_Release(lock); return PyErr_NoMemory(); } assert(PyThreadState_Get() == ensured_tstate); - PyThreadState_Release(other_thread_ref); + PyThreadState_Release(other_thread_view); // Ensure that we're restoring the prior thread state - PyThreadState_Release(thread_ref); + PyThreadState_Release(thread_view); assert(PyThreadState_Get() == interp_tstate); assert(PyGILState_GetThisThreadState() == interp_tstate); PyThreadState_Swap(interp_tstate); Py_EndInterpreter(interp_tstate); - PyInterpreterLock_Release(ref); + PyInterpreterLock_Release(lock); PyThreadState_Swap(save_tstate); Py_RETURN_NONE; } static PyObject * -test_weak_interpreter_ref_after_shutdown(PyObject *self, PyObject *unused) +test_interp_view_after_shutdown(PyObject *self, PyObject *unused) { PyThreadState *save_tstate = PyThreadState_Swap(NULL); - PyInterpreterView wref; PyThreadState *interp_tstate = Py_NewInterpreter(); if (interp_tstate == NULL) { return PyErr_NoMemory(); } - int res = PyInterpreterView_FromCurrent(&wref); - (void)res; - assert(res == 0); + PyInterpreterView view = PyInterpreterView_FromCurrent(); + if (view == 0) { + return PyErr_NoMemory(); + } - // As a sanity check, ensure that the weakref actually works - PyInterpreterLock ref; - res = PyInterpreterLock_FromView(wref, &ref); - assert(res == 0); - PyInterpreterLock_Release(ref); + // As a sanity check, ensure that the view actually works + PyInterpreterLock lock = PyInterpreterLock_FromView(view); + PyInterpreterLock_Release(lock); - // Now, destroy the interpreter and try to acquire a weak reference. + // Now, destroy the interpreter and try to acquire a lock from a view. // It should fail. Py_EndInterpreter(interp_tstate); - res = PyInterpreterLock_FromView(wref, &ref); - assert(res == -1); + lock = PyInterpreterLock_FromView(view); + assert(lock == 0); PyThreadState_Swap(save_tstate); Py_RETURN_NONE; } -static PyObject * -foo(PyObject *self, PyObject *foo) -{ - PyInterpreterLock ref; - PyInterpreterLock_FromCurrent(&ref); - PyInterpreterLock_Release(ref); - Py_RETURN_NONE; -} - static PyMethodDef TestMethods[] = { {"set_errno", set_errno, METH_VARARGS}, {"test_config", test_config, METH_NOARGS}, @@ -2856,11 +2843,10 @@ static PyMethodDef TestMethods[] = { {"test_atexit", test_atexit, METH_NOARGS}, {"code_offset_to_line", _PyCFunction_CAST(code_offset_to_line), METH_FASTCALL}, {"toggle_reftrace_printer", toggle_reftrace_printer, METH_O}, - {"test_interpreter_refs", test_interpreter_refs, METH_NOARGS}, + {"test_interpreter_lock", test_interpreter_locks, METH_NOARGS}, {"test_thread_state_ensure_nested", test_thread_state_ensure_nested, METH_NOARGS}, {"test_thread_state_ensure_crossinterp", test_thread_state_ensure_crossinterp, METH_NOARGS}, - {"test_weak_interpreter_ref_after_shutdown", test_weak_interpreter_ref_after_shutdown, METH_NOARGS}, - {"foo", foo, METH_NOARGS}, + {"test_interp_view_after_shutdown", test_interp_view_after_shutdown, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 8e87641ee9e673..8191f08b0b982e 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2418,59 +2418,57 @@ set_vectorcall_nop(PyObject *self, PyObject *func) Py_RETURN_NONE; } -#define NUM_REFS 100 +#define NUM_LOCKS 100 static PyObject * -test_interp_refcount(PyObject *self, PyObject *unused) +test_interp_lock_countdown(PyObject *self, PyObject *unused) { PyInterpreterState *interp = PyInterpreterState_Get(); assert(_PyInterpreterState_LockCountdown(interp) == 0); - PyInterpreterLock refs[NUM_REFS]; - for (int i = 0; i < NUM_REFS; ++i) { - int res = PyInterpreterLock_FromCurrent(&refs[i]); - (void)res; - assert(res == 0); + PyInterpreterLock locks[NUM_LOCKS]; + for (int i = 0; i < NUM_LOCKS; ++i) { + locks[i] = PyInterpreterLock_FromCurrent(); + assert(locks[i] != 0); assert(_PyInterpreterState_LockCountdown(interp) == i + 1); } - for (int i = 0; i < NUM_REFS; ++i) { - PyInterpreterLock_Release(refs[i]); - assert(_PyInterpreterState_LockCountdown(interp) == (NUM_REFS - i - 1)); + for (int i = 0; i < NUM_LOCKS; ++i) { + PyInterpreterLock_Release(locks[i]); + assert(_PyInterpreterState_LockCountdown(interp) == (NUM_LOCKS - i - 1)); } Py_RETURN_NONE; } static PyObject * -test_interp_weakref_incref(PyObject *self, PyObject *unused) +test_interp_view_countdown(PyObject *self, PyObject *unused) { PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterView wref; - if (PyInterpreterView_FromCurrent(&wref) < 0) { + PyInterpreterView view = PyInterpreterView_FromCurrent(); + if (view == 0) { return NULL; } assert(_PyInterpreterState_LockCountdown(interp) == 0); - PyInterpreterLock refs[NUM_REFS]; + PyInterpreterLock locks[NUM_LOCKS]; - for (int i = 0; i < NUM_REFS; ++i) { - int res = PyInterpreterLock_FromView(wref, &refs[i]); - (void)res; - assert(res == 0); - assert(PyInterpreterLock_GetInterpreter(refs[i]) == interp); + for (int i = 0; i < NUM_LOCKS; ++i) { + locks[i] = PyInterpreterLock_FromView(view); + assert(locks[i] != 0); + assert(PyInterpreterLock_GetInterpreter(locks[i]) == interp); assert(_PyInterpreterState_LockCountdown(interp) == i + 1); } - for (int i = 0; i < NUM_REFS; ++i) { - PyInterpreterLock_Release(refs[i]); - assert(_PyInterpreterState_LockCountdown(interp) == (NUM_REFS - i - 1)); + for (int i = 0; i < NUM_LOCKS; ++i) { + PyInterpreterLock_Release(locks[i]); + assert(_PyInterpreterState_LockCountdown(interp) == (NUM_LOCKS - i - 1)); } - PyInterpreterView_Close(wref); + PyInterpreterView_Close(view); Py_RETURN_NONE; } -#undef NUM_REFS +#undef NUM_LOCKS static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, @@ -2581,8 +2579,8 @@ static PyMethodDef module_functions[] = { #endif {"set_vectorcall_nop", set_vectorcall_nop, METH_O}, {"simple_pending_call", simple_pending_call, METH_O}, - {"test_interp_refcount", test_interp_refcount, METH_NOARGS}, - {"test_interp_weakref_incref", test_interp_weakref_incref, METH_NOARGS}, + {"test_interp_lock_countdown", test_interp_lock_countdown, METH_NOARGS}, + {"test_interp_view_countdown", test_interp_view_countdown, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 27ea3e22667f31..2758ec46a9330e 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2280,6 +2280,34 @@ test_get_incomplete_frame(void) return result; } +static void +do_gilstate_ensure(void *event_ptr) +{ + PyEvent *event = (PyEvent *)event_ptr; + // Signal to the calling thread that we've started + _PyEvent_Notify(event); + PyGILState_Ensure(); // This should hang + assert(NULL); +} + +static int +test_gilstate_after_finalization(void) +{ + _testembed_initialize(); + Py_Finalize(); + PyThread_handle_t handle; + PyThread_ident_t ident; + PyEvent event = {0}; + if (PyThread_start_joinable_thread(&do_gilstate_ensure, &event, &ident, &handle) < 0) { + return -1; + } + PyEvent_Wait(&event); + // We're now pretty confident that the thread went for + // PyGILState_Ensure(), but that means it got hung. + return PyThread_detach_thread(handle); +} + + const char *THREAD_CODE = \ "import time\n" "time.sleep(0.2)\n" @@ -2291,7 +2319,7 @@ const char *THREAD_CODE = \ "fib(10)"; typedef struct { - PyInterpreterLock ref; + PyInterpreterLock lock; int done; } ThreadData; @@ -2300,20 +2328,23 @@ do_tstate_ensure(void *arg) { ThreadData *data = (ThreadData *)arg; PyThreadView refs[4]; - int res = PyThreadState_Ensure(data->ref, &refs[0]); - assert(res == 0); - PyThreadState_Ensure(data->ref, &refs[1]); - PyThreadState_Ensure(data->ref, &refs[2]); + refs[0] = PyThreadState_Ensure(data->lock); + refs[1] = PyThreadState_Ensure(data->lock); + refs[2] = PyThreadState_Ensure(data->lock); PyGILState_STATE gstate = PyGILState_Ensure(); - PyThreadState_Ensure(data->ref, &refs[3]); - res = PyRun_SimpleString(THREAD_CODE); + refs[3] = PyThreadState_Ensure(data->lock); + assert(refs[0] != 0); + assert(refs[1] != 0); + assert(refs[2] != 0); + assert(refs[3] != 0); + int res = PyRun_SimpleString(THREAD_CODE); + assert(res == 0); PyThreadState_Release(refs[3]); PyGILState_Release(gstate); PyThreadState_Release(refs[2]); PyThreadState_Release(refs[1]); - assert(res == 0); PyThreadState_Release(refs[0]); - PyInterpreterLock_Release(data->ref); + PyInterpreterLock_Release(data->lock); data->done = 1; } @@ -2323,8 +2354,8 @@ test_thread_state_ensure(void) _testembed_initialize(); PyThread_handle_t handle; PyThread_ident_t ident; - PyInterpreterLock ref; - if (PyInterpreterLock_FromCurrent(&ref) < 0) { + PyInterpreterLock ref = PyInterpreterLock_FromCurrent(); + if (ref == 0) { return -1; }; ThreadData data = { ref }; @@ -2341,55 +2372,31 @@ test_thread_state_ensure(void) return 0; } -static void -do_gilstate_ensure(void *event_ptr) -{ - PyEvent *event = (PyEvent *)event_ptr; - // Signal to the calling thread that we've started - _PyEvent_Notify(event); - PyGILState_Ensure(); // This should hang - assert(NULL); -} - -static int -test_gilstate_after_finalization(void) -{ - _testembed_initialize(); - Py_Finalize(); - PyThread_handle_t handle; - PyThread_ident_t ident; - PyEvent event = {0}; - if (PyThread_start_joinable_thread(&do_gilstate_ensure, &event, &ident, &handle) < 0) { - return -1; - } - PyEvent_Wait(&event); - // We're now pretty confident that the thread went for - // PyGILState_Ensure(), but that means it got hung. - return PyThread_detach_thread(handle); -} - static int -test_main_interpreter_ref(void) +test_main_interpreter_view(void) { // It should not work before the runtime has started. - PyInterpreterLock ref; - int res = PyUnstable_InterpreterView_FromDefault(&ref); - (void)res; - assert(res == -1); + PyInterpreterView view = PyUnstable_InterpreterView_FromDefault(); + assert(view == 0); _testembed_initialize(); // Main interpreter is initialized and ready. - res = PyUnstable_InterpreterView_FromDefault(&ref); - assert(res == 0); - assert(PyInterpreterLock_GetInterpreter(ref) == PyInterpreterState_Main()); - PyInterpreterLock_Release(ref); + view = PyUnstable_InterpreterView_FromDefault(); + assert(view != 0); + + PyInterpreterLock lock = PyInterpreterLock_FromView(view); + assert(lock != 0); + PyInterpreterLock_Release(lock); Py_Finalize(); - // Main interpreter is dead, we can no longer acquire references to it. - res = PyUnstable_InterpreterView_FromDefault(&ref); - assert(res == -1); + // We shouldn't be able to get locks for the interpreter now + lock = PyInterpreterLock_FromView(view); + assert(lock == 0); + + PyInterpreterView_Close(view); + return 0; } @@ -2481,9 +2488,9 @@ static struct TestCase TestCases[] = { {"test_frozenmain", test_frozenmain}, #endif {"test_get_incomplete_frame", test_get_incomplete_frame}, - {"test_thread_state_ensure", test_thread_state_ensure}, {"test_gilstate_after_finalization", test_gilstate_after_finalization}, - {"test_main_interpreter_ref", test_main_interpreter_ref}, + {"test_thread_state_ensure", test_thread_state_ensure}, + {"test_main_interpreter_view", test_main_interpreter_view}, {NULL, NULL} }; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 3cb864ccb4a604..b5f765424057fe 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2130,7 +2130,7 @@ make_pre_finalization_calls(PyThreadState *tstate, int subinterpreters) // XXX Why does _PyThreadState_DeleteList() rely on all interpreters // being stopped? _PyEval_StopTheWorldAll(interp->runtime); - _PyRWMutex_Lock(&interp->references.lock); + _PyRWMutex_Lock(&interp->finalization_locks.lock); int has_subinterpreters = subinterpreters ? runtime_has_subinterpreters(interp->runtime) : 0; @@ -2139,13 +2139,13 @@ make_pre_finalization_calls(PyThreadState *tstate, int subinterpreters) || interp_has_atexit_callbacks(interp) || interp_has_pending_calls(interp) || has_subinterpreters - || interp->references.refcount > 0); + || interp->finalization_locks.countdown > 0); if (!should_continue) { break; } // Temporarily let other threads execute _PyThreadState_Detach(tstate); - _PyRWMutex_Unlock(&interp->references.lock); + _PyRWMutex_Unlock(&interp->finalization_locks.lock); _PyEval_StartTheWorldAll(interp->runtime); PyMutex_Unlock(&interp->ceval.pending.mutex); _PyThreadState_Attach(tstate); @@ -2209,7 +2209,7 @@ _Py_Finalize(_PyRuntimeState *runtime) for (PyThreadState *p = list; p != NULL; p = p->next) { _PyThreadState_SetShuttingDown(p); } - _PyRWMutex_Unlock(&tstate->interp->references.lock); + _PyRWMutex_Unlock(&tstate->interp->finalization_locks.lock); _PyEval_StartTheWorldAll(runtime); PyMutex_Unlock(&tstate->interp->ceval.pending.mutex); @@ -2579,7 +2579,7 @@ Py_EndInterpreter(PyThreadState *tstate) _PyThreadState_SetShuttingDown(p); } - _PyRWMutex_Unlock(&interp->references.lock); + _PyRWMutex_Unlock(&interp->finalization_locks.lock); _PyEval_StartTheWorldAll(interp->runtime); PyMutex_Unlock(&interp->ceval.pending.mutex); _PyThreadState_DeleteList(list, /*is_after_fork=*/0); diff --git a/Python/pystate.c b/Python/pystate.c index 6b8736bfc83963..699f987f48afe2 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1756,19 +1756,6 @@ PyThreadState_Clear(PyThreadState *tstate) static void decrement_stoptheworld_countdown(struct _stoptheworld_state *stw); -static void -decref_interpreter(PyInterpreterState *interp) -{ - assert(interp != NULL); - _PyRWMutex_RLock(&interp->references.lock); - Py_ssize_t old = _Py_atomic_add_ssize(&interp->references.refcount, -1); - _PyRWMutex_RUnlock(&interp->references.lock); - if (old <= 0) { - Py_FatalError("interpreter has negative reference count, likely due" - " to an extra PyInterpreterLock_Release()"); - } -} - /* Common code for PyThreadState_Delete() and PyThreadState_DeleteCurrent() */ static void tstate_delete_common(PyThreadState *tstate, int release_gil) @@ -2773,6 +2760,17 @@ PyGILState_Check(void) return (tstate == tcur); } +static PyInterpreterLock +get_main_interp_lock(void) +{ + PyInterpreterView view = PyUnstable_InterpreterView_FromDefault(); + if (view == 0) { + return 0; + } + + return PyInterpreterLock_FromView(view); +} + PyGILState_STATE PyGILState_Ensure(void) { @@ -2783,17 +2781,17 @@ PyGILState_Ensure(void) PyThreadState *tcur = gilstate_get(); int has_gil; - PyInterpreterLock ref; if (tcur == NULL) { /* Create a new Python thread state for this thread */ // XXX Use PyInterpreterState_EnsureThreadState()? - if (PyUnstable_InterpreterView_FromDefault(&ref) < 0) { + PyInterpreterLock lock = get_main_interp_lock(); + if (lock == 0) { // The main interpreter has finished, so we don't have // any intepreter to make a thread state for. Hang the // thread to act as failure. PyThread_hang_thread(); } - tcur = new_threadstate(PyInterpreterLock_GetInterpreter(ref), + tcur = new_threadstate(PyInterpreterLock_GetInterpreter(lock), _PyThreadState_WHENCE_GILSTATE); if (tcur == NULL) { Py_FatalError("Couldn't create thread-state for new thread"); @@ -2806,7 +2804,7 @@ PyGILState_Ensure(void) assert(tcur->gilstate_counter == 1); tcur->gilstate_counter = 0; has_gil = 0; /* new thread state is never current */ - PyInterpreterLock_Release(ref); + PyInterpreterLock_Release(lock); } else { has_gil = holds_gil(tcur); @@ -3152,172 +3150,171 @@ _Py_GetMainConfig(void) } Py_ssize_t -_PyInterpreterState_Refcount(PyInterpreterState *interp) +_PyInterpreterState_LockCountdown(PyInterpreterState *interp) { assert(interp != NULL); - return _Py_atomic_load_ssize_relaxed(&interp->references.refcount); + return _Py_atomic_load_ssize_relaxed(&interp->finalization_locks.countdown); } -static int -try_acquire_strong_ref(PyInterpreterState *interp, PyInterpreterLock *strong_ptr) +static PyInterpreterLock +try_acquire_interp_lock(PyInterpreterState *interp) { - _PyRWMutex_RLock(&interp->references.lock); + _PyRWMutex_RLock(&interp->finalization_locks.lock); if (_PyInterpreterState_GetFinalizing(interp) != NULL) { - *strong_ptr = 0; - _PyRWMutex_RUnlock(&interp->references.lock); - return -1; + _PyRWMutex_RUnlock(&interp->finalization_locks.lock); + return (PyInterpreterLock)interp; } - _Py_atomic_add_ssize(&interp->references.refcount, 1); - _PyRWMutex_RUnlock(&interp->references.lock); - *strong_ptr = (PyInterpreterLock)interp; - return 0; + _Py_atomic_add_ssize(&interp->finalization_locks.countdown, 1); + _PyRWMutex_RUnlock(&interp->finalization_locks.lock); + return (PyInterpreterLock)interp; } - static PyInterpreterState * -ref_as_interp(PyInterpreterLock ref) +lock_as_interp(PyInterpreterLock lock) { - PyInterpreterState *interp = (PyInterpreterState *)ref; + PyInterpreterState *interp = (PyInterpreterState *)lock; if (interp == NULL) { - Py_FatalError("Got a null interpreter reference, likely due to use after PyInterpreterLock_Release()"); + Py_FatalError("Got a null interpreter lock, likely due to" + " use after PyInterpreterLock_Release()"); } return interp; } -int -PyInterpreterLock_FromCurrent(PyInterpreterLock *ref) +PyInterpreterLock +PyInterpreterLock_FromCurrent(void) { - assert(ref != NULL); PyInterpreterState *interp = PyInterpreterState_Get(); - if (try_acquire_strong_ref(interp, ref)) { + PyInterpreterLock lock = try_acquire_interp_lock(interp); + if (lock == 0) { PyErr_SetString(PyExc_PythonFinalizationError, - "cannot acquire strong interpreter references anymore"); - return -1; + "cannot acquire finalization lock anymore"); + return 0; } - return 0; + return lock; } PyInterpreterLock -PyInterpreterLock_Copy(PyInterpreterLock ref) +PyInterpreterLock_Copy(PyInterpreterLock lock) { - PyInterpreterState *interp = ref_as_interp(ref); - PyInterpreterLock new_ref; - int res = try_acquire_strong_ref(interp, &new_ref); - (void)res; - // We already hold a strong reference, so it shouldn't be possible - // for the interpreter to be at a point where references don't work anymore - assert(res == 0); - assert(new_ref != 0); - return new_ref; + PyInterpreterState *interp = lock_as_interp(lock); + PyInterpreterLock new_lock = try_acquire_interp_lock(interp); + // We already hold a lock, so it shouldn't be possible + // for the interpreter to be at a point where locks don't work anymore + assert(new_lock != 0); + return new_lock; } #undef PyInterpreterLock_Release void -PyInterpreterLock_Release(PyInterpreterLock ref) +PyInterpreterLock_Release(PyInterpreterLock lock) { - PyInterpreterState *interp = ref_as_interp(ref); - decref_interpreter(interp); + PyInterpreterState *interp = lock_as_interp(lock); + assert(interp != NULL); + _PyRWMutex_RLock(&interp->finalization_locks.lock); + Py_ssize_t old = _Py_atomic_add_ssize(&interp->finalization_locks.countdown, -1); + _PyRWMutex_RUnlock(&interp->finalization_locks.lock); + if (old <= 0) { + Py_FatalError("interpreter has negative lock count, likely due" + " to an extra PyInterpreterLock_Release() call"); + } } PyInterpreterState * -PyInterpreterLock_GetInterpreter(PyInterpreterLock ref) +PyInterpreterLock_GetInterpreter(PyInterpreterLock lock) { - PyInterpreterState *interp = ref_as_interp(ref); + PyInterpreterState *interp = lock_as_interp(lock); return interp; } -int -PyInterpreterView_FromCurrent(PyInterpreterView *wref_ptr) +PyInterpreterView +PyInterpreterView_FromCurrent(void) { PyInterpreterState *interp = PyInterpreterState_Get(); /* PyInterpreterView_Close() can be called without an attached thread state, so we have to use the raw allocator. */ - _PyInterpreterView *wref = PyMem_RawMalloc(sizeof(_PyInterpreterView)); - if (wref == NULL) { + _PyInterpreterView *view = PyMem_RawMalloc(sizeof(_PyInterpreterView)); + if (view == NULL) { PyErr_NoMemory(); return -1; } - wref->refcount = 1; - wref->id = interp->id; - *wref_ptr = (PyInterpreterView)wref; - return 0; + view->refcount = 1; + view->id = interp->id; + return (PyInterpreterView)view; } static _PyInterpreterView * -wref_handle_as_ptr(PyInterpreterView wref_handle) +view_as_ptr(PyInterpreterView view_handle) { - _PyInterpreterView *wref = (_PyInterpreterView *)wref_handle; - if (wref == NULL) { - Py_FatalError("Got a null weak interpreter reference, likely due to use after PyInterpreterView_Close()"); + _PyInterpreterView *view = (_PyInterpreterView *)view_handle; + if (view == NULL) { + Py_FatalError("Got a null interpreter view, likely due to use after " + "PyInterpreterView_Close()"); } - return wref; + return view; } PyInterpreterView -PyInterpreterView_Copy(PyInterpreterView wref_handle) +PyInterpreterView_Copy(PyInterpreterView view_handle) { - _PyInterpreterView *wref = wref_handle_as_ptr(wref_handle); - ++wref->refcount; - return wref; + _PyInterpreterView *view = view_as_ptr(view_handle); + ++view->refcount; + return view_handle; } #undef PyInterpreterView_Close void -PyInterpreterView_Close(PyInterpreterView wref_handle) +PyInterpreterView_Close(PyInterpreterView view_handle) { - _PyInterpreterView *wref = wref_handle_as_ptr(wref_handle); - if (--wref->refcount == 0) { - PyMem_RawFree(wref); + _PyInterpreterView *view = view_as_ptr(view_handle); + if (--view->refcount == 0) { + PyMem_RawFree(view); } } -int -PyInterpreterLock_FromView(PyInterpreterView wref_handle, PyInterpreterLock *strong_ptr) +PyInterpreterLock +PyInterpreterLock_FromView(PyInterpreterView view_handle) { - assert(strong_ptr != NULL); - _PyInterpreterView *wref = wref_handle_as_ptr(wref_handle); - int64_t interp_id = wref->id; + _PyInterpreterView *view = view_as_ptr(view_handle); + int64_t interp_id = view->id; /* Interpreters cannot be deleted while we hold the runtime lock. */ _PyRuntimeState *runtime = &_PyRuntime; HEAD_LOCK(runtime); PyInterpreterState *interp = interp_look_up_id(runtime, interp_id); if (interp == NULL) { HEAD_UNLOCK(runtime); - *strong_ptr = 0; - return -1; + return 0; } - int res = try_acquire_strong_ref(interp, strong_ptr); + PyInterpreterLock lock = try_acquire_interp_lock(interp); HEAD_UNLOCK(runtime); - return res; + return lock; } -int -PyUnstable_InterpreterView_FromDefault(PyInterpreterLock *strong_ptr) +PyInterpreterView +PyUnstable_InterpreterView_FromDefault(void) { - assert(strong_ptr != NULL); _PyRuntimeState *runtime = &_PyRuntime; - HEAD_LOCK(runtime); - if (runtime->initialized == 0) { - // Main interpreter is not initialized. - // This can be the case before Py_Initialize(), or after Py_Finalize(). - HEAD_UNLOCK(runtime); - return -1; + _PyInterpreterView *view = PyMem_RawMalloc(sizeof(_PyInterpreterView)); + + if (view == NULL) { + return 0; } - int res = try_acquire_strong_ref(&runtime->_main_interpreter, strong_ptr); + + HEAD_LOCK(runtime); + view->id = runtime->_main_interpreter.id; + view->refcount = 1; HEAD_UNLOCK(runtime); - return res; + return (PyInterpreterView)view; } -int -PyThreadState_Ensure(PyInterpreterLock interp_ref, PyThreadView *thread_ref) +PyThreadView +PyThreadState_Ensure(PyInterpreterLock lock) { - PyInterpreterState *interp = ref_as_interp(interp_ref); + PyInterpreterState *interp = lock_as_interp(lock); PyThreadState *attached_tstate = current_fast_get(); - *thread_ref = 0; if (attached_tstate != NULL && attached_tstate->interp == interp) { /* Yay! We already have an attached thread state that matches. */ ++attached_tstate->ensure.counter; @@ -3342,7 +3339,7 @@ PyThreadState_Ensure(PyInterpreterLock interp_ref, PyThreadView *thread_ref) fresh_tstate->ensure.delete_on_release = 1; if (attached_tstate != NULL) { - *thread_ref = (PyThreadView)PyThreadState_Swap(fresh_tstate); + return (PyThreadView)PyThreadState_Swap(fresh_tstate); } else { _PyThreadState_Attach(fresh_tstate); } @@ -3351,7 +3348,7 @@ PyThreadState_Ensure(PyInterpreterLock interp_ref, PyThreadView *thread_ref) } void -PyThreadState_Release(PyThreadView thread_ref) +PyThreadState_Release(PyThreadView thread_view) { PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); @@ -3359,8 +3356,8 @@ PyThreadState_Release(PyThreadView thread_ref) if (remaining < 0) { Py_FatalError("PyThreadState_Release() called more times than PyThreadState_Ensure()"); } - // The thread reference might be NULL - PyThreadState *to_restore = (PyThreadState *)thread_ref; + // The thread view might be NULL + PyThreadState *to_restore = (PyThreadState *)thread_view; if (remaining == 0) { if (tstate->ensure.delete_on_release) { PyThreadState_Clear(tstate); From 705d5cb941f5ca20bd115d4ad1843043a1d3fb74 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 4 Oct 2025 11:46:09 -0400 Subject: [PATCH 69/85] Fix failing tests. --- Lib/test/test_embed.py | 4 ++-- Programs/_testembed.c | 6 +----- Python/pystate.c | 21 ++++++++++++++++----- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index e7fddf483c8ddd..964b04114a828a 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -1899,8 +1899,8 @@ def test_gilstate_after_finalization(self): def test_thread_state_ensure(self): self.run_embedded_interpreter("test_thread_state_ensure") - def test_main_interpreter_ref(self): - self.run_embedded_interpreter("test_main_interpreter_ref") + def test_main_interpreter_view(self): + self.run_embedded_interpreter("test_main_interpreter_view") class MiscTests(EmbeddingTestsMixin, unittest.TestCase): diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 2758ec46a9330e..e4382b5411139e 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2375,14 +2375,10 @@ test_thread_state_ensure(void) static int test_main_interpreter_view(void) { - // It should not work before the runtime has started. - PyInterpreterView view = PyUnstable_InterpreterView_FromDefault(); - assert(view == 0); - _testembed_initialize(); // Main interpreter is initialized and ready. - view = PyUnstable_InterpreterView_FromDefault(); + PyInterpreterView view = PyUnstable_InterpreterView_FromDefault(); assert(view != 0); PyInterpreterLock lock = PyInterpreterLock_FromView(view); diff --git a/Python/pystate.c b/Python/pystate.c index 699f987f48afe2..bf089c72c2bad2 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3310,6 +3310,11 @@ PyUnstable_InterpreterView_FromDefault(void) return (PyInterpreterView)view; } +// This is a bit of a hack -- since 0 is reserved for failure, we need +// to have our own sentinel for when we want to indicate that no prior +// thread state was attached. +static int NO_TSTATE_SENTINEL = 0; + PyThreadView PyThreadState_Ensure(PyInterpreterLock lock) { @@ -3318,7 +3323,7 @@ PyThreadState_Ensure(PyInterpreterLock lock) if (attached_tstate != NULL && attached_tstate->interp == interp) { /* Yay! We already have an attached thread state that matches. */ ++attached_tstate->ensure.counter; - return 0; + return (PyThreadView)&NO_TSTATE_SENTINEL; } PyThreadState *detached_gilstate = gilstate_get(); @@ -3327,13 +3332,13 @@ PyThreadState_Ensure(PyInterpreterLock lock) assert(attached_tstate == NULL); ++detached_gilstate->ensure.counter; _PyThreadState_Attach(detached_gilstate); - return 0; + return (PyThreadView)&NO_TSTATE_SENTINEL; } PyThreadState *fresh_tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_GILSTATE); if (fresh_tstate == NULL) { - return -1; + return 0; } fresh_tstate->ensure.counter = 1; fresh_tstate->ensure.delete_on_release = 1; @@ -3344,7 +3349,7 @@ PyThreadState_Ensure(PyInterpreterLock lock) _PyThreadState_Attach(fresh_tstate); } - return 0; + return (PyThreadView)&NO_TSTATE_SENTINEL; } void @@ -3357,7 +3362,13 @@ PyThreadState_Release(PyThreadView thread_view) Py_FatalError("PyThreadState_Release() called more times than PyThreadState_Ensure()"); } // The thread view might be NULL - PyThreadState *to_restore = (PyThreadState *)thread_view; + PyThreadState *to_restore; + if (thread_view == (PyThreadView)&NO_TSTATE_SENTINEL) { + to_restore = NULL; + } + else { + to_restore = (PyThreadState *)thread_view; + } if (remaining == 0) { if (tstate->ensure.delete_on_release) { PyThreadState_Clear(tstate); From 2dcf9753b7a41605c3241595dd2743c4918c0236 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 4 Oct 2025 11:48:22 -0400 Subject: [PATCH 70/85] Some touchups. --- Modules/_testcapimodule.c | 16 ++++++++-------- Modules/_testinternalcapi.c | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 491554e2344522..ca87ff97d9f18d 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2566,17 +2566,17 @@ static void test_interp_locks_common(void) { PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterLock ref = PyInterpreterLock_FromCurrent(); - assert(ref != 0); - assert(PyInterpreterLock_GetInterpreter(ref) == interp); + PyInterpreterLock lock = PyInterpreterLock_FromCurrent(); + assert(lock != 0); + assert(PyInterpreterLock_GetInterpreter(lock) == interp); - PyInterpreterLock ref_2 = PyInterpreterLock_Copy(ref); - assert(ref_2 != 0); - assert(PyInterpreterLock_GetInterpreter(ref_2) == interp); + PyInterpreterLock lock_2 = PyInterpreterLock_Copy(lock); + assert(lock_2 != 0); + assert(PyInterpreterLock_GetInterpreter(lock_2) == interp); // We can close the references in any order - PyInterpreterLock_Release(ref); - PyInterpreterLock_Release(ref_2); + PyInterpreterLock_Release(lock_2); + PyInterpreterLock_Release(lock); } static PyObject * diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 8191f08b0b982e..f5f0ceef595d48 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2577,8 +2577,8 @@ static PyMethodDef module_functions[] = { #ifdef __EMSCRIPTEN__ {"emscripten_set_up_async_input_device", emscripten_set_up_async_input_device, METH_NOARGS}, #endif - {"set_vectorcall_nop", set_vectorcall_nop, METH_O}, {"simple_pending_call", simple_pending_call, METH_O}, + {"set_vectorcall_nop", set_vectorcall_nop, METH_O}, {"test_interp_lock_countdown", test_interp_lock_countdown, METH_NOARGS}, {"test_interp_view_countdown", test_interp_view_countdown, METH_NOARGS}, {NULL, NULL} /* sentinel */ From 06520d244933cd644fdf7db196337080577597e8 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 4 Oct 2025 11:59:56 -0400 Subject: [PATCH 71/85] Fix C analyzer. --- Tools/c-analyzer/cpython/ignored.tsv | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index c3b13d69f0de8e..d2b3651c0e3e85 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -195,6 +195,9 @@ Python/import.c - pkgcontext - Python/pystate.c - _Py_tss_tstate - Python/pystate.c - _Py_tss_gilstate - +# Global sentinel that is fine to share across interpreters +Python/pystate.c - NO_TSTATE_SENTINEL - + ##----------------------- ## should be const # XXX Make them const. From 4371c15607e546eba5e1ce7be063d5fa20a52dcd Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 26 Oct 2025 21:49:26 -0400 Subject: [PATCH 72/85] Update to use new "PyInterpreterGuard" names. --- Include/cpython/pystate.h | 25 +++++++----- Modules/_testcapimodule.c | 62 ++++++++++++++-------------- Modules/_testinternalcapi.c | 36 ++++++++--------- Programs/_testembed.c | 32 +++++++-------- Python/pystate.c | 81 ++++++++++++++++++------------------- 5 files changed, 119 insertions(+), 117 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 5b5da1826ae889..d8bb68fe315de0 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -284,20 +284,22 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( /* Interpreter locks */ -typedef uintptr_t PyInterpreterLock; +typedef uintptr_t PyInterpreterGuard; typedef uintptr_t PyInterpreterView; -PyAPI_FUNC(PyInterpreterLock) PyInterpreterLock_FromCurrent(void); -PyAPI_FUNC(PyInterpreterLock) PyInterpreterLock_Copy(PyInterpreterLock lock); -PyAPI_FUNC(void) PyInterpreterLock_Release(PyInterpreterLock lock); -PyAPI_FUNC(PyInterpreterState *) PyInterpreterLock_GetInterpreter(PyInterpreterLock lock); -PyAPI_FUNC(PyInterpreterLock) PyInterpreterLock_FromView(PyInterpreterView view); +PyAPI_FUNC(PyInterpreterGuard) PyInterpreterGuard_FromCurrent(void); +PyAPI_FUNC(PyInterpreterGuard) PyInterpreterGuard_Copy(PyInterpreterGuard guard); +PyAPI_FUNC(void) PyInterpreterGuard_Release(PyInterpreterGuard guard); +PyAPI_FUNC(PyInterpreterState *) PyInterpreterGuard_GetInterpreter(PyInterpreterGuard guard); +PyAPI_FUNC(PyInterpreterGuard) PyInterpreterGuard_FromView(PyInterpreterView view); -#define PyInterpreterLock_Release(lock) do { \ - PyInterpreterLock_Release(lock); \ - lock = 0; \ +#ifdef Py_DEBUG +#define PyInterpreterGuard_Release(guard) do { \ + PyInterpreterGuard_Release(guard); \ + guard = 0; \ } while (0) +#endif /* Interpreter views */ @@ -312,16 +314,17 @@ PyAPI_FUNC(void) PyInterpreterView_Close(PyInterpreterView view); PyAPI_FUNC(PyInterpreterView) PyUnstable_InterpreterView_FromDefault(void); +#ifdef Py_DEBUG #define PyInterpreterView_Close(view) do { \ PyInterpreterView_Close(view); \ view = 0; \ } while (0) - +#endif /* Thread views */ typedef uintptr_t PyThreadView; -PyAPI_FUNC(PyThreadView) PyThreadState_Ensure(PyInterpreterLock lock); +PyAPI_FUNC(PyThreadView) PyThreadState_Ensure(PyInterpreterGuard guard); PyAPI_FUNC(void) PyThreadState_Release(PyThreadView thread_ref); diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index ca87ff97d9f18d..82d18b8c98c9db 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2563,32 +2563,32 @@ toggle_reftrace_printer(PyObject *ob, PyObject *arg) } static void -test_interp_locks_common(void) +test_interp_guards_common(void) { PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterLock lock = PyInterpreterLock_FromCurrent(); - assert(lock != 0); - assert(PyInterpreterLock_GetInterpreter(lock) == interp); + PyInterpreterGuard guard = PyInterpreterGuard_FromCurrent(); + assert(guard != 0); + assert(PyInterpreterGuard_GetInterpreter(guard) == interp); - PyInterpreterLock lock_2 = PyInterpreterLock_Copy(lock); - assert(lock_2 != 0); - assert(PyInterpreterLock_GetInterpreter(lock_2) == interp); + PyInterpreterGuard guard_2 = PyInterpreterGuard_Copy(guard); + assert(guard_2 != 0); + assert(PyInterpreterGuard_GetInterpreter(guard_2) == interp); // We can close the references in any order - PyInterpreterLock_Release(lock_2); - PyInterpreterLock_Release(lock); + PyInterpreterGuard_Release(guard_2); + PyInterpreterGuard_Release(guard); } static PyObject * test_interpreter_locks(PyObject *self, PyObject *unused) { // Test the main interpreter - test_interp_locks_common(); + test_interp_guards_common(); // Test a (legacy) subinterpreter PyThreadState *save_tstate = PyThreadState_Swap(NULL); PyThreadState *interp_tstate = Py_NewInterpreter(); - test_interp_locks_common(); + test_interp_guards_common(); Py_EndInterpreter(interp_tstate); // Test an isolated subinterpreter @@ -2604,7 +2604,7 @@ test_interpreter_locks(PyObject *self, PyObject *unused) return NULL; } - test_interp_locks_common(); + test_interp_guards_common(); Py_EndInterpreter(isolated_interp_tstate); PyThreadState_Swap(save_tstate); Py_RETURN_NONE; @@ -2613,8 +2613,8 @@ test_interpreter_locks(PyObject *self, PyObject *unused) static PyObject * test_thread_state_ensure_nested(PyObject *self, PyObject *unused) { - PyInterpreterLock lock = PyInterpreterLock_FromCurrent(); - if (lock == 0) { + PyInterpreterGuard guard = PyInterpreterGuard_FromCurrent(); + if (guard == 0) { return NULL; } PyThreadState *save_tstate = PyThreadState_Swap(NULL); @@ -2623,9 +2623,9 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) for (int i = 0; i < 10; ++i) { // Test reactivation of the detached tstate. - thread_views[i] = PyThreadState_Ensure(lock); + thread_views[i] = PyThreadState_Ensure(guard); if (thread_views[i] == 0) { - PyInterpreterLock_Release(lock); + PyInterpreterGuard_Release(guard); return PyErr_NoMemory(); } @@ -2640,11 +2640,11 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) // If the (detached) gilstate matches the interpreter, then it shouldn't // create a new thread state. for (int i = 0; i < 10; ++i) { - thread_views[i] = PyThreadState_Ensure(lock); + thread_views[i] = PyThreadState_Ensure(guard); if (thread_views[i] == 0) { // This will technically leak other thread states, but it doesn't // matter because this is a test. - PyInterpreterLock_Release(lock); + PyInterpreterGuard_Release(guard); return PyErr_NoMemory(); } @@ -2657,7 +2657,7 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) } assert(PyThreadState_GetUnchecked() == NULL); - PyInterpreterLock_Release(lock); + PyInterpreterGuard_Release(guard); PyThreadState_Swap(save_tstate); Py_RETURN_NONE; } @@ -2665,11 +2665,11 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) static PyObject * test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) { - PyInterpreterLock lock = PyInterpreterLock_FromCurrent(); + PyInterpreterGuard guard = PyInterpreterGuard_FromCurrent(); PyThreadState *save_tstate = PyThreadState_Swap(NULL); PyThreadState *interp_tstate = Py_NewInterpreter(); if (interp_tstate == NULL) { - PyInterpreterLock_Release(lock); + PyInterpreterGuard_Release(guard); return PyErr_NoMemory(); } @@ -2686,22 +2686,22 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) interp = interpreters.create() interp.exec(some_func) */ - PyThreadView thread_view = PyThreadState_Ensure(lock); + PyThreadView thread_view = PyThreadState_Ensure(guard); if (thread_view == 0) { - PyInterpreterLock_Release(lock); + PyInterpreterGuard_Release(guard); return PyErr_NoMemory(); } PyThreadState *ensured_tstate = PyThreadState_Get(); assert(ensured_tstate != save_tstate); - assert(PyInterpreterState_Get() == PyInterpreterLock_GetInterpreter(lock)); + assert(PyInterpreterState_Get() == PyInterpreterGuard_GetInterpreter(guard)); assert(PyGILState_GetThisThreadState() == ensured_tstate); // Now though, we should reactivate the thread state - PyThreadView other_thread_view = PyThreadState_Ensure(lock); + PyThreadView other_thread_view = PyThreadState_Ensure(guard); if (other_thread_view == 0) { PyThreadState_Release(thread_view); - PyInterpreterLock_Release(lock); + PyInterpreterGuard_Release(guard); return PyErr_NoMemory(); } @@ -2716,7 +2716,7 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) PyThreadState_Swap(interp_tstate); Py_EndInterpreter(interp_tstate); - PyInterpreterLock_Release(lock); + PyInterpreterGuard_Release(guard); PyThreadState_Swap(save_tstate); Py_RETURN_NONE; } @@ -2736,14 +2736,14 @@ test_interp_view_after_shutdown(PyObject *self, PyObject *unused) } // As a sanity check, ensure that the view actually works - PyInterpreterLock lock = PyInterpreterLock_FromView(view); - PyInterpreterLock_Release(lock); + PyInterpreterGuard guard = PyInterpreterGuard_FromView(view); + PyInterpreterGuard_Release(guard); // Now, destroy the interpreter and try to acquire a lock from a view. // It should fail. Py_EndInterpreter(interp_tstate); - lock = PyInterpreterLock_FromView(view); - assert(lock == 0); + guard = PyInterpreterGuard_FromView(view); + assert(guard == 0); PyThreadState_Swap(save_tstate); Py_RETURN_NONE; diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index f5f0ceef595d48..064693b3f085ee 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2418,23 +2418,23 @@ set_vectorcall_nop(PyObject *self, PyObject *func) Py_RETURN_NONE; } -#define NUM_LOCKS 100 +#define NUM_GUARDS 100 static PyObject * -test_interp_lock_countdown(PyObject *self, PyObject *unused) +test_interp_guard_countdown(PyObject *self, PyObject *unused) { PyInterpreterState *interp = PyInterpreterState_Get(); assert(_PyInterpreterState_LockCountdown(interp) == 0); - PyInterpreterLock locks[NUM_LOCKS]; - for (int i = 0; i < NUM_LOCKS; ++i) { - locks[i] = PyInterpreterLock_FromCurrent(); - assert(locks[i] != 0); + PyInterpreterGuard guards[NUM_GUARDS]; + for (int i = 0; i < NUM_GUARDS; ++i) { + guards[i] = PyInterpreterGuard_FromCurrent(); + assert(guards[i] != 0); assert(_PyInterpreterState_LockCountdown(interp) == i + 1); } - for (int i = 0; i < NUM_LOCKS; ++i) { - PyInterpreterLock_Release(locks[i]); - assert(_PyInterpreterState_LockCountdown(interp) == (NUM_LOCKS - i - 1)); + for (int i = 0; i < NUM_GUARDS; ++i) { + PyInterpreterGuard_Release(guards[i]); + assert(_PyInterpreterState_LockCountdown(interp) == (NUM_GUARDS - i - 1)); } Py_RETURN_NONE; @@ -2450,18 +2450,18 @@ test_interp_view_countdown(PyObject *self, PyObject *unused) } assert(_PyInterpreterState_LockCountdown(interp) == 0); - PyInterpreterLock locks[NUM_LOCKS]; + PyInterpreterGuard guards[NUM_GUARDS]; - for (int i = 0; i < NUM_LOCKS; ++i) { - locks[i] = PyInterpreterLock_FromView(view); - assert(locks[i] != 0); - assert(PyInterpreterLock_GetInterpreter(locks[i]) == interp); + for (int i = 0; i < NUM_GUARDS; ++i) { + guards[i] = PyInterpreterGuard_FromView(view); + assert(guards[i] != 0); + assert(PyInterpreterGuard_GetInterpreter(guards[i]) == interp); assert(_PyInterpreterState_LockCountdown(interp) == i + 1); } - for (int i = 0; i < NUM_LOCKS; ++i) { - PyInterpreterLock_Release(locks[i]); - assert(_PyInterpreterState_LockCountdown(interp) == (NUM_LOCKS - i - 1)); + for (int i = 0; i < NUM_GUARDS; ++i) { + PyInterpreterGuard_Release(guards[i]); + assert(_PyInterpreterState_LockCountdown(interp) == (NUM_GUARDS - i - 1)); } PyInterpreterView_Close(view); @@ -2579,7 +2579,7 @@ static PyMethodDef module_functions[] = { #endif {"simple_pending_call", simple_pending_call, METH_O}, {"set_vectorcall_nop", set_vectorcall_nop, METH_O}, - {"test_interp_lock_countdown", test_interp_lock_countdown, METH_NOARGS}, + {"test_interp_guard_countdown", test_interp_guard_countdown, METH_NOARGS}, {"test_interp_view_countdown", test_interp_view_countdown, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Programs/_testembed.c b/Programs/_testembed.c index e4382b5411139e..0819c632f3c775 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2319,7 +2319,7 @@ const char *THREAD_CODE = \ "fib(10)"; typedef struct { - PyInterpreterLock lock; + PyInterpreterGuard guard; int done; } ThreadData; @@ -2328,11 +2328,11 @@ do_tstate_ensure(void *arg) { ThreadData *data = (ThreadData *)arg; PyThreadView refs[4]; - refs[0] = PyThreadState_Ensure(data->lock); - refs[1] = PyThreadState_Ensure(data->lock); - refs[2] = PyThreadState_Ensure(data->lock); + refs[0] = PyThreadState_Ensure(data->guard); + refs[1] = PyThreadState_Ensure(data->guard); + refs[2] = PyThreadState_Ensure(data->guard); PyGILState_STATE gstate = PyGILState_Ensure(); - refs[3] = PyThreadState_Ensure(data->lock); + refs[3] = PyThreadState_Ensure(data->guard); assert(refs[0] != 0); assert(refs[1] != 0); assert(refs[2] != 0); @@ -2344,7 +2344,7 @@ do_tstate_ensure(void *arg) PyThreadState_Release(refs[2]); PyThreadState_Release(refs[1]); PyThreadState_Release(refs[0]); - PyInterpreterLock_Release(data->lock); + PyInterpreterGuard_Release(data->guard); data->done = 1; } @@ -2354,17 +2354,17 @@ test_thread_state_ensure(void) _testembed_initialize(); PyThread_handle_t handle; PyThread_ident_t ident; - PyInterpreterLock ref = PyInterpreterLock_FromCurrent(); - if (ref == 0) { + PyInterpreterGuard guard = PyInterpreterGuard_FromCurrent(); + if (guard == 0) { return -1; }; - ThreadData data = { ref }; + ThreadData data = { guard }; if (PyThread_start_joinable_thread(do_tstate_ensure, &data, &ident, &handle) < 0) { - PyInterpreterLock_Release(ref); + PyInterpreterGuard_Release(guard); return -1; } - // We hold a strong interpreter reference, so we don't + // We hold an interpreter guard, so we don't // have to worry about the interpreter shutting down before // we finalize. Py_Finalize(); @@ -2381,15 +2381,15 @@ test_main_interpreter_view(void) PyInterpreterView view = PyUnstable_InterpreterView_FromDefault(); assert(view != 0); - PyInterpreterLock lock = PyInterpreterLock_FromView(view); - assert(lock != 0); - PyInterpreterLock_Release(lock); + PyInterpreterGuard guard = PyInterpreterGuard_FromView(view); + assert(guard != 0); + PyInterpreterGuard_Release(guard); Py_Finalize(); // We shouldn't be able to get locks for the interpreter now - lock = PyInterpreterLock_FromView(view); - assert(lock == 0); + guard = PyInterpreterGuard_FromView(view); + assert(guard == 0); PyInterpreterView_Close(view); diff --git a/Python/pystate.c b/Python/pystate.c index 221e48b82c07c4..f1bf3d7da8d35a 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2754,15 +2754,15 @@ PyGILState_Check(void) return (tstate == tcur); } -static PyInterpreterLock -get_main_interp_lock(void) +static PyInterpreterGuard +get_main_interp_guard(void) { PyInterpreterView view = PyUnstable_InterpreterView_FromDefault(); if (view == 0) { return 0; } - return PyInterpreterLock_FromView(view); + return PyInterpreterGuard_FromView(view); } PyGILState_STATE @@ -2778,14 +2778,14 @@ PyGILState_Ensure(void) if (tcur == NULL) { /* Create a new Python thread state for this thread */ // XXX Use PyInterpreterState_EnsureThreadState()? - PyInterpreterLock lock = get_main_interp_lock(); - if (lock == 0) { + PyInterpreterGuard guard = get_main_interp_guard(); + if (guard == 0) { // The main interpreter has finished, so we don't have // any intepreter to make a thread state for. Hang the // thread to act as failure. PyThread_hang_thread(); } - tcur = new_threadstate(PyInterpreterLock_GetInterpreter(lock), + tcur = new_threadstate(PyInterpreterGuard_GetInterpreter(guard), _PyThreadState_WHENCE_GILSTATE); if (tcur == NULL) { Py_FatalError("Couldn't create thread-state for new thread"); @@ -2798,7 +2798,7 @@ PyGILState_Ensure(void) assert(tcur->gilstate_counter == 1); tcur->gilstate_counter = 0; has_gil = 0; /* new thread state is never current */ - PyInterpreterLock_Release(lock); + PyInterpreterGuard_Release(guard); } else { has_gil = holds_gil(tcur); @@ -3150,74 +3150,73 @@ _PyInterpreterState_LockCountdown(PyInterpreterState *interp) return _Py_atomic_load_ssize_relaxed(&interp->finalization_locks.countdown); } -static PyInterpreterLock -try_acquire_interp_lock(PyInterpreterState *interp) +static PyInterpreterGuard +try_acquire_interp_guard(PyInterpreterState *interp) { _PyRWMutex_RLock(&interp->finalization_locks.lock); if (_PyInterpreterState_GetFinalizing(interp) != NULL) { _PyRWMutex_RUnlock(&interp->finalization_locks.lock); - return (PyInterpreterLock)interp; + return (PyInterpreterGuard)interp; } _Py_atomic_add_ssize(&interp->finalization_locks.countdown, 1); _PyRWMutex_RUnlock(&interp->finalization_locks.lock); - return (PyInterpreterLock)interp; + return (PyInterpreterGuard)interp; } static PyInterpreterState * -lock_as_interp(PyInterpreterLock lock) +guard_as_interp(PyInterpreterGuard guard) { - PyInterpreterState *interp = (PyInterpreterState *)lock; + PyInterpreterState *interp = (PyInterpreterState *)guard; if (interp == NULL) { - Py_FatalError("Got a null interpreter lock, likely due to" - " use after PyInterpreterLock_Release()"); + Py_FatalError("Got a null interpreter guard"); } return interp; } -PyInterpreterLock -PyInterpreterLock_FromCurrent(void) +PyInterpreterGuard +PyInterpreterGuard_FromCurrent(void) { PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterLock lock = try_acquire_interp_lock(interp); - if (lock == 0) { + PyInterpreterGuard guard = try_acquire_interp_guard(interp); + if (guard == 0) { PyErr_SetString(PyExc_PythonFinalizationError, "cannot acquire finalization lock anymore"); return 0; } - return lock; + return guard; } -PyInterpreterLock -PyInterpreterLock_Copy(PyInterpreterLock lock) +PyInterpreterGuard +PyInterpreterGuard_Copy(PyInterpreterGuard guard) { - PyInterpreterState *interp = lock_as_interp(lock); - PyInterpreterLock new_lock = try_acquire_interp_lock(interp); - // We already hold a lock, so it shouldn't be possible - // for the interpreter to be at a point where locks don't work anymore - assert(new_lock != 0); - return new_lock; + PyInterpreterState *interp = guard_as_interp(guard); + PyInterpreterGuard new_guard = try_acquire_interp_guard(interp); + // We already hold a guard, so it shouldn't be possible + // for the interpreter to be at a point where guards don't work anymore + assert(new_guard != 0); + return new_guard; } -#undef PyInterpreterLock_Release +#undef PyInterpreterGuard_Release void -PyInterpreterLock_Release(PyInterpreterLock lock) +PyInterpreterGuard_Release(PyInterpreterGuard guard) { - PyInterpreterState *interp = lock_as_interp(lock); + PyInterpreterState *interp = guard_as_interp(guard); assert(interp != NULL); _PyRWMutex_RLock(&interp->finalization_locks.lock); Py_ssize_t old = _Py_atomic_add_ssize(&interp->finalization_locks.countdown, -1); _PyRWMutex_RUnlock(&interp->finalization_locks.lock); if (old <= 0) { - Py_FatalError("interpreter has negative lock count, likely due" - " to an extra PyInterpreterLock_Release() call"); + Py_FatalError("interpreter has negative guard count, likely due" + " to an extra PyInterpreterGuard_Release() call"); } } PyInterpreterState * -PyInterpreterLock_GetInterpreter(PyInterpreterLock lock) +PyInterpreterGuard_GetInterpreter(PyInterpreterGuard guard) { - PyInterpreterState *interp = lock_as_interp(lock); + PyInterpreterState *interp = guard_as_interp(guard); return interp; } @@ -3267,8 +3266,8 @@ PyInterpreterView_Close(PyInterpreterView view_handle) } } -PyInterpreterLock -PyInterpreterLock_FromView(PyInterpreterView view_handle) +PyInterpreterGuard +PyInterpreterGuard_FromView(PyInterpreterView view_handle) { _PyInterpreterView *view = view_as_ptr(view_handle); int64_t interp_id = view->id; @@ -3281,9 +3280,9 @@ PyInterpreterLock_FromView(PyInterpreterView view_handle) return 0; } - PyInterpreterLock lock = try_acquire_interp_lock(interp); + PyInterpreterGuard guard = try_acquire_interp_guard(interp); HEAD_UNLOCK(runtime); - return lock; + return guard; } PyInterpreterView @@ -3310,9 +3309,9 @@ PyUnstable_InterpreterView_FromDefault(void) static int NO_TSTATE_SENTINEL = 0; PyThreadView -PyThreadState_Ensure(PyInterpreterLock lock) +PyThreadState_Ensure(PyInterpreterGuard guard) { - PyInterpreterState *interp = lock_as_interp(lock); + PyInterpreterState *interp = guard_as_interp(guard); PyThreadState *attached_tstate = current_fast_get(); if (attached_tstate != NULL && attached_tstate->interp == interp) { /* Yay! We already have an attached thread state that matches. */ From 0c2ce0263ebda617da6ac372fe69e2ad3630aff3 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 26 Oct 2025 21:52:41 -0400 Subject: [PATCH 73/85] Fix some other missing cases. --- Include/internal/pycore_interp_structs.h | 2 +- Python/pylifecycle.c | 13 +++++++------ Python/pystate.c | 16 ++++++++-------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index a84b2ac711b53f..644e10778b570b 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -968,7 +968,7 @@ struct _is { struct { _PyRWMutex lock; Py_ssize_t countdown; - } finalization_locks; + } finalization_guards; /* the initial PyInterpreterState.threads.head */ _PyThreadStateImpl _initial_thread; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index b5f765424057fe..5b054ce17b9fbd 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2130,22 +2130,23 @@ make_pre_finalization_calls(PyThreadState *tstate, int subinterpreters) // XXX Why does _PyThreadState_DeleteList() rely on all interpreters // being stopped? _PyEval_StopTheWorldAll(interp->runtime); - _PyRWMutex_Lock(&interp->finalization_locks.lock); + _PyRWMutex_Lock(&interp->finalization_guards.lock); int has_subinterpreters = subinterpreters ? runtime_has_subinterpreters(interp->runtime) : 0; - // TODO: The interpreter reference countdown probably isn't very efficient. + // TODO: The interpreter guard countdown isn't very efficient. We should + // wait on an event or something like that. int should_continue = (interp_has_threads(interp) || interp_has_atexit_callbacks(interp) || interp_has_pending_calls(interp) || has_subinterpreters - || interp->finalization_locks.countdown > 0); + || interp->finalization_guards.countdown > 0); if (!should_continue) { break; } // Temporarily let other threads execute _PyThreadState_Detach(tstate); - _PyRWMutex_Unlock(&interp->finalization_locks.lock); + _PyRWMutex_Unlock(&interp->finalization_guards.lock); _PyEval_StartTheWorldAll(interp->runtime); PyMutex_Unlock(&interp->ceval.pending.mutex); _PyThreadState_Attach(tstate); @@ -2209,7 +2210,7 @@ _Py_Finalize(_PyRuntimeState *runtime) for (PyThreadState *p = list; p != NULL; p = p->next) { _PyThreadState_SetShuttingDown(p); } - _PyRWMutex_Unlock(&tstate->interp->finalization_locks.lock); + _PyRWMutex_Unlock(&tstate->interp->finalization_guards.lock); _PyEval_StartTheWorldAll(runtime); PyMutex_Unlock(&tstate->interp->ceval.pending.mutex); @@ -2579,7 +2580,7 @@ Py_EndInterpreter(PyThreadState *tstate) _PyThreadState_SetShuttingDown(p); } - _PyRWMutex_Unlock(&interp->finalization_locks.lock); + _PyRWMutex_Unlock(&interp->finalization_guards.lock); _PyEval_StartTheWorldAll(interp->runtime); PyMutex_Unlock(&interp->ceval.pending.mutex); _PyThreadState_DeleteList(list, /*is_after_fork=*/0); diff --git a/Python/pystate.c b/Python/pystate.c index f1bf3d7da8d35a..38f9e6e754c443 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3147,19 +3147,19 @@ Py_ssize_t _PyInterpreterState_LockCountdown(PyInterpreterState *interp) { assert(interp != NULL); - return _Py_atomic_load_ssize_relaxed(&interp->finalization_locks.countdown); + return _Py_atomic_load_ssize_relaxed(&interp->finalization_guards.countdown); } static PyInterpreterGuard try_acquire_interp_guard(PyInterpreterState *interp) { - _PyRWMutex_RLock(&interp->finalization_locks.lock); + _PyRWMutex_RLock(&interp->finalization_guards.lock); if (_PyInterpreterState_GetFinalizing(interp) != NULL) { - _PyRWMutex_RUnlock(&interp->finalization_locks.lock); + _PyRWMutex_RUnlock(&interp->finalization_guards.lock); return (PyInterpreterGuard)interp; } - _Py_atomic_add_ssize(&interp->finalization_locks.countdown, 1); - _PyRWMutex_RUnlock(&interp->finalization_locks.lock); + _Py_atomic_add_ssize(&interp->finalization_guards.countdown, 1); + _PyRWMutex_RUnlock(&interp->finalization_guards.lock); return (PyInterpreterGuard)interp; } @@ -3204,9 +3204,9 @@ PyInterpreterGuard_Release(PyInterpreterGuard guard) { PyInterpreterState *interp = guard_as_interp(guard); assert(interp != NULL); - _PyRWMutex_RLock(&interp->finalization_locks.lock); - Py_ssize_t old = _Py_atomic_add_ssize(&interp->finalization_locks.countdown, -1); - _PyRWMutex_RUnlock(&interp->finalization_locks.lock); + _PyRWMutex_RLock(&interp->finalization_guards.lock); + Py_ssize_t old = _Py_atomic_add_ssize(&interp->finalization_guards.countdown, -1); + _PyRWMutex_RUnlock(&interp->finalization_guards.lock); if (old <= 0) { Py_FatalError("interpreter has negative guard count, likely due" " to an extra PyInterpreterGuard_Release() call"); From 9230fa26082e95d8185f7a7b6d93e31af3ceeb92 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 7 Dec 2025 10:37:42 -0500 Subject: [PATCH 74/85] Fix stray changes. --- Include/cpython/pystate.h | 2 +- Include/internal/pycore_pystate.h | 2 +- Modules/_testcapimodule.c | 1 + Modules/_testinternalcapi.c | 50 +++++++++++++++---------------- Python/pystate.c | 3 +- 5 files changed, 30 insertions(+), 28 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index c07f71680dac1b..b736bce3aac53f 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -322,7 +322,7 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( PyInterpreterState *interp, _PyFrameEvalFunction eval_frame); -/* Interpreter locks */ +/* Interpreter guards */ typedef uintptr_t PyInterpreterGuard; typedef uintptr_t PyInterpreterView; diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index fda90067208734..3fdb8bb30e2ce8 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -339,7 +339,7 @@ _Py_RecursionLimit_GetMargin(PyThreadState *tstate) } // Exports for '_testinternalcapi' shared extension -PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_LockCountdown(PyInterpreterState *interp); +PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_GuardCountdown(PyInterpreterState *interp); #ifdef __cplusplus } diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index cfebe2ca8ac4ea..43ca2a3c8fb3b0 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2562,6 +2562,7 @@ toggle_reftrace_printer(PyObject *ob, PyObject *arg) Py_RETURN_NONE; } + typedef struct { PyObject_HEAD } ManagedWeakrefNoGCObject; diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index a78b35f9320f0c..ad687a24044701 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2460,28 +2460,6 @@ check_threadstate_set_stack_protection(PyThreadState *tstate, assert(ts->c_stack_soft_limit < ts->c_stack_top); } -#define NUM_GUARDS 100 - -static PyObject * -test_interp_guard_countdown(PyObject *self, PyObject *unused) -{ - PyInterpreterState *interp = PyInterpreterState_Get(); - assert(_PyInterpreterState_LockCountdown(interp) == 0); - PyInterpreterGuard guards[NUM_GUARDS]; - for (int i = 0; i < NUM_GUARDS; ++i) { - guards[i] = PyInterpreterGuard_FromCurrent(); - assert(guards[i] != 0); - assert(_PyInterpreterState_LockCountdown(interp) == i + 1); - } - - for (int i = 0; i < NUM_GUARDS; ++i) { - PyInterpreterGuard_Release(guards[i]); - assert(_PyInterpreterState_LockCountdown(interp) == (NUM_GUARDS - i - 1)); - } - - Py_RETURN_NONE; -} - static PyObject * test_threadstate_set_stack_protection(PyObject *self, PyObject *Py_UNUSED(args)) @@ -2519,6 +2497,28 @@ test_threadstate_set_stack_protection(PyObject *self, PyObject *Py_UNUSED(args)) Py_RETURN_NONE; } +#define NUM_GUARDS 100 + +static PyObject * +test_interp_guard_countdown(PyObject *self, PyObject *unused) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + assert(_PyInterpreterState_GuardCountdown(interp) == 0); + PyInterpreterGuard guards[NUM_GUARDS]; + for (int i = 0; i < NUM_GUARDS; ++i) { + guards[i] = PyInterpreterGuard_FromCurrent(); + assert(guards[i] != 0); + assert(_PyInterpreterState_GuardCountdown(interp) == i + 1); + } + + for (int i = 0; i < NUM_GUARDS; ++i) { + PyInterpreterGuard_Release(guards[i]); + assert(_PyInterpreterState_GuardCountdown(interp) == (NUM_GUARDS - i - 1)); + } + + Py_RETURN_NONE; +} + static PyObject * test_interp_view_countdown(PyObject *self, PyObject *unused) { @@ -2527,7 +2527,7 @@ test_interp_view_countdown(PyObject *self, PyObject *unused) if (view == 0) { return NULL; } - assert(_PyInterpreterState_LockCountdown(interp) == 0); + assert(_PyInterpreterState_GuardCountdown(interp) == 0); PyInterpreterGuard guards[NUM_GUARDS]; @@ -2535,12 +2535,12 @@ test_interp_view_countdown(PyObject *self, PyObject *unused) guards[i] = PyInterpreterGuard_FromView(view); assert(guards[i] != 0); assert(PyInterpreterGuard_GetInterpreter(guards[i]) == interp); - assert(_PyInterpreterState_LockCountdown(interp) == i + 1); + assert(_PyInterpreterState_GuardCountdown(interp) == i + 1); } for (int i = 0; i < NUM_GUARDS; ++i) { PyInterpreterGuard_Release(guards[i]); - assert(_PyInterpreterState_LockCountdown(interp) == (NUM_GUARDS - i - 1)); + assert(_PyInterpreterState_GuardCountdown(interp) == (NUM_GUARDS - i - 1)); } PyInterpreterView_Close(view); diff --git a/Python/pystate.c b/Python/pystate.c index 5955419419116b..69574cd9bb56fb 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1560,6 +1560,7 @@ new_threadstate(PyInterpreterState *interp, int whence) } #endif + #ifdef Py_STATS // The PyStats structure is quite large and is allocated separated from tstate. if (!_PyStats_ThreadInit(interp, tstate)) { @@ -3186,7 +3187,7 @@ _Py_GetMainConfig(void) } Py_ssize_t -_PyInterpreterState_LockCountdown(PyInterpreterState *interp) +_PyInterpreterState_GuardCountdown(PyInterpreterState *interp) { assert(interp != NULL); return _Py_atomic_load_ssize_relaxed(&interp->finalization_guards.countdown); From ba4c5388627267a40b460d00b8fbfb1d447ab981 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 19 Apr 2026 11:38:54 -0400 Subject: [PATCH 75/85] Remove PyThreadView. --- Include/cpython/pystate.h | 6 ++---- Modules/_testcapimodule.c | 6 +++--- Programs/_testembed.c | 2 +- Python/pystate.c | 14 +++++++------- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index a189e5532b70b1..caf8fbc1f057db 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -369,11 +369,9 @@ PyAPI_FUNC(PyInterpreterView) PyUnstable_InterpreterView_FromDefault(void); } while (0) #endif -typedef uintptr_t PyThreadView; +PyAPI_FUNC(PyThreadState *) PyThreadState_Ensure(PyInterpreterGuard guard); -PyAPI_FUNC(PyThreadView) PyThreadState_Ensure(PyInterpreterGuard guard); - -PyAPI_FUNC(void) PyThreadState_Release(PyThreadView thread_ref); +PyAPI_FUNC(void) PyThreadState_Release(PyThreadState *tstate); PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameAllowSpecialization( PyInterpreterState *interp, diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 55f6097b3bf6c8..90f71208994ac3 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2663,7 +2663,7 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) } PyThreadState *save_tstate = PyThreadState_Swap(NULL); assert(PyGILState_GetThisThreadState() == save_tstate); - PyThreadView thread_views[10]; + PyThreadState * thread_views[10]; for (int i = 0; i < 10; ++i) { // Test reactivation of the detached tstate. @@ -2730,7 +2730,7 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) interp = interpreters.create() interp.exec(some_func) */ - PyThreadView thread_view = PyThreadState_Ensure(guard); + PyThreadState * thread_view = PyThreadState_Ensure(guard); if (thread_view == 0) { PyInterpreterGuard_Release(guard); return PyErr_NoMemory(); @@ -2742,7 +2742,7 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) assert(PyGILState_GetThisThreadState() == ensured_tstate); // Now though, we should reactivate the thread state - PyThreadView other_thread_view = PyThreadState_Ensure(guard); + PyThreadState * other_thread_view = PyThreadState_Ensure(guard); if (other_thread_view == 0) { PyThreadState_Release(thread_view); PyInterpreterGuard_Release(guard); diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 08b9793e0e2964..dcfca7b337356b 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2690,7 +2690,7 @@ static void do_tstate_ensure(void *arg) { ThreadData *data = (ThreadData *)arg; - PyThreadView refs[4]; + PyThreadState * refs[4]; refs[0] = PyThreadState_Ensure(data->guard); refs[1] = PyThreadState_Ensure(data->guard); refs[2] = PyThreadState_Ensure(data->guard); diff --git a/Python/pystate.c b/Python/pystate.c index cef9842f81a1d4..fbec74d952636a 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3492,7 +3492,7 @@ PyUnstable_InterpreterView_FromDefault(void) // thread state was attached. static int NO_TSTATE_SENTINEL = 0; -PyThreadView +PyThreadState * PyThreadState_Ensure(PyInterpreterGuard guard) { PyInterpreterState *interp = guard_as_interp(guard); @@ -3500,7 +3500,7 @@ PyThreadState_Ensure(PyInterpreterGuard guard) if (attached_tstate != NULL && attached_tstate->interp == interp) { /* Yay! We already have an attached thread state that matches. */ ++attached_tstate->ensure.counter; - return (PyThreadView)&NO_TSTATE_SENTINEL; + return (PyThreadState *)&NO_TSTATE_SENTINEL; } PyThreadState *detached_gilstate = gilstate_get(); @@ -3509,7 +3509,7 @@ PyThreadState_Ensure(PyInterpreterGuard guard) assert(attached_tstate == NULL); ++detached_gilstate->ensure.counter; _PyThreadState_Attach(detached_gilstate); - return (PyThreadView)&NO_TSTATE_SENTINEL; + return (PyThreadState *)&NO_TSTATE_SENTINEL; } PyThreadState *fresh_tstate = _PyThreadState_NewBound(interp, @@ -3521,16 +3521,16 @@ PyThreadState_Ensure(PyInterpreterGuard guard) fresh_tstate->ensure.delete_on_release = 1; if (attached_tstate != NULL) { - return (PyThreadView)PyThreadState_Swap(fresh_tstate); + return (PyThreadState *)PyThreadState_Swap(fresh_tstate); } else { _PyThreadState_Attach(fresh_tstate); } - return (PyThreadView)&NO_TSTATE_SENTINEL; + return (PyThreadState *)&NO_TSTATE_SENTINEL; } void -PyThreadState_Release(PyThreadView thread_view) +PyThreadState_Release(PyThreadState * thread_view) { PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); @@ -3540,7 +3540,7 @@ PyThreadState_Release(PyThreadView thread_view) } // The thread view might be NULL PyThreadState *to_restore; - if (thread_view == (PyThreadView)&NO_TSTATE_SENTINEL) { + if (thread_view == (PyThreadState *)&NO_TSTATE_SENTINEL) { to_restore = NULL; } else { From a767e43f31b44f16875c3a5a838c2782e4609d0b Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 19 Apr 2026 12:15:23 -0400 Subject: [PATCH 76/85] Remove opaque pointer types. --- Include/cpython/pystate.h | 60 +++-------- Include/internal/pycore_pystate.h | 12 +++ Modules/_testcapimodule.c | 76 +++++++------- Modules/_testinternalcapi.c | 14 +-- Programs/_testembed.c | 24 ++--- Python/pystate.c | 164 ++++++++++++------------------ 6 files changed, 149 insertions(+), 201 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index caf8fbc1f057db..c4f0f22038577f 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -329,52 +329,24 @@ PyAPI_FUNC(_PyFrameEvalFunction) _PyInterpreterState_GetEvalFrameFunc( PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( PyInterpreterState *interp, _PyFrameEvalFunction eval_frame); - -/* Interpreter guards */ - -typedef uintptr_t PyInterpreterGuard; -typedef uintptr_t PyInterpreterView; - - -PyAPI_FUNC(PyInterpreterGuard) PyInterpreterGuard_FromCurrent(void); -PyAPI_FUNC(PyInterpreterGuard) PyInterpreterGuard_Copy(PyInterpreterGuard guard); -PyAPI_FUNC(void) PyInterpreterGuard_Release(PyInterpreterGuard guard); -PyAPI_FUNC(PyInterpreterState *) PyInterpreterGuard_GetInterpreter(PyInterpreterGuard guard); -PyAPI_FUNC(PyInterpreterGuard) PyInterpreterGuard_FromView(PyInterpreterView view); - -#ifdef Py_DEBUG -#define PyInterpreterGuard_Release(guard) do { \ - PyInterpreterGuard_Release(guard); \ - guard = 0; \ -} while (0) -#endif - -/* Interpreter views */ - -typedef struct _PyInterpreterView { - int64_t id; - Py_ssize_t refcount; -} _PyInterpreterView; - -PyAPI_FUNC(PyInterpreterView) PyInterpreterView_FromCurrent(void); -PyAPI_FUNC(PyInterpreterView) PyInterpreterView_Copy(PyInterpreterView view); -PyAPI_FUNC(void) PyInterpreterView_Close(PyInterpreterView view); -PyAPI_FUNC(PyInterpreterView) PyUnstable_InterpreterView_FromDefault(void); - - -#ifdef Py_DEBUG -#define PyInterpreterView_Close(view) do { \ - PyInterpreterView_Close(view); \ - view = 0; \ -} while (0) -#endif - -PyAPI_FUNC(PyThreadState *) PyThreadState_Ensure(PyInterpreterGuard guard); - -PyAPI_FUNC(void) PyThreadState_Release(PyThreadState *tstate); - PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameAllowSpecialization( PyInterpreterState *interp, int allow_specialization); PyAPI_FUNC(int) _PyInterpreterState_IsSpecializationEnabled( PyInterpreterState *interp); + +/* PEP 788 -- Interpreter guards and views. */ + +typedef struct _PyInterpreterGuard PyInterpreterGuard; +typedef struct _PyInterpreterView PyInterpreterView; + +PyAPI_FUNC(PyInterpreterGuard *) PyInterpreterGuard_FromCurrent(void); +PyAPI_FUNC(void) PyInterpreterGuard_Close(PyInterpreterGuard *guard); +PyAPI_FUNC(PyInterpreterGuard *) PyInterpreterGuard_FromView(PyInterpreterView *view); + +PyAPI_FUNC(PyInterpreterView *) PyInterpreterView_FromCurrent(void); +PyAPI_FUNC(void) PyInterpreterView_Close(PyInterpreterView *view); +PyAPI_FUNC(PyInterpreterView *) PyInterpreterView_FromMain(void); + +PyAPI_FUNC(PyThreadState *) PyThreadState_Ensure(PyInterpreterGuard *guard); +PyAPI_FUNC(void) PyThreadState_Release(PyThreadState *tstate); \ No newline at end of file diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 3fdb8bb30e2ce8..317dea7c1a4a98 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -338,8 +338,20 @@ _Py_RecursionLimit_GetMargin(PyThreadState *tstate) #endif } +/* PEP 788 structures. */ + +struct _PyInterpreterGuard { + PyInterpreterState *interp; +}; + +struct _PyInterpreterView { + int64_t id; + Py_ssize_t refcount; +}; + // Exports for '_testinternalcapi' shared extension PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_GuardCountdown(PyInterpreterState *interp); +PyAPI_FUNC(PyInterpreterState *) _PyInterpreterGuard_GetInterpreter(PyInterpreterGuard *guard); #ifdef __cplusplus } diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 90f71208994ac3..c704d6f5463867 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2609,18 +2609,15 @@ create_managed_weakref_nogc_type(PyObject *self, PyObject *Py_UNUSED(args)) static void test_interp_guards_common(void) { - PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterGuard guard = PyInterpreterGuard_FromCurrent(); - assert(guard != 0); - assert(PyInterpreterGuard_GetInterpreter(guard) == interp); + PyInterpreterGuard *guard = PyInterpreterGuard_FromCurrent(); + assert(guard != NULL); - PyInterpreterGuard guard_2 = PyInterpreterGuard_Copy(guard); - assert(guard_2 != 0); - assert(PyInterpreterGuard_GetInterpreter(guard_2) == interp); + PyInterpreterGuard *guard_2 = PyInterpreterGuard_FromCurrent(); + assert(guard_2 != NULL); - // We can close the references in any order - PyInterpreterGuard_Release(guard_2); - PyInterpreterGuard_Release(guard); + // We can close the guards in any order + PyInterpreterGuard_Close(guard_2); + PyInterpreterGuard_Close(guard); } static PyObject * @@ -2657,25 +2654,25 @@ test_interpreter_guards(PyObject *self, PyObject *unused) static PyObject * test_thread_state_ensure_nested(PyObject *self, PyObject *unused) { - PyInterpreterGuard guard = PyInterpreterGuard_FromCurrent(); - if (guard == 0) { + PyInterpreterGuard *guard = PyInterpreterGuard_FromCurrent(); + if (guard == NULL) { return NULL; } PyThreadState *save_tstate = PyThreadState_Swap(NULL); assert(PyGILState_GetThisThreadState() == save_tstate); - PyThreadState * thread_views[10]; + PyThreadState *thread_states[10]; for (int i = 0; i < 10; ++i) { // Test reactivation of the detached tstate. - thread_views[i] = PyThreadState_Ensure(guard); - if (thread_views[i] == 0) { - PyInterpreterGuard_Release(guard); + thread_states[i] = PyThreadState_Ensure(guard); + if (thread_states[i] == 0) { + PyInterpreterGuard_Close(guard); return PyErr_NoMemory(); } // No new thread state should've been created. assert(PyThreadState_Get() == save_tstate); - PyThreadState_Release(thread_views[i]); + PyThreadState_Release(thread_states[i]); } assert(PyThreadState_GetUnchecked() == NULL); @@ -2684,11 +2681,11 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) // If the (detached) gilstate matches the interpreter, then it shouldn't // create a new thread state. for (int i = 0; i < 10; ++i) { - thread_views[i] = PyThreadState_Ensure(guard); - if (thread_views[i] == 0) { + thread_states[i] = PyThreadState_Ensure(guard); + if (thread_states[i] == 0) { // This will technically leak other thread states, but it doesn't // matter because this is a test. - PyInterpreterGuard_Release(guard); + PyInterpreterGuard_Close(guard); return PyErr_NoMemory(); } @@ -2697,11 +2694,11 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) for (int i = 0; i < 10; ++i) { assert(PyThreadState_Get() == save_tstate); - PyThreadState_Release(thread_views[i]); + PyThreadState_Release(thread_states[i]); } assert(PyThreadState_GetUnchecked() == NULL); - PyInterpreterGuard_Release(guard); + PyInterpreterGuard_Close(guard); PyThreadState_Swap(save_tstate); Py_RETURN_NONE; } @@ -2709,11 +2706,11 @@ test_thread_state_ensure_nested(PyObject *self, PyObject *unused) static PyObject * test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) { - PyInterpreterGuard guard = PyInterpreterGuard_FromCurrent(); + PyInterpreterGuard *guard = PyInterpreterGuard_FromCurrent(); PyThreadState *save_tstate = PyThreadState_Swap(NULL); PyThreadState *interp_tstate = Py_NewInterpreter(); if (interp_tstate == NULL) { - PyInterpreterGuard_Release(guard); + PyInterpreterGuard_Close(guard); return PyErr_NoMemory(); } @@ -2730,37 +2727,36 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) interp = interpreters.create() interp.exec(some_func) */ - PyThreadState * thread_view = PyThreadState_Ensure(guard); - if (thread_view == 0) { - PyInterpreterGuard_Release(guard); + PyThreadState *thread_state = PyThreadState_Ensure(guard); + if (thread_state == NULL) { + PyInterpreterGuard_Close(guard); return PyErr_NoMemory(); } PyThreadState *ensured_tstate = PyThreadState_Get(); assert(ensured_tstate != save_tstate); - assert(PyInterpreterState_Get() == PyInterpreterGuard_GetInterpreter(guard)); assert(PyGILState_GetThisThreadState() == ensured_tstate); // Now though, we should reactivate the thread state - PyThreadState * other_thread_view = PyThreadState_Ensure(guard); - if (other_thread_view == 0) { - PyThreadState_Release(thread_view); - PyInterpreterGuard_Release(guard); + PyThreadState *other_thread_state = PyThreadState_Ensure(guard); + if (other_thread_state == NULL) { + PyThreadState_Release(thread_state); + PyInterpreterGuard_Close(guard); return PyErr_NoMemory(); } assert(PyThreadState_Get() == ensured_tstate); - PyThreadState_Release(other_thread_view); + PyThreadState_Release(other_thread_state); // Ensure that we're restoring the prior thread state - PyThreadState_Release(thread_view); + PyThreadState_Release(thread_state); assert(PyThreadState_Get() == interp_tstate); assert(PyGILState_GetThisThreadState() == interp_tstate); PyThreadState_Swap(interp_tstate); Py_EndInterpreter(interp_tstate); - PyInterpreterGuard_Release(guard); + PyInterpreterGuard_Close(guard); PyThreadState_Swap(save_tstate); Py_RETURN_NONE; } @@ -2774,20 +2770,20 @@ test_interp_view_after_shutdown(PyObject *self, PyObject *unused) return PyErr_NoMemory(); } - PyInterpreterView view = PyInterpreterView_FromCurrent(); - if (view == 0) { + PyInterpreterView *view = PyInterpreterView_FromCurrent(); + if (view == NULL) { return PyErr_NoMemory(); } // As a sanity check, ensure that the view actually works - PyInterpreterGuard guard = PyInterpreterGuard_FromView(view); - PyInterpreterGuard_Release(guard); + PyInterpreterGuard *guard = PyInterpreterGuard_FromView(view); + PyInterpreterGuard_Close(guard); // Now, destroy the interpreter and try to acquire a lock from a view. // It should fail. Py_EndInterpreter(interp_tstate); guard = PyInterpreterGuard_FromView(view); - assert(guard == 0); + assert(guard == NULL); PyThreadState_Swap(save_tstate); Py_RETURN_NONE; diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index bca6218715e51f..c882245ac7a214 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2883,7 +2883,7 @@ test_interp_guard_countdown(PyObject *self, PyObject *unused) { PyInterpreterState *interp = PyInterpreterState_Get(); assert(_PyInterpreterState_GuardCountdown(interp) == 0); - PyInterpreterGuard guards[NUM_GUARDS]; + PyInterpreterGuard *guards[NUM_GUARDS]; for (int i = 0; i < NUM_GUARDS; ++i) { guards[i] = PyInterpreterGuard_FromCurrent(); assert(guards[i] != 0); @@ -2891,7 +2891,7 @@ test_interp_guard_countdown(PyObject *self, PyObject *unused) } for (int i = 0; i < NUM_GUARDS; ++i) { - PyInterpreterGuard_Release(guards[i]); + PyInterpreterGuard_Close(guards[i]); assert(_PyInterpreterState_GuardCountdown(interp) == (NUM_GUARDS - i - 1)); } @@ -2902,23 +2902,23 @@ static PyObject * test_interp_view_countdown(PyObject *self, PyObject *unused) { PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterView view = PyInterpreterView_FromCurrent(); - if (view == 0) { + PyInterpreterView *view = PyInterpreterView_FromCurrent(); + if (view == NULL) { return NULL; } assert(_PyInterpreterState_GuardCountdown(interp) == 0); - PyInterpreterGuard guards[NUM_GUARDS]; + PyInterpreterGuard *guards[NUM_GUARDS]; for (int i = 0; i < NUM_GUARDS; ++i) { guards[i] = PyInterpreterGuard_FromView(view); assert(guards[i] != 0); - assert(PyInterpreterGuard_GetInterpreter(guards[i]) == interp); + assert(_PyInterpreterGuard_GetInterpreter(guards[i]) == interp); assert(_PyInterpreterState_GuardCountdown(interp) == i + 1); } for (int i = 0; i < NUM_GUARDS; ++i) { - PyInterpreterGuard_Release(guards[i]); + PyInterpreterGuard_Close(guards[i]); assert(_PyInterpreterState_GuardCountdown(interp) == (NUM_GUARDS - i - 1)); } diff --git a/Programs/_testembed.c b/Programs/_testembed.c index dcfca7b337356b..a49e452a58bdba 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2682,7 +2682,7 @@ const char *THREAD_CODE = \ "fib(10)"; typedef struct { - PyInterpreterGuard guard; + PyInterpreterGuard *guard; int done; } ThreadData; @@ -2690,7 +2690,7 @@ static void do_tstate_ensure(void *arg) { ThreadData *data = (ThreadData *)arg; - PyThreadState * refs[4]; + PyThreadState *refs[4]; refs[0] = PyThreadState_Ensure(data->guard); refs[1] = PyThreadState_Ensure(data->guard); refs[2] = PyThreadState_Ensure(data->guard); @@ -2707,7 +2707,7 @@ do_tstate_ensure(void *arg) PyThreadState_Release(refs[2]); PyThreadState_Release(refs[1]); PyThreadState_Release(refs[0]); - PyInterpreterGuard_Release(data->guard); + PyInterpreterGuard_Close(data->guard); data->done = 1; } @@ -2717,14 +2717,14 @@ test_thread_state_ensure(void) _testembed_initialize(); PyThread_handle_t handle; PyThread_ident_t ident; - PyInterpreterGuard guard = PyInterpreterGuard_FromCurrent(); - if (guard == 0) { + PyInterpreterGuard *guard = PyInterpreterGuard_FromCurrent(); + if (guard == NULL) { return -1; }; ThreadData data = { guard }; if (PyThread_start_joinable_thread(do_tstate_ensure, &data, &ident, &handle) < 0) { - PyInterpreterGuard_Release(guard); + PyInterpreterGuard_Close(guard); return -1; } // We hold an interpreter guard, so we don't @@ -2741,18 +2741,18 @@ test_main_interpreter_view(void) _testembed_initialize(); // Main interpreter is initialized and ready. - PyInterpreterView view = PyUnstable_InterpreterView_FromDefault(); - assert(view != 0); + PyInterpreterView *view = PyInterpreterView_FromMain(); + assert(view != NULL); - PyInterpreterGuard guard = PyInterpreterGuard_FromView(view); - assert(guard != 0); - PyInterpreterGuard_Release(guard); + PyInterpreterGuard *guard = PyInterpreterGuard_FromView(view); + assert(guard != NULL); + PyInterpreterGuard_Close(guard); Py_Finalize(); // We shouldn't be able to get locks for the interpreter now guard = PyInterpreterGuard_FromView(view); - assert(guard == 0); + assert(guard == NULL); PyInterpreterView_Close(view); diff --git a/Python/pystate.c b/Python/pystate.c index fbec74d952636a..678b7f38d72793 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2899,12 +2899,12 @@ PyGILState_Check(void) return (tstate == tcur); } -static PyInterpreterGuard +static PyInterpreterGuard * get_main_interp_guard(void) { - PyInterpreterView view = PyUnstable_InterpreterView_FromDefault(); - if (view == 0) { - return 0; + PyInterpreterView *view = PyInterpreterView_FromMain(); + if (view == NULL) { + return NULL; } return PyInterpreterGuard_FromView(view); @@ -2922,15 +2922,14 @@ PyGILState_Ensure(void) int has_gil; if (tcur == NULL) { /* Create a new Python thread state for this thread */ - // XXX Use PyInterpreterState_EnsureThreadState()? - PyInterpreterGuard guard = get_main_interp_guard(); - if (guard == 0) { + PyInterpreterGuard *guard = get_main_interp_guard(); + if (guard == NULL) { // The main interpreter has finished, so we don't have // any intepreter to make a thread state for. Hang the // thread to act as failure. PyThread_hang_thread(); } - tcur = new_threadstate(PyInterpreterGuard_GetInterpreter(guard), + tcur = new_threadstate(guard->interp, _PyThreadState_WHENCE_GILSTATE); if (tcur == NULL) { Py_FatalError("Couldn't create thread-state for new thread"); @@ -2943,7 +2942,7 @@ PyGILState_Ensure(void) assert(tcur->gilstate_counter == 1); tcur->gilstate_counter = 0; has_gil = 0; /* new thread state is never current */ - PyInterpreterGuard_Release(guard); + PyInterpreterGuard_Close(guard); } else { has_gil = holds_gil(tcur); @@ -3331,130 +3330,96 @@ Py_ssize_t _PyInterpreterState_GuardCountdown(PyInterpreterState *interp) { assert(interp != NULL); - return _Py_atomic_load_ssize_relaxed(&interp->finalization_guards.countdown); + Py_ssize_t count = _Py_atomic_load_ssize_relaxed(&interp->finalization_guards.countdown); + assert(count >= 0); + return count; +} + +PyInterpreterState * +_PyInterpreterGuard_GetInterpreter(PyInterpreterGuard *guard) +{ + assert(guard != NULL); + assert(guard->interp != NULL); + return guard->interp; } -static PyInterpreterGuard +static PyInterpreterGuard * try_acquire_interp_guard(PyInterpreterState *interp) { + assert(interp != NULL); _PyRWMutex_RLock(&interp->finalization_guards.lock); if (_PyInterpreterState_GetFinalizing(interp) != NULL) { _PyRWMutex_RUnlock(&interp->finalization_guards.lock); - return (PyInterpreterGuard)interp; + assert(_Py_atomic_load_ssize_relaxed(&interp->finalization_guards.countdown) == 0); + return NULL; } _Py_atomic_add_ssize(&interp->finalization_guards.countdown, 1); _PyRWMutex_RUnlock(&interp->finalization_guards.lock); - return (PyInterpreterGuard)interp; -} - -static PyInterpreterState * -guard_as_interp(PyInterpreterGuard guard) -{ - PyInterpreterState *interp = (PyInterpreterState *)guard; - if (interp == NULL) { - Py_FatalError("Got a null interpreter guard"); - } - - return interp; + return (PyInterpreterGuard *)interp; } -PyInterpreterGuard +PyInterpreterGuard * PyInterpreterGuard_FromCurrent(void) { - PyInterpreterState *interp = PyInterpreterState_Get(); - PyInterpreterGuard guard = try_acquire_interp_guard(interp); - if (guard == 0) { + PyInterpreterState *interp = _PyInterpreterState_GET(); + assert(interp != NULL); + PyInterpreterGuard *guard = try_acquire_interp_guard(interp); + if (guard == NULL) { PyErr_SetString(PyExc_PythonFinalizationError, - "cannot acquire finalization lock anymore"); - return 0; + "cannot acquire finalization guard anymore"); + return NULL; } return guard; } -PyInterpreterGuard -PyInterpreterGuard_Copy(PyInterpreterGuard guard) -{ - PyInterpreterState *interp = guard_as_interp(guard); - PyInterpreterGuard new_guard = try_acquire_interp_guard(interp); - // We already hold a guard, so it shouldn't be possible - // for the interpreter to be at a point where guards don't work anymore - assert(new_guard != 0); - return new_guard; -} - -#undef PyInterpreterGuard_Release void -PyInterpreterGuard_Release(PyInterpreterGuard guard) +PyInterpreterGuard_Close(PyInterpreterGuard *guard) { - PyInterpreterState *interp = guard_as_interp(guard); + PyInterpreterState *interp = guard->interp; assert(interp != NULL); _PyRWMutex_RLock(&interp->finalization_guards.lock); Py_ssize_t old = _Py_atomic_add_ssize(&interp->finalization_guards.countdown, -1); _PyRWMutex_RUnlock(&interp->finalization_guards.lock); + assert(old > 0); if (old <= 0) { Py_FatalError("interpreter has negative guard count, likely due" - " to an extra PyInterpreterGuard_Release() call"); + " to an extra PyInterpreterGuard_Close() call"); } } -PyInterpreterState * -PyInterpreterGuard_GetInterpreter(PyInterpreterGuard guard) -{ - PyInterpreterState *interp = guard_as_interp(guard); - return interp; -} - -PyInterpreterView +PyInterpreterView * PyInterpreterView_FromCurrent(void) { - PyInterpreterState *interp = PyInterpreterState_Get(); + PyInterpreterState *interp = _PyInterpreterState_GET(); + assert(interp != NULL); /* PyInterpreterView_Close() can be called without an attached thread state, so we have to use the raw allocator. */ - _PyInterpreterView *view = PyMem_RawMalloc(sizeof(_PyInterpreterView)); + PyInterpreterView *view = PyMem_RawMalloc(sizeof(PyInterpreterView)); if (view == NULL) { PyErr_NoMemory(); - return -1; + return NULL; } view->refcount = 1; view->id = interp->id; - return (PyInterpreterView)view; -} - -static _PyInterpreterView * -view_as_ptr(PyInterpreterView view_handle) -{ - _PyInterpreterView *view = (_PyInterpreterView *)view_handle; - if (view == NULL) { - Py_FatalError("Got a null interpreter view, likely due to use after " - "PyInterpreterView_Close()"); - } - return view; } -PyInterpreterView -PyInterpreterView_Copy(PyInterpreterView view_handle) -{ - _PyInterpreterView *view = view_as_ptr(view_handle); - ++view->refcount; - return view_handle; -} - -#undef PyInterpreterView_Close void -PyInterpreterView_Close(PyInterpreterView view_handle) +PyInterpreterView_Close(PyInterpreterView *view) { - _PyInterpreterView *view = view_as_ptr(view_handle); + assert(view != NULL); + assert(view->refcount > 0); if (--view->refcount == 0) { PyMem_RawFree(view); } } -PyInterpreterGuard -PyInterpreterGuard_FromView(PyInterpreterView view_handle) +PyInterpreterGuard * +PyInterpreterGuard_FromView(PyInterpreterView *view) { - _PyInterpreterView *view = view_as_ptr(view_handle); + assert(view != NULL); int64_t interp_id = view->id; + assert(interp_id >= 0); /* Interpreters cannot be deleted while we hold the runtime lock. */ _PyRuntimeState *runtime = &_PyRuntime; HEAD_LOCK(runtime); @@ -3464,38 +3429,41 @@ PyInterpreterGuard_FromView(PyInterpreterView view_handle) return 0; } - PyInterpreterGuard guard = try_acquire_interp_guard(interp); + PyInterpreterGuard *guard = try_acquire_interp_guard(interp); HEAD_UNLOCK(runtime); return guard; } -PyInterpreterView -PyUnstable_InterpreterView_FromDefault(void) +PyInterpreterView * +PyInterpreterView_FromMain(void) { - _PyRuntimeState *runtime = &_PyRuntime; - _PyInterpreterView *view = PyMem_RawMalloc(sizeof(_PyInterpreterView)); - + PyInterpreterView *view = PyMem_RawMalloc(sizeof(PyInterpreterView)); if (view == NULL) { - return 0; + return NULL; } + _PyRuntimeState *runtime = &_PyRuntime; HEAD_LOCK(runtime); view->id = runtime->_main_interpreter.id; view->refcount = 1; HEAD_UNLOCK(runtime); - return (PyInterpreterView)view; + return view; } -// This is a bit of a hack -- since 0 is reserved for failure, we need +// This is a bit of a hack -- since NULL is reserved for failure, we need // to have our own sentinel for when we want to indicate that no prior // thread state was attached. +// To do this, we just use the memory address of a global variable and +// cast it to a PyThreadState *. static int NO_TSTATE_SENTINEL = 0; PyThreadState * -PyThreadState_Ensure(PyInterpreterGuard guard) +PyThreadState_Ensure(PyInterpreterGuard *guard) { - PyInterpreterState *interp = guard_as_interp(guard); + assert(guard != NULL); + PyInterpreterState *interp = guard->interp; + assert(interp != NULL); PyThreadState *attached_tstate = current_fast_get(); if (attached_tstate != NULL && attached_tstate->interp == interp) { /* Yay! We already have an attached thread state that matches. */ @@ -3515,13 +3483,13 @@ PyThreadState_Ensure(PyInterpreterGuard guard) PyThreadState *fresh_tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_GILSTATE); if (fresh_tstate == NULL) { - return 0; + return NULL; } fresh_tstate->ensure.counter = 1; fresh_tstate->ensure.delete_on_release = 1; if (attached_tstate != NULL) { - return (PyThreadState *)PyThreadState_Swap(fresh_tstate); + return PyThreadState_Swap(fresh_tstate); } else { _PyThreadState_Attach(fresh_tstate); } @@ -3530,7 +3498,7 @@ PyThreadState_Ensure(PyInterpreterGuard guard) } void -PyThreadState_Release(PyThreadState * thread_view) +PyThreadState_Release(PyThreadState *old_tstate) { PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); @@ -3540,11 +3508,11 @@ PyThreadState_Release(PyThreadState * thread_view) } // The thread view might be NULL PyThreadState *to_restore; - if (thread_view == (PyThreadState *)&NO_TSTATE_SENTINEL) { + if (old_tstate == (PyThreadState *)&NO_TSTATE_SENTINEL) { to_restore = NULL; } else { - to_restore = (PyThreadState *)thread_view; + to_restore = old_tstate; } if (remaining == 0) { if (tstate->ensure.delete_on_release) { From b9d3bc8b3f2e09c1e1ed58c97a230a79033b3c7a Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 19 Apr 2026 12:17:14 -0400 Subject: [PATCH 77/85] Rename WHENCE_GILSTATE to WHENCE_C_API. --- Include/cpython/pystate.h | 2 +- Python/pystate.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index c4f0f22038577f..990e23ce6d04a0 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -105,7 +105,7 @@ struct _ts { # define _PyThreadState_WHENCE_INIT 1 # define _PyThreadState_WHENCE_FINI 2 # define _PyThreadState_WHENCE_THREADING 3 -# define _PyThreadState_WHENCE_GILSTATE 4 +# define _PyThreadState_WHENCE_C_API 4 # define _PyThreadState_WHENCE_EXEC 5 # define _PyThreadState_WHENCE_THREADING_DAEMON 6 #endif diff --git a/Python/pystate.c b/Python/pystate.c index 678b7f38d72793..d5b65490593e3c 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2930,7 +2930,7 @@ PyGILState_Ensure(void) PyThread_hang_thread(); } tcur = new_threadstate(guard->interp, - _PyThreadState_WHENCE_GILSTATE); + _PyThreadState_WHENCE_C_API); if (tcur == NULL) { Py_FatalError("Couldn't create thread-state for new thread"); } @@ -3481,7 +3481,7 @@ PyThreadState_Ensure(PyInterpreterGuard *guard) } PyThreadState *fresh_tstate = _PyThreadState_NewBound(interp, - _PyThreadState_WHENCE_GILSTATE); + _PyThreadState_WHENCE_C_API); if (fresh_tstate == NULL) { return NULL; } From 878803ead7d84af1251dcf76826d7e4e69ca0748 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 19 Apr 2026 12:29:23 -0400 Subject: [PATCH 78/85] Add PyThreadState_EnsureFromView(). --- Include/cpython/pystate.h | 4 +++ Python/pystate.c | 67 +++++++++++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 990e23ce6d04a0..6ada9e3f35d1d4 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -239,6 +239,7 @@ struct _ts { // structure and all share the same per-interpreter structure). PyStats *pystats; #endif + struct { /* Number of nested PyThreadState_Ensure() calls on this thread state */ Py_ssize_t counter; @@ -248,6 +249,9 @@ struct _ts { This is only true for thread states created by PyThreadState_Ensure() */ int delete_on_release; + + /* The interpreter guard owned by PyThreadState_EnsureFromView(), if any. */ + PyInterpreterGuard *owned_guard; } ensure; }; diff --git a/Python/pystate.c b/Python/pystate.c index d5b65490593e3c..edfda8659380c1 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3348,11 +3348,13 @@ try_acquire_interp_guard(PyInterpreterState *interp) { assert(interp != NULL); _PyRWMutex_RLock(&interp->finalization_guards.lock); + if (_PyInterpreterState_GetFinalizing(interp) != NULL) { _PyRWMutex_RUnlock(&interp->finalization_guards.lock); assert(_Py_atomic_load_ssize_relaxed(&interp->finalization_guards.countdown) == 0); return NULL; } + _Py_atomic_add_ssize(&interp->finalization_guards.countdown, 1); _PyRWMutex_RUnlock(&interp->finalization_guards.lock); return (PyInterpreterGuard *)interp; @@ -3363,12 +3365,14 @@ PyInterpreterGuard_FromCurrent(void) { PyInterpreterState *interp = _PyInterpreterState_GET(); assert(interp != NULL); + PyInterpreterGuard *guard = try_acquire_interp_guard(interp); if (guard == NULL) { PyErr_SetString(PyExc_PythonFinalizationError, "cannot acquire finalization guard anymore"); return NULL; } + return guard; } @@ -3377,9 +3381,11 @@ PyInterpreterGuard_Close(PyInterpreterGuard *guard) { PyInterpreterState *interp = guard->interp; assert(interp != NULL); + _PyRWMutex_RLock(&interp->finalization_guards.lock); Py_ssize_t old = _Py_atomic_add_ssize(&interp->finalization_guards.countdown, -1); _PyRWMutex_RUnlock(&interp->finalization_guards.lock); + assert(old > 0); if (old <= 0) { Py_FatalError("interpreter has negative guard count, likely due" @@ -3392,13 +3398,15 @@ PyInterpreterView_FromCurrent(void) { PyInterpreterState *interp = _PyInterpreterState_GET(); assert(interp != NULL); - /* PyInterpreterView_Close() can be called without an attached thread - state, so we have to use the raw allocator. */ + + // PyInterpreterView_Close() can be called without an attached thread + // state, so we have to use the raw allocator. PyInterpreterView *view = PyMem_RawMalloc(sizeof(PyInterpreterView)); if (view == NULL) { PyErr_NoMemory(); return NULL; } + view->refcount = 1; view->id = interp->id; return view; @@ -3420,7 +3428,8 @@ PyInterpreterGuard_FromView(PyInterpreterView *view) assert(view != NULL); int64_t interp_id = view->id; assert(interp_id >= 0); - /* Interpreters cannot be deleted while we hold the runtime lock. */ + + // Interpreters cannot be deleted while we hold the runtime lock. _PyRuntimeState *runtime = &_PyRuntime; HEAD_LOCK(runtime); PyInterpreterState *interp = interp_look_up_id(runtime, interp_id); @@ -3497,6 +3506,31 @@ PyThreadState_Ensure(PyInterpreterGuard *guard) return (PyThreadState *)&NO_TSTATE_SENTINEL; } +PyThreadState * +PyThreadState_EnsureFromView(PyInterpreterView *view) +{ + assert(view != NULL); + PyInterpreterGuard *guard = PyInterpreterGuard_FromView(view); + if (guard == NULL) { + return NULL; + } + + PyThreadState *tstate = PyThreadState_Ensure(guard); + if (tstate == NULL) { + PyInterpreterGuard_Close(guard); + return NULL; + } + + if (tstate->ensure.owned_guard != NULL) { + assert(tstate->ensure.owned_guard->interp == guard->interp); + PyInterpreterGuard_Close(guard); + } else { + tstate->ensure.owned_guard = guard; + } + + return tstate; +} + void PyThreadState_Release(PyThreadState *old_tstate) { @@ -3506,7 +3540,11 @@ PyThreadState_Release(PyThreadState *old_tstate) if (remaining < 0) { Py_FatalError("PyThreadState_Release() called more times than PyThreadState_Ensure()"); } - // The thread view might be NULL + + if (remaining != 0) { + return; + } + PyThreadState *to_restore; if (old_tstate == (PyThreadState *)&NO_TSTATE_SENTINEL) { to_restore = NULL; @@ -3514,15 +3552,18 @@ PyThreadState_Release(PyThreadState *old_tstate) else { to_restore = old_tstate; } - if (remaining == 0) { - if (tstate->ensure.delete_on_release) { - PyThreadState_Clear(tstate); - PyThreadState_Swap(to_restore); - PyThreadState_Delete(tstate); - } else { - PyThreadState_Swap(to_restore); - } + + assert(tstate->ensure.delete_on_release == 1 || tstate->ensure.delete_on_release == 0); + if (tstate->ensure.delete_on_release) { + PyThreadState_Clear(tstate); + PyThreadState_Swap(to_restore); + PyThreadState_Delete(tstate); + } else { + PyThreadState_Swap(to_restore); } - return; + if (tstate->ensure.owned_guard != NULL) { + PyInterpreterGuard_Close(tstate->ensure.owned_guard); + tstate->ensure.owned_guard = NULL; + } } From 8bf18992f5c528636fc4d85248ce451739953402 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 19 Apr 2026 12:32:45 -0400 Subject: [PATCH 79/85] Fix compilation errors. --- Include/cpython/pystate.h | 18 +----------------- Include/pystate.h | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 6ada9e3f35d1d4..1b23d5d60bb79c 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -337,20 +337,4 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameAllowSpecialization( PyInterpreterState *interp, int allow_specialization); PyAPI_FUNC(int) _PyInterpreterState_IsSpecializationEnabled( - PyInterpreterState *interp); - -/* PEP 788 -- Interpreter guards and views. */ - -typedef struct _PyInterpreterGuard PyInterpreterGuard; -typedef struct _PyInterpreterView PyInterpreterView; - -PyAPI_FUNC(PyInterpreterGuard *) PyInterpreterGuard_FromCurrent(void); -PyAPI_FUNC(void) PyInterpreterGuard_Close(PyInterpreterGuard *guard); -PyAPI_FUNC(PyInterpreterGuard *) PyInterpreterGuard_FromView(PyInterpreterView *view); - -PyAPI_FUNC(PyInterpreterView *) PyInterpreterView_FromCurrent(void); -PyAPI_FUNC(void) PyInterpreterView_Close(PyInterpreterView *view); -PyAPI_FUNC(PyInterpreterView *) PyInterpreterView_FromMain(void); - -PyAPI_FUNC(PyThreadState *) PyThreadState_Ensure(PyInterpreterGuard *guard); -PyAPI_FUNC(void) PyThreadState_Release(PyThreadState *tstate); \ No newline at end of file + PyInterpreterState *interp); \ No newline at end of file diff --git a/Include/pystate.h b/Include/pystate.h index 727b8fbfffe0e6..9e4758c57481e5 100644 --- a/Include/pystate.h +++ b/Include/pystate.h @@ -120,6 +120,22 @@ PyAPI_FUNC(void) PyGILState_Release(PyGILState_STATE); PyAPI_FUNC(PyThreadState *) PyGILState_GetThisThreadState(void); +/* PEP 788 -- Interpreter guards and views. */ + +typedef struct _PyInterpreterGuard PyInterpreterGuard; +typedef struct _PyInterpreterView PyInterpreterView; + +PyAPI_FUNC(PyInterpreterGuard *) PyInterpreterGuard_FromCurrent(void); +PyAPI_FUNC(void) PyInterpreterGuard_Close(PyInterpreterGuard *guard); +PyAPI_FUNC(PyInterpreterGuard *) PyInterpreterGuard_FromView(PyInterpreterView *view); + +PyAPI_FUNC(PyInterpreterView *) PyInterpreterView_FromCurrent(void); +PyAPI_FUNC(void) PyInterpreterView_Close(PyInterpreterView *view); +PyAPI_FUNC(PyInterpreterView *) PyInterpreterView_FromMain(void); + +PyAPI_FUNC(PyThreadState *) PyThreadState_Ensure(PyInterpreterGuard *guard); +PyAPI_FUNC(void) PyThreadState_Release(PyThreadState *tstate); + #ifndef Py_LIMITED_API # define Py_CPYTHON_PYSTATE_H # include "cpython/pystate.h" From 33202413377ce023d21e04c28acd2aba68de8777 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 20 Apr 2026 18:40:16 -0400 Subject: [PATCH 80/85] Use a dedicated heap allocation for interpreter guards. --- Python/pystate.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Python/pystate.c b/Python/pystate.c index edfda8659380c1..d0ae2502575bb8 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1618,6 +1618,7 @@ static void add_threadstate(PyInterpreterState *interp, PyThreadState *tstate, PyThreadState *next) { + assert(interp != NULL); assert(interp->threads.head != tstate); if (next != NULL) { assert(next->prev == NULL || next->prev == tstate); @@ -3355,9 +3356,17 @@ try_acquire_interp_guard(PyInterpreterState *interp) return NULL; } + PyInterpreterGuard *guard = PyMem_RawMalloc(sizeof(PyInterpreterGuard)); + if (guard == NULL) { + _PyRWMutex_RUnlock(&interp->finalization_guards.lock); + return NULL; + } + _Py_atomic_add_ssize(&interp->finalization_guards.countdown, 1); _PyRWMutex_RUnlock(&interp->finalization_guards.lock); - return (PyInterpreterGuard *)interp; + + guard->interp = interp; + return guard; } PyInterpreterGuard * @@ -3391,6 +3400,8 @@ PyInterpreterGuard_Close(PyInterpreterGuard *guard) Py_FatalError("interpreter has negative guard count, likely due" " to an extra PyInterpreterGuard_Close() call"); } + + PyMem_RawFree(guard); } PyInterpreterView * @@ -3440,6 +3451,8 @@ PyInterpreterGuard_FromView(PyInterpreterView *view) PyInterpreterGuard *guard = try_acquire_interp_guard(interp); HEAD_UNLOCK(runtime); + + assert(guard == NULL || guard->interp != NULL); return guard; } From 77039065e8023771ff9678849ff602f21e5deff2 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 20 Apr 2026 20:23:48 -0400 Subject: [PATCH 81/85] Fix PyThreadState_EnsureFromView() and add a test. --- Include/pystate.h | 1 + Modules/_testcapimodule.c | 62 +++++++++++++++++++++++++++++++++++++++ Python/pylifecycle.c | 2 +- Python/pystate.c | 55 +++++++++++++++++++++------------- 4 files changed, 98 insertions(+), 22 deletions(-) diff --git a/Include/pystate.h b/Include/pystate.h index 9e4758c57481e5..254702d43e8728 100644 --- a/Include/pystate.h +++ b/Include/pystate.h @@ -134,6 +134,7 @@ PyAPI_FUNC(void) PyInterpreterView_Close(PyInterpreterView *view); PyAPI_FUNC(PyInterpreterView *) PyInterpreterView_FromMain(void); PyAPI_FUNC(PyThreadState *) PyThreadState_Ensure(PyInterpreterGuard *guard); +PyAPI_FUNC(PyThreadState *) PyThreadState_EnsureFromView(PyInterpreterView *view); PyAPI_FUNC(void) PyThreadState_Release(PyThreadState *tstate); #ifndef Py_LIMITED_API diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index c704d6f5463867..d9660329485b5f 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2767,11 +2767,14 @@ test_interp_view_after_shutdown(PyObject *self, PyObject *unused) PyThreadState *save_tstate = PyThreadState_Swap(NULL); PyThreadState *interp_tstate = Py_NewInterpreter(); if (interp_tstate == NULL) { + PyThreadState_Swap(save_tstate); return PyErr_NoMemory(); } PyInterpreterView *view = PyInterpreterView_FromCurrent(); if (view == NULL) { + Py_EndInterpreter(interp_tstate); + PyThreadState_Swap(save_tstate); return PyErr_NoMemory(); } @@ -2789,6 +2792,64 @@ test_interp_view_after_shutdown(PyObject *self, PyObject *unused) Py_RETURN_NONE; } +static PyObject * +test_thread_state_ensure_view(PyObject *self, PyObject *unused) +{ + // For simplicity's sake, we assume that functions won't fail due to being + // out of memory. + PyThreadState *save_tstate = PyThreadState_Swap(NULL); + PyThreadState *interp_tstate = Py_NewInterpreter(); + assert(interp_tstate != NULL); + assert(PyInterpreterState_Get() == PyThreadState_GetInterpreter(interp_tstate)); + + PyInterpreterView *main_view = PyInterpreterView_FromMain(); + assert(main_view != NULL); + + PyInterpreterView *view = PyInterpreterView_FromCurrent(); + assert(view != NULL); + + Py_BEGIN_ALLOW_THREADS; + PyThreadState *tstate = PyThreadState_EnsureFromView(view); + assert(tstate != NULL); + assert(PyThreadState_Get() == interp_tstate); + + // Test a nested call + PyThreadState *tstate2 = PyThreadState_EnsureFromView(view); + assert(PyThreadState_Get() == interp_tstate); + + // We're in a new interpreter now. PyThreadState_EnsureFromView() should + // now create a new thread state. + PyThreadState *main_tstate = PyThreadState_EnsureFromView(main_view); + assert(main_tstate == interp_tstate); // The old thread state + assert(PyInterpreterState_Get() == PyInterpreterState_Main()); + + // Going back to the old interpreter should create a new thread state again. + PyThreadState *tstate3 = PyThreadState_EnsureFromView(view); + assert(PyInterpreterState_Get() == PyThreadState_GetInterpreter(interp_tstate)); + assert(PyThreadState_Get() != interp_tstate); + PyThreadState_Release(tstate3); + PyThreadState_Release(main_tstate); + + // We're back in the original interpreter. PyThreadState_EnsureFromView() should + // no longer create a new thread state. + assert(PyThreadState_Get() == interp_tstate); + PyThreadState *tstate4 = PyThreadState_EnsureFromView(view); + assert(PyThreadState_Get() == interp_tstate); + PyThreadState_Release(tstate4); + PyThreadState_Release(tstate2); + PyThreadState_Release(tstate); + assert(PyThreadState_GetUnchecked() == NULL); + Py_END_ALLOW_THREADS; + + assert(PyThreadState_Get() == interp_tstate); + PyInterpreterView_Close(view); + PyInterpreterView_Close(main_view); + Py_EndInterpreter(interp_tstate); + PyThreadState_Swap(save_tstate); + + Py_RETURN_NONE; +} + static PyObject* test_soft_deprecated_macros(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) @@ -2927,6 +2988,7 @@ static PyMethodDef TestMethods[] = { {"test_thread_state_ensure_nested", test_thread_state_ensure_nested, METH_NOARGS}, {"test_thread_state_ensure_crossinterp", test_thread_state_ensure_crossinterp, METH_NOARGS}, {"test_interp_view_after_shutdown", test_interp_view_after_shutdown, METH_NOARGS}, + {"test_thread_state_ensure_view", test_thread_state_ensure_view, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 1a36830e8b15c2..5936481a75de95 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2340,7 +2340,7 @@ make_pre_finalization_calls(PyThreadState *tstate, int subinterpreters) || interp_has_atexit_callbacks(interp) || interp_has_pending_calls(interp) || has_subinterpreters - || interp->finalization_guards.countdown > 0); + || _Py_atomic_load_ssize_acquire(&interp->finalization_guards.countdown) > 0); if (!should_continue) { break; } diff --git a/Python/pystate.c b/Python/pystate.c index d0ae2502575bb8..bb5aafbd2edb14 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3344,8 +3344,8 @@ _PyInterpreterGuard_GetInterpreter(PyInterpreterGuard *guard) return guard->interp; } -static PyInterpreterGuard * -try_acquire_interp_guard(PyInterpreterState *interp) +static int +try_acquire_interp_guard(PyInterpreterState *interp, PyInterpreterGuard *guard) { assert(interp != NULL); _PyRWMutex_RLock(&interp->finalization_guards.lock); @@ -3353,20 +3353,14 @@ try_acquire_interp_guard(PyInterpreterState *interp) if (_PyInterpreterState_GetFinalizing(interp) != NULL) { _PyRWMutex_RUnlock(&interp->finalization_guards.lock); assert(_Py_atomic_load_ssize_relaxed(&interp->finalization_guards.countdown) == 0); - return NULL; - } - - PyInterpreterGuard *guard = PyMem_RawMalloc(sizeof(PyInterpreterGuard)); - if (guard == NULL) { - _PyRWMutex_RUnlock(&interp->finalization_guards.lock); - return NULL; + return -1; } _Py_atomic_add_ssize(&interp->finalization_guards.countdown, 1); _PyRWMutex_RUnlock(&interp->finalization_guards.lock); guard->interp = interp; - return guard; + return 0; } PyInterpreterGuard * @@ -3375,8 +3369,14 @@ PyInterpreterGuard_FromCurrent(void) PyInterpreterState *interp = _PyInterpreterState_GET(); assert(interp != NULL); - PyInterpreterGuard *guard = try_acquire_interp_guard(interp); + PyInterpreterGuard *guard = PyMem_RawMalloc(sizeof(PyInterpreterGuard)); if (guard == NULL) { + PyErr_NoMemory(); + return NULL; + } + + if (try_acquire_interp_guard(interp, guard) < 0) { + PyMem_RawFree(guard); PyErr_SetString(PyExc_PythonFinalizationError, "cannot acquire finalization guard anymore"); return NULL; @@ -3396,11 +3396,6 @@ PyInterpreterGuard_Close(PyInterpreterGuard *guard) _PyRWMutex_RUnlock(&interp->finalization_guards.lock); assert(old > 0); - if (old <= 0) { - Py_FatalError("interpreter has negative guard count, likely due" - " to an extra PyInterpreterGuard_Close() call"); - } - PyMem_RawFree(guard); } @@ -3440,18 +3435,32 @@ PyInterpreterGuard_FromView(PyInterpreterView *view) int64_t interp_id = view->id; assert(interp_id >= 0); + // This allocation has to happen before we acquire the runtime lock, because + // PyMem_RawMalloc() might call some weird callback (such as tracemalloc) + // that tries to re-entrantly acquire the lock. + PyInterpreterGuard *guard = PyMem_RawMalloc(sizeof(PyInterpreterGuard)); + if (guard == NULL) { + return NULL; + } + // Interpreters cannot be deleted while we hold the runtime lock. _PyRuntimeState *runtime = &_PyRuntime; HEAD_LOCK(runtime); PyInterpreterState *interp = interp_look_up_id(runtime, interp_id); if (interp == NULL) { HEAD_UNLOCK(runtime); - return 0; + PyMem_RawFree(guard); + return NULL; } - PyInterpreterGuard *guard = try_acquire_interp_guard(interp); + int result = try_acquire_interp_guard(interp, guard); HEAD_UNLOCK(runtime); + if (result < 0) { + PyMem_RawFree(guard); + return NULL; + } + assert(guard == NULL || guard->interp != NULL); return guard; } @@ -3528,20 +3537,24 @@ PyThreadState_EnsureFromView(PyInterpreterView *view) return NULL; } - PyThreadState *tstate = PyThreadState_Ensure(guard); - if (tstate == NULL) { + PyThreadState *result_tstate = PyThreadState_Ensure(guard); + if (result_tstate == NULL) { PyInterpreterGuard_Close(guard); return NULL; } + PyThreadState *tstate = current_fast_get(); + assert(tstate != NULL); + if (tstate->ensure.owned_guard != NULL) { assert(tstate->ensure.owned_guard->interp == guard->interp); PyInterpreterGuard_Close(guard); } else { + assert(tstate->ensure.owned_guard == NULL); tstate->ensure.owned_guard = guard; } - return tstate; + return result_tstate; } void From 19dfacad05c5576d4478579b26cc05b5c9d515e2 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 20 Apr 2026 20:34:16 -0400 Subject: [PATCH 82/85] Add a race test for PyThreadState_EnsureFromView. --- Lib/test/test_embed.py | 3 ++ Programs/_testembed.c | 74 ++++++++++++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 52f7b6da990521..831aae9f264519 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -2002,6 +2002,9 @@ def test_thread_state_ensure(self): def test_main_interpreter_view(self): self.run_embedded_interpreter("test_main_interpreter_view") + def test_thread_state_ensure_from_view(self): + self.run_embedded_interpreter("test_thread_state_ensure_from_view") + class MiscTests(EmbeddingTestsMixin, unittest.TestCase): def test_unicode_id_init(self): diff --git a/Programs/_testembed.c b/Programs/_testembed.c index a49e452a58bdba..8575fb30c95a8d 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2682,32 +2682,34 @@ const char *THREAD_CODE = \ "fib(10)"; typedef struct { - PyInterpreterGuard *guard; + void *argument; int done; + PyEvent event; } ThreadData; static void do_tstate_ensure(void *arg) { ThreadData *data = (ThreadData *)arg; - PyThreadState *refs[4]; - refs[0] = PyThreadState_Ensure(data->guard); - refs[1] = PyThreadState_Ensure(data->guard); - refs[2] = PyThreadState_Ensure(data->guard); + PyThreadState *tstates[4]; + PyInterpreterGuard *guard = data->argument; + tstates[0] = PyThreadState_Ensure(guard); + tstates[1] = PyThreadState_Ensure(guard); + tstates[2] = PyThreadState_Ensure(guard); PyGILState_STATE gstate = PyGILState_Ensure(); - refs[3] = PyThreadState_Ensure(data->guard); - assert(refs[0] != 0); - assert(refs[1] != 0); - assert(refs[2] != 0); - assert(refs[3] != 0); + tstates[3] = PyThreadState_Ensure(guard); + assert(tstates[0] != NULL); + assert(tstates[1] != NULL); + assert(tstates[2] != NULL); + assert(tstates[3] != NULL); int res = PyRun_SimpleString(THREAD_CODE); assert(res == 0); - PyThreadState_Release(refs[3]); + PyThreadState_Release(tstates[3]); PyGILState_Release(gstate); - PyThreadState_Release(refs[2]); - PyThreadState_Release(refs[1]); - PyThreadState_Release(refs[0]); - PyInterpreterGuard_Close(data->guard); + PyThreadState_Release(tstates[2]); + PyThreadState_Release(tstates[1]); + PyThreadState_Release(tstates[0]); + PyInterpreterGuard_Close(guard); data->done = 1; } @@ -2718,9 +2720,7 @@ test_thread_state_ensure(void) PyThread_handle_t handle; PyThread_ident_t ident; PyInterpreterGuard *guard = PyInterpreterGuard_FromCurrent(); - if (guard == NULL) { - return -1; - }; + assert(guard != NULL); ThreadData data = { guard }; if (PyThread_start_joinable_thread(do_tstate_ensure, &data, &ident, &handle) < 0) { @@ -2759,6 +2759,43 @@ test_main_interpreter_view(void) return 0; } +static void +do_tstate_ensure_from_view(void *arg) +{ + ThreadData *data = (ThreadData *)arg; + PyInterpreterView *view = data->argument; + assert(view != NULL); + PyThreadState *tstate = PyThreadState_EnsureFromView(view); + assert(tstate != NULL); + _PyEvent_Notify(&data->event); + int res = PyRun_SimpleString(THREAD_CODE); + assert(res == 0); + data->done = 1; + PyThreadState_Release(tstate); +} + +static int +test_thread_state_ensure_from_view(void) +{ + _testembed_initialize(); + PyThread_handle_t handle; + PyThread_ident_t ident; + PyInterpreterView *view = PyInterpreterView_FromCurrent(); + assert(view != NULL); + + ThreadData data = { view }; + if (PyThread_start_joinable_thread(do_tstate_ensure_from_view, &data, + &ident, &handle) < 0) { + PyInterpreterView_Close(view); + return -1; + } + + PyEvent_Wait(&data.event); + Py_Finalize(); + assert(data.done == 1); + return 0; +} + /* ********************************************************* * List of test cases and the function that implements it. * @@ -2855,6 +2892,7 @@ static struct TestCase TestCases[] = { {"test_inittab_submodule_singlephase", test_inittab_submodule_singlephase}, {"test_thread_state_ensure", test_thread_state_ensure}, {"test_main_interpreter_view", test_main_interpreter_view}, + {"test_thread_state_ensure_from_view", test_thread_state_ensure_from_view}, {NULL, NULL} }; From 23a84abb8e44c7c9380813a007f056da8288b0eb Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 20 Apr 2026 21:07:53 -0400 Subject: [PATCH 83/85] Use an event for waiting on guards. --- Include/cpython/pystate.h | 2 +- Include/internal/pycore_interp_structs.h | 1 + Python/pylifecycle.c | 23 ++++++++++++++++------- Python/pystate.c | 15 ++++++++++++++- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 1b23d5d60bb79c..a9d97e47e005df 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -337,4 +337,4 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameAllowSpecialization( PyInterpreterState *interp, int allow_specialization); PyAPI_FUNC(int) _PyInterpreterState_IsSpecializationEnabled( - PyInterpreterState *interp); \ No newline at end of file + PyInterpreterState *interp); diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index fb4a1f4dc37ca8..6a0141a88bd526 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -1053,6 +1053,7 @@ struct _is { struct { _PyRWMutex lock; Py_ssize_t countdown; + PyEvent done; } finalization_guards; /* the initial PyInterpreterState.threads.head */ diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 5936481a75de95..4860423fafdd93 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2309,16 +2309,25 @@ make_pre_finalization_calls(PyThreadState *tstate, int subinterpreters) if (subinterpreters) { /* Clean up any lingering subinterpreters. - - Two preconditions need to be met here: - - - This has to happen before _PyRuntimeState_SetFinalizing is - called, or else threads might get prematurely blocked. - - The world must not be stopped, as finalizers can run. - */ + * Two preconditions need to be met here: + * 1. This has to happen before _PyRuntimeState_SetFinalizing is + * called, or else threads might get prematurely blocked. + * 2. The world must not be stopped, as finalizers can run. + */ finalize_subinterpreters(); } + /* Wait on finalization guards. + * + * To avoid eating CPU cycles, we use an event to signal when we reach + * zero remaining guards. But, this isn't atomic! This event can be reset + * later if another thread creates a new finalization guard. The actual + * atomic check is made below, when we hold the finalization guard lock. + * Again, this is purely an optimization to avoid overloading the CPU. + */ + if (_Py_atomic_load_ssize_relaxed(&interp->finalization_guards.countdown) > 0) { + PyEvent_Wait(&interp->finalization_guards.done); + } /* Stop the world to prevent other threads from creating threads or * atexit callbacks. On the default build, this is simply locked by diff --git a/Python/pystate.c b/Python/pystate.c index bb5aafbd2edb14..b78dc56b129990 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3356,7 +3356,17 @@ try_acquire_interp_guard(PyInterpreterState *interp, PyInterpreterGuard *guard) return -1; } - _Py_atomic_add_ssize(&interp->finalization_guards.countdown, 1); + Py_ssize_t old_value = _Py_atomic_add_ssize(&interp->finalization_guards.countdown, 1); + if (old_value == 0) { + // Reset the event. + // We first have to notify the finalization thread if it's waiting on us, but + // it will get trapped waiting on the RW lock. When it goes to check + // again after we release the lock, it will see that the countdown is + // non-zero and begin waiting again (hence why we need to reset the + // event). + _PyEvent_Notify(&interp->finalization_guards.done); + memset(&interp->finalization_guards.done, 0, sizeof(PyEvent)); + } _PyRWMutex_RUnlock(&interp->finalization_guards.lock); guard->interp = interp; @@ -3393,6 +3403,9 @@ PyInterpreterGuard_Close(PyInterpreterGuard *guard) _PyRWMutex_RLock(&interp->finalization_guards.lock); Py_ssize_t old = _Py_atomic_add_ssize(&interp->finalization_guards.countdown, -1); + if (old == 1) { + _PyEvent_Notify(&interp->finalization_guards.done); + } _PyRWMutex_RUnlock(&interp->finalization_guards.lock); assert(old > 0); From 2e68b1538d1070b141ead8018f758b79888e1847 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 20 Apr 2026 21:16:38 -0400 Subject: [PATCH 84/85] Emit a fatal error for CTRL^Cs while waiting on finalization guards. --- Python/pylifecycle.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 4860423fafdd93..61f8a5a45b23bc 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2326,7 +2326,19 @@ make_pre_finalization_calls(PyThreadState *tstate, int subinterpreters) * Again, this is purely an optimization to avoid overloading the CPU. */ if (_Py_atomic_load_ssize_relaxed(&interp->finalization_guards.countdown) > 0) { - PyEvent_Wait(&interp->finalization_guards.done); + for (;;) { + PyTime_t wait_ns = 1000 * 1000; // 1ms + if (PyEvent_WaitTimed(&interp->finalization_guards.done, wait_ns, /*detach=*/1)) { + break; + } + + // For debugging purposes, we emit a fatal error if someone + // CTRL^C'ed the process. + if (PyErr_CheckSignals()) { + PyErr_FormatUnraisable("Exception ignored while waiting on finalization guards"); + Py_FatalError("Interrupted while waiting on finalization guard"); + } + } } /* Stop the world to prevent other threads from creating threads or From 829f96b3d9a26ecc1c39a41493e2c60af8a18b46 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Tue, 21 Apr 2026 13:40:10 -0400 Subject: [PATCH 85/85] Close owned guards before deallocating the thread state, not after. --- Modules/_testcapimodule.c | 18 ++++-------------- Python/pystate.c | 8 ++++++-- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index d9660329485b5f..feb651e269b68b 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2709,10 +2709,7 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) PyInterpreterGuard *guard = PyInterpreterGuard_FromCurrent(); PyThreadState *save_tstate = PyThreadState_Swap(NULL); PyThreadState *interp_tstate = Py_NewInterpreter(); - if (interp_tstate == NULL) { - PyInterpreterGuard_Close(guard); - return PyErr_NoMemory(); - } + assert(interp_tstate != NULL); /* This should create a new thread state for the calling interpreter, *not* reactivate the old one. In a real-world scenario, this would arise in @@ -2728,10 +2725,7 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) interp.exec(some_func) */ PyThreadState *thread_state = PyThreadState_Ensure(guard); - if (thread_state == NULL) { - PyInterpreterGuard_Close(guard); - return PyErr_NoMemory(); - } + assert(thread_state != NULL); PyThreadState *ensured_tstate = PyThreadState_Get(); assert(ensured_tstate != save_tstate); @@ -2739,13 +2733,9 @@ test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) // Now though, we should reactivate the thread state PyThreadState *other_thread_state = PyThreadState_Ensure(guard); - if (other_thread_state == NULL) { - PyThreadState_Release(thread_state); - PyInterpreterGuard_Close(guard); - return PyErr_NoMemory(); - } - + assert(other_thread_state != NULL); assert(PyThreadState_Get() == ensured_tstate); + PyThreadState_Release(other_thread_state); // Ensure that we're restoring the prior thread state diff --git a/Python/pystate.c b/Python/pystate.c index b78dc56b129990..e8d7e23db7af0e 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -3595,14 +3595,18 @@ PyThreadState_Release(PyThreadState *old_tstate) assert(tstate->ensure.delete_on_release == 1 || tstate->ensure.delete_on_release == 0); if (tstate->ensure.delete_on_release) { PyThreadState_Clear(tstate); - PyThreadState_Swap(to_restore); - PyThreadState_Delete(tstate); } else { PyThreadState_Swap(to_restore); } + PyThreadState_Swap(to_restore); + if (tstate->ensure.owned_guard != NULL) { PyInterpreterGuard_Close(tstate->ensure.owned_guard); tstate->ensure.owned_guard = NULL; } + + if (tstate->ensure.delete_on_release) { + PyThreadState_Delete(tstate); + } }