diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index ac8798ff6129a0..5b5da1826ae889 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -217,6 +217,17 @@ struct _ts { */ PyObject *threading_local_sentinel; _PyRemoteDebuggerSupport remote_debugger_support; + + struct { + /* Number of nested PyThreadState_Ensure() calls on this thread state */ + Py_ssize_t counter; + + /* Should this thread state be deleted upon calling + PyThreadState_Release() (with the counter at 1)? + + This is only true for thread states created by PyThreadState_Ensure() */ + int delete_on_release; + } ensure; }; /* other API */ @@ -270,3 +281,47 @@ PyAPI_FUNC(_PyFrameEvalFunction) _PyInterpreterState_GetEvalFrameFunc( PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( PyInterpreterState *interp, _PyFrameEvalFunction eval_frame); + +/* Interpreter locks */ + +typedef uintptr_t PyInterpreterLock; +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); + +#define PyInterpreterLock_Release(lock) do { \ + PyInterpreterLock_Release(lock); \ + lock = 0; \ +} while (0) + +/* 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); + + +#define PyInterpreterView_Close(view) do { \ + PyInterpreterView_Close(view); \ + view = 0; \ +} while (0) + + +/* Thread views */ + +typedef uintptr_t PyThreadView; + +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 2124e76514f1af..4a49ee4bc6a18c 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -971,6 +971,11 @@ struct _is { # endif #endif + struct { + _PyRWMutex lock; + Py_ssize_t countdown; + } finalization_locks; + /* the initial PyInterpreterState.threads.head */ _PyThreadStateImpl _initial_thread; // _initial_thread should be the last field of PyInterpreterState. diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index ea3dfbd2eef9c1..5c4f0146bcfeb5 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -329,6 +329,9 @@ _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_LockCountdown(PyInterpreterState *interp); + #ifdef __cplusplus } #endif diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 1933f691a78be5..964b04114a828a 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -1893,10 +1893,15 @@ 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") + + def test_main_interpreter_view(self): + self.run_embedded_interpreter("test_main_interpreter_view") + class MiscTests(EmbeddingTestsMixin, unittest.TestCase): def test_unicode_id_init(self): diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 4e73be20e1b709..ca87ff97d9f18d 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2562,6 +2562,193 @@ toggle_reftrace_printer(PyObject *ob, PyObject *arg) Py_RETURN_NONE; } +static void +test_interp_locks_common(void) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + PyInterpreterLock lock = PyInterpreterLock_FromCurrent(); + assert(lock != 0); + assert(PyInterpreterLock_GetInterpreter(lock) == 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(lock_2); + PyInterpreterLock_Release(lock); +} + +static PyObject * +test_interpreter_locks(PyObject *self, PyObject *unused) +{ + // Test the main interpreter + test_interp_locks_common(); + + // Test a (legacy) subinterpreter + PyThreadState *save_tstate = PyThreadState_Swap(NULL); + PyThreadState *interp_tstate = Py_NewInterpreter(); + test_interp_locks_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_locks_common(); + Py_EndInterpreter(isolated_interp_tstate); + PyThreadState_Swap(save_tstate); + Py_RETURN_NONE; +} + +static PyObject * +test_thread_state_ensure_nested(PyObject *self, PyObject *unused) +{ + PyInterpreterLock lock = PyInterpreterLock_FromCurrent(); + if (lock == 0) { + return NULL; + } + PyThreadState *save_tstate = PyThreadState_Swap(NULL); + assert(PyGILState_GetThisThreadState() == save_tstate); + PyThreadView thread_views[10]; + + for (int i = 0; i < 10; ++i) { + // Test reactivation of the detached tstate. + 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(thread_views[i]); + } + + 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) { + 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(lock); + return PyErr_NoMemory(); + } + + assert(PyThreadState_Get() == save_tstate); + } + + for (int i = 0; i < 10; ++i) { + assert(PyThreadState_Get() == save_tstate); + PyThreadState_Release(thread_views[i]); + } + + assert(PyThreadState_GetUnchecked() == NULL); + PyInterpreterLock_Release(lock); + PyThreadState_Swap(save_tstate); + Py_RETURN_NONE; +} + +static PyObject * +test_thread_state_ensure_crossinterp(PyObject *self, PyObject *unused) +{ + PyInterpreterLock lock = PyInterpreterLock_FromCurrent(); + PyThreadState *save_tstate = PyThreadState_Swap(NULL); + PyThreadState *interp_tstate = Py_NewInterpreter(); + if (interp_tstate == NULL) { + PyInterpreterLock_Release(lock); + 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 + 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) + */ + 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(lock)); + assert(PyGILState_GetThisThreadState() == ensured_tstate); + + // Now though, we should reactivate the thread state + 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_view); + + // Ensure that we're restoring the prior thread state + PyThreadState_Release(thread_view); + assert(PyThreadState_Get() == interp_tstate); + assert(PyGILState_GetThisThreadState() == interp_tstate); + + PyThreadState_Swap(interp_tstate); + Py_EndInterpreter(interp_tstate); + + PyInterpreterLock_Release(lock); + PyThreadState_Swap(save_tstate); + Py_RETURN_NONE; +} + +static PyObject * +test_interp_view_after_shutdown(PyObject *self, PyObject *unused) +{ + PyThreadState *save_tstate = PyThreadState_Swap(NULL); + PyThreadState *interp_tstate = Py_NewInterpreter(); + if (interp_tstate == NULL) { + return PyErr_NoMemory(); + } + + PyInterpreterView view = PyInterpreterView_FromCurrent(); + if (view == 0) { + return PyErr_NoMemory(); + } + + // 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 lock from a view. + // It should fail. + Py_EndInterpreter(interp_tstate); + lock = PyInterpreterLock_FromView(view); + assert(lock == 0); + + PyThreadState_Swap(save_tstate); + Py_RETURN_NONE; +} + static PyMethodDef TestMethods[] = { {"set_errno", set_errno, METH_VARARGS}, {"test_config", test_config, METH_NOARGS}, @@ -2656,6 +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_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_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 c2647d405e25bc..f5f0ceef595d48 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2418,6 +2418,58 @@ set_vectorcall_nop(PyObject *self, PyObject *func) Py_RETURN_NONE; } +#define NUM_LOCKS 100 + +static PyObject * +test_interp_lock_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); + 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)); + } + + Py_RETURN_NONE; +} + +static PyObject * +test_interp_view_countdown(PyObject *self, PyObject *unused) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + PyInterpreterView view = PyInterpreterView_FromCurrent(); + if (view == 0) { + return NULL; + } + assert(_PyInterpreterState_LockCountdown(interp) == 0); + + PyInterpreterLock locks[NUM_LOCKS]; + + 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_LOCKS; ++i) { + PyInterpreterLock_Release(locks[i]); + assert(_PyInterpreterState_LockCountdown(interp) == (NUM_LOCKS - i - 1)); + } + + PyInterpreterView_Close(view); + Py_RETURN_NONE; +} + +#undef NUM_LOCKS + static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, @@ -2527,6 +2579,8 @@ 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_view_countdown", test_interp_view_countdown, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Programs/_testembed.c b/Programs/_testembed.c index d3600fecbe2775..e4382b5411139e 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -2307,6 +2307,95 @@ test_gilstate_after_finalization(void) return PyThread_detach_thread(handle); } + +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)"; + +typedef struct { + PyInterpreterLock lock; + int done; +} ThreadData; + +static void +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); + PyGILState_STATE gstate = PyGILState_Ensure(); + 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]); + PyThreadState_Release(refs[0]); + PyInterpreterLock_Release(data->lock); + data->done = 1; +} + +static int +test_thread_state_ensure(void) +{ + _testembed_initialize(); + PyThread_handle_t handle; + PyThread_ident_t ident; + PyInterpreterLock ref = PyInterpreterLock_FromCurrent(); + if (ref == 0) { + return -1; + }; + ThreadData data = { ref }; + if (PyThread_start_joinable_thread(do_tstate_ensure, &data, + &ident, &handle) < 0) { + PyInterpreterLock_Release(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; +} + +static int +test_main_interpreter_view(void) +{ + _testembed_initialize(); + + // Main interpreter is initialized and ready. + PyInterpreterView view = PyUnstable_InterpreterView_FromDefault(); + assert(view != 0); + + PyInterpreterLock lock = PyInterpreterLock_FromView(view); + assert(lock != 0); + PyInterpreterLock_Release(lock); + + Py_Finalize(); + + // We shouldn't be able to get locks for the interpreter now + lock = PyInterpreterLock_FromView(view); + assert(lock == 0); + + PyInterpreterView_Close(view); + + return 0; +} + /* ********************************************************* * List of test cases and the function that implements it. * @@ -2396,6 +2485,8 @@ static struct TestCase TestCases[] = { #endif {"test_get_incomplete_frame", test_get_incomplete_frame}, {"test_gilstate_after_finalization", test_gilstate_after_finalization}, + {"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 b813166f167d70..b5f765424057fe 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2130,18 +2130,25 @@ 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); 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) - || has_subinterpreters); + || has_subinterpreters + || interp->finalization_locks.countdown > 0); if (!should_continue) { break; } + // Temporarily let other threads execute + _PyThreadState_Detach(tstate); + _PyRWMutex_Unlock(&interp->finalization_locks.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); @@ -2202,6 +2209,7 @@ _Py_Finalize(_PyRuntimeState *runtime) for (PyThreadState *p = list; p != NULL; p = p->next) { _PyThreadState_SetShuttingDown(p); } + _PyRWMutex_Unlock(&tstate->interp->finalization_locks.lock); _PyEval_StartTheWorldAll(runtime); PyMutex_Unlock(&tstate->interp->ceval.pending.mutex); @@ -2571,6 +2579,7 @@ Py_EndInterpreter(PyThreadState *tstate) _PyThreadState_SetShuttingDown(p); } + _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 dbed609f29aa07..bf089c72c2bad2 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1314,12 +1314,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) +static PyInterpreterState * +_PyInterpreterState_LookUpIDNoErr(int64_t requested_id) { PyInterpreterState *interp = NULL; if (requested_id >= 0) { @@ -1328,6 +1324,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); @@ -1540,7 +1548,6 @@ new_threadstate(PyInterpreterState *interp, int whence) return NULL; } #endif - /* We serialize concurrent creation to protect global state. */ HEAD_LOCK(interp->runtime); @@ -2753,33 +2760,38 @@ 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) { - _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; if (tcur == NULL) { /* Create a new Python thread state for this thread */ // XXX Use PyInterpreterState_EnsureThreadState()? - tcur = new_threadstate(runtime->gilstate.autoInterpreterState, + 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(lock), _PyThreadState_WHENCE_GILSTATE); if (tcur == NULL) { Py_FatalError("Couldn't create thread-state for new thread"); @@ -2792,12 +2804,14 @@ PyGILState_Ensure(void) assert(tcur->gilstate_counter == 1); tcur->gilstate_counter = 0; has_gil = 0; /* new thread state is never current */ + PyInterpreterLock_Release(lock); } else { has_gil = holds_gil(tcur); } if (!has_gil) { + // XXX Do we need to protect this against finalization? PyEval_RestoreThread(tcur); } @@ -3134,3 +3148,236 @@ _Py_GetMainConfig(void) } return _PyInterpreterState_GetConfig(interp); } + +Py_ssize_t +_PyInterpreterState_LockCountdown(PyInterpreterState *interp) +{ + assert(interp != NULL); + return _Py_atomic_load_ssize_relaxed(&interp->finalization_locks.countdown); +} + +static PyInterpreterLock +try_acquire_interp_lock(PyInterpreterState *interp) +{ + _PyRWMutex_RLock(&interp->finalization_locks.lock); + if (_PyInterpreterState_GetFinalizing(interp) != NULL) { + _PyRWMutex_RUnlock(&interp->finalization_locks.lock); + return (PyInterpreterLock)interp; + } + _Py_atomic_add_ssize(&interp->finalization_locks.countdown, 1); + _PyRWMutex_RUnlock(&interp->finalization_locks.lock); + return (PyInterpreterLock)interp; +} + +static PyInterpreterState * +lock_as_interp(PyInterpreterLock lock) +{ + PyInterpreterState *interp = (PyInterpreterState *)lock; + if (interp == NULL) { + Py_FatalError("Got a null interpreter lock, likely due to" + " use after PyInterpreterLock_Release()"); + } + + return interp; +} + +PyInterpreterLock +PyInterpreterLock_FromCurrent(void) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + PyInterpreterLock lock = try_acquire_interp_lock(interp); + if (lock == 0) { + PyErr_SetString(PyExc_PythonFinalizationError, + "cannot acquire finalization lock anymore"); + return 0; + } + return lock; +} + +PyInterpreterLock +PyInterpreterLock_Copy(PyInterpreterLock lock) +{ + 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 lock) +{ + 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 lock) +{ + PyInterpreterState *interp = lock_as_interp(lock); + return interp; +} + +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 *view = PyMem_RawMalloc(sizeof(_PyInterpreterView)); + if (view == NULL) { + PyErr_NoMemory(); + return -1; + } + 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 *view = view_as_ptr(view_handle); + if (--view->refcount == 0) { + PyMem_RawFree(view); + } +} + +PyInterpreterLock +PyInterpreterLock_FromView(PyInterpreterView view_handle) +{ + _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); + return 0; + } + + PyInterpreterLock lock = try_acquire_interp_lock(interp); + HEAD_UNLOCK(runtime); + return lock; +} + +PyInterpreterView +PyUnstable_InterpreterView_FromDefault(void) +{ + _PyRuntimeState *runtime = &_PyRuntime; + _PyInterpreterView *view = PyMem_RawMalloc(sizeof(_PyInterpreterView)); + + if (view == NULL) { + return 0; + } + + HEAD_LOCK(runtime); + view->id = runtime->_main_interpreter.id; + view->refcount = 1; + HEAD_UNLOCK(runtime); + + 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) +{ + PyInterpreterState *interp = lock_as_interp(lock); + 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 (PyThreadView)&NO_TSTATE_SENTINEL; + } + + 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 (PyThreadView)&NO_TSTATE_SENTINEL; + } + + PyThreadState *fresh_tstate = _PyThreadState_NewBound(interp, + _PyThreadState_WHENCE_GILSTATE); + if (fresh_tstate == NULL) { + return 0; + } + fresh_tstate->ensure.counter = 1; + fresh_tstate->ensure.delete_on_release = 1; + + if (attached_tstate != NULL) { + return (PyThreadView)PyThreadState_Swap(fresh_tstate); + } else { + _PyThreadState_Attach(fresh_tstate); + } + + return (PyThreadView)&NO_TSTATE_SENTINEL; +} + +void +PyThreadState_Release(PyThreadView thread_view) +{ + 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()"); + } + // The thread view might be NULL + 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); + PyThreadState_Swap(to_restore); + PyThreadState_Delete(tstate); + } else { + PyThreadState_Swap(to_restore); + } + } + + return; +} 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.