diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h index 8bc681b1a93f5c..a13096e53b30c4 100644 --- a/Include/cpython/initconfig.h +++ b/Include/cpython/initconfig.h @@ -250,6 +250,7 @@ typedef struct { int allow_threads; int allow_daemon_threads; int check_multi_interp_extensions; + int own_gil; } _PyInterpreterConfig; #define _PyInterpreterConfig_INIT \ @@ -259,6 +260,7 @@ typedef struct { .allow_threads = 1, \ .allow_daemon_threads = 0, \ .check_multi_interp_extensions = 1, \ + .own_gil = 1, \ } #define _PyInterpreterConfig_LEGACY_INIT \ @@ -268,6 +270,7 @@ typedef struct { .allow_threads = 1, \ .allow_daemon_threads = 1, \ .check_multi_interp_extensions = 0, \ + .own_gil = 0, \ } /* --- Helper functions --------------------------------------- */ diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index deda070a6dea79..3a80563f3df230 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -21,8 +21,7 @@ struct _ceval_runtime_state; extern void _Py_FinishPendingCalls(PyThreadState *tstate); -extern void _PyEval_InitRuntimeState(struct _ceval_runtime_state *); -extern void _PyEval_InitState(struct _ceval_state *, PyThread_type_lock); +extern void _PyEval_InitState(PyInterpreterState *, PyThread_type_lock); extern void _PyEval_FiniState(struct _ceval_state *ceval); PyAPI_FUNC(void) _PyEval_SignalReceived(PyInterpreterState *interp); PyAPI_FUNC(int) _PyEval_AddPendingCall( @@ -96,8 +95,8 @@ _PyEval_Vector(PyThreadState *tstate, PyObject* const* args, size_t argcount, PyObject *kwnames); -extern int _PyEval_ThreadsInitialized(struct pyruntimestate *runtime); -extern PyStatus _PyEval_InitGIL(PyThreadState *tstate); +extern int _PyEval_ThreadsInitialized(void); +extern PyStatus _PyEval_InitGIL(PyThreadState *tstate, int own_gil); extern void _PyEval_FiniGIL(PyInterpreterState *interp); extern void _PyEval_ReleaseLock(PyThreadState *tstate); diff --git a/Include/internal/pycore_ceval_state.h b/Include/internal/pycore_ceval_state.h index 9ba42eb03b2676..b352801673c40a 100644 --- a/Include/internal/pycore_ceval_state.h +++ b/Include/internal/pycore_ceval_state.h @@ -49,7 +49,6 @@ struct _ceval_runtime_state { the main thread of the main interpreter can handle signals: see _Py_ThreadCanHandleSignals(). */ _Py_atomic_int signals_pending; - struct _gil_runtime_state gil; }; #ifdef PY_HAVE_PERF_TRAMPOLINE @@ -83,6 +82,8 @@ struct _pending_calls { struct _ceval_state { int recursion_limit; + struct _gil_runtime_state *gil; + int own_gil; /* This single variable consolidates all requests to break out of the fast path in the eval loop. */ _Py_atomic_int eval_breaker; diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index d64a68cd2da54e..1f4f51f36ae565 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -163,6 +163,9 @@ struct _is { basis. Also see _PyRuntimeState regarding the various mutex fields. */ + /* The per-interpreter GIL, which might not be used. */ + struct _gil_runtime_state _gil; + /* the initial PyInterpreterState.threads.head */ PyThreadState _initial_thread; }; diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index 3ebe49926edda6..28802962797ed5 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -32,8 +32,6 @@ struct _getargs_runtime_state { struct _PyArg_Parser *static_parsers; }; -/* ceval state */ - /* GIL state */ struct _gilstate_runtime_state { diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index c34ee578b5c83f..c132f2889978c5 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1221,18 +1221,24 @@ def test_configured_settings(self): FORK = 1<<15 EXEC = 1<<16 - features = ['fork', 'exec', 'threads', 'daemon_threads', 'extensions'] + features = ['fork', 'exec', 'threads', 'daemon_threads', + 'extensions', 'own_gil'] kwlist = [f'allow_{n}' for n in features] - kwlist[-1] = 'check_multi_interp_extensions' + kwlist[-2] = 'check_multi_interp_extensions' + kwlist[-1] = 'own_gil' for config, expected in { - (True, True, True, True, True): - FORK | EXEC | THREADS | DAEMON_THREADS | EXTENSIONS, - (False, False, False, False, False): 0, - (False, False, True, False, True): THREADS | EXTENSIONS, + (True, True, True, True, True, True): + (FORK | EXEC | THREADS | DAEMON_THREADS | EXTENSIONS, True), + (False, False, False, False, False, False): + (0, False), + (False, False, True, False, True, False): + (THREADS | EXTENSIONS, False), }.items(): kwargs = dict(zip(kwlist, config)) + exp_flags, exp_gil = expected expected = { - 'feature_flags': expected, + 'feature_flags': exp_flags, + 'own_gil': exp_gil, } with self.subTest(config): r, w = os.pipe() @@ -1272,6 +1278,7 @@ def test_overridden_setting_extensions_subinterp_check(self): 'allow_exec': True, 'allow_threads': True, 'allow_daemon_threads': True, + 'own_gil': False, } def check(enabled, override): @@ -1282,6 +1289,7 @@ def check(enabled, override): flags = BASE_FLAGS | EXTENSIONS if enabled else BASE_FLAGS settings = { 'feature_flags': flags, + 'own_gil': False, } expected = { diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index e56d0db8627e91..4c6730c8b2710f 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -1663,8 +1663,8 @@ def test_init_main_interpreter_settings(self): EXEC = 1<<16 expected = { # All optional features should be enabled. - 'feature_flags': - FORK | EXEC | THREADS | DAEMON_THREADS, + 'feature_flags': FORK | EXEC | THREADS | DAEMON_THREADS, + 'own_gil': True, } out, err = self.run_embedded_interpreter( 'test_init_main_interpreter_settings', diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 3ef07203c46c7e..d8f59f4c5c5206 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -1453,6 +1453,7 @@ class SubinterpImportTests(unittest.TestCase): allow_exec=False, allow_threads=True, allow_daemon_threads=False, + own_gil=False, ) @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index a39a267b403d83..ac4c640e9795d0 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1348,6 +1348,7 @@ def func(): allow_threads={allowed}, allow_daemon_threads={daemon_allowed}, check_multi_interp_extensions=False, + own_gil=False, ) """) with test.support.SuppressCrashReport(): diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 557a6d46ed4632..fd4dc7b10fbacf 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -1487,6 +1487,7 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) int allow_threads = -1; int allow_daemon_threads = -1; int check_multi_interp_extensions = -1; + int own_gil = -1; int r; PyThreadState *substate, *mainstate; /* only initialise 'cflags.cf_flags' to test backwards compatibility */ @@ -1498,12 +1499,14 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) "allow_threads", "allow_daemon_threads", "check_multi_interp_extensions", + "own_gil", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "s$ppppp:run_in_subinterp_with_config", kwlist, + "s$pppppp:run_in_subinterp_with_config", kwlist, &code, &allow_fork, &allow_exec, &allow_threads, &allow_daemon_threads, - &check_multi_interp_extensions)) { + &check_multi_interp_extensions, + &own_gil)) { return NULL; } if (allow_fork < 0) { @@ -1518,6 +1521,10 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) PyErr_SetString(PyExc_ValueError, "missing allow_threads"); return NULL; } + if (own_gil < 0) { + PyErr_SetString(PyExc_ValueError, "missing own_gil"); + return NULL; + } if (allow_daemon_threads < 0) { PyErr_SetString(PyExc_ValueError, "missing allow_daemon_threads"); return NULL; @@ -1537,6 +1544,7 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) .allow_threads = allow_threads, .allow_daemon_threads = allow_daemon_threads, .check_multi_interp_extensions = check_multi_interp_extensions, + .own_gil = own_gil, }; PyStatus status = _Py_NewInterpreterFromConfig(&substate, &config); if (PyStatus_Exception(status)) { diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 632fac2de0c419..f687bd89af4f7d 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -667,6 +667,13 @@ get_interp_settings(PyObject *self, PyObject *args) return NULL; } + /* "own GIL" */ + PyObject *own_gil = interp->ceval.own_gil ? Py_True : Py_False; + if (PyDict_SetItemString(settings, "own_gil", own_gil) != 0) { + Py_DECREF(settings); + return NULL; + } + return settings; } diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index 29796be4b80e93..8f9ceb73972549 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -229,6 +229,9 @@ static void _gil_initialize(struct _gil_runtime_state *gil) static int gil_created(struct _gil_runtime_state *gil) { + if (gil == NULL) { + return 0; + } return (_Py_atomic_load_explicit(&gil->locked, _Py_memory_order_acquire) >= 0); } @@ -273,10 +276,9 @@ static void recreate_gil(struct _gil_runtime_state *gil) #endif static void -drop_gil(struct _ceval_runtime_state *ceval, struct _ceval_state *ceval2, - PyThreadState *tstate) +drop_gil(struct _ceval_state *ceval, PyThreadState *tstate) { - struct _gil_runtime_state *gil = &ceval->gil; + struct _gil_runtime_state *gil = ceval->gil; if (!_Py_atomic_load_relaxed(&gil->locked)) { Py_FatalError("drop_gil: GIL is not locked"); } @@ -296,7 +298,7 @@ drop_gil(struct _ceval_runtime_state *ceval, struct _ceval_state *ceval2, MUTEX_UNLOCK(gil->mutex); #ifdef FORCE_SWITCHING - if (_Py_atomic_load_relaxed(&ceval2->gil_drop_request) && tstate != NULL) { + if (_Py_atomic_load_relaxed(&ceval->gil_drop_request) && tstate != NULL) { MUTEX_LOCK(gil->switch_mutex); /* Not switched yet => wait */ if (((PyThreadState*)_Py_atomic_load_relaxed(&gil->last_holder)) == tstate) @@ -358,9 +360,8 @@ take_gil(PyThreadState *tstate) assert(is_tstate_valid(tstate)); PyInterpreterState *interp = tstate->interp; - struct _ceval_runtime_state *ceval = &interp->runtime->ceval; - struct _ceval_state *ceval2 = &interp->ceval; - struct _gil_runtime_state *gil = &ceval->gil; + struct _ceval_state *ceval = &interp->ceval; + struct _gil_runtime_state *gil = ceval->gil; /* Check that _PyEval_InitThreads() was called to create the lock */ assert(gil_created(gil)); @@ -434,12 +435,12 @@ take_gil(PyThreadState *tstate) in take_gil() while the main thread called wait_for_thread_shutdown() from Py_Finalize(). */ MUTEX_UNLOCK(gil->mutex); - drop_gil(ceval, ceval2, tstate); + drop_gil(ceval, tstate); PyThread_exit_thread(); } assert(is_tstate_valid(tstate)); - if (_Py_atomic_load_relaxed(&ceval2->gil_drop_request)) { + if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) { RESET_GIL_DROP_REQUEST(interp); } else { @@ -448,7 +449,7 @@ take_gil(PyThreadState *tstate) handle signals. Note: RESET_GIL_DROP_REQUEST() calls COMPUTE_EVAL_BREAKER(). */ - COMPUTE_EVAL_BREAKER(interp, ceval, ceval2); + COMPUTE_EVAL_BREAKER(interp, &_PyRuntime.ceval, ceval); } /* Don't access tstate if the thread must exit */ @@ -463,63 +464,85 @@ take_gil(PyThreadState *tstate) void _PyEval_SetSwitchInterval(unsigned long microseconds) { - struct _gil_runtime_state *gil = &_PyRuntime.ceval.gil; + PyInterpreterState *interp = _PyInterpreterState_Get(); + struct _gil_runtime_state *gil = interp->ceval.gil; + assert(gil != NULL); gil->interval = microseconds; } unsigned long _PyEval_GetSwitchInterval(void) { - struct _gil_runtime_state *gil = &_PyRuntime.ceval.gil; + PyInterpreterState *interp = _PyInterpreterState_Get(); + struct _gil_runtime_state *gil = interp->ceval.gil; + assert(gil != NULL); return gil->interval; } int -_PyEval_ThreadsInitialized(_PyRuntimeState *runtime) +_PyEval_ThreadsInitialized(void) { - return gil_created(&runtime->ceval.gil); + /* XXX This is only needed for an assert in PyGILState_Ensure(), + * which currently does not work with subinterpreters. + * Thus we only use the main interpreter. */ + PyInterpreterState *interp = _PyInterpreterState_Main(); + if (interp == NULL) { + return 0; + } + struct _gil_runtime_state *gil = interp->ceval.gil; + return gil_created(gil); } int PyEval_ThreadsInitialized(void) { - _PyRuntimeState *runtime = &_PyRuntime; - return _PyEval_ThreadsInitialized(runtime); + return _PyEval_ThreadsInitialized(); } PyStatus -_PyEval_InitGIL(PyThreadState *tstate) -{ - if (!_Py_IsMainInterpreter(tstate->interp)) { - /* Currently, the GIL is shared by all interpreters, - and only the main interpreter is responsible to create - and destroy it. */ +_PyEval_InitGIL(PyThreadState *tstate, int own_gil) +{ + assert(tstate->interp->ceval.gil == NULL); + if (!own_gil) { + /* The interpreter will share the main interpreter's instead. */ + PyInterpreterState *main_interp = _PyInterpreterState_Main(); + assert(tstate->interp != main_interp); + struct _gil_runtime_state *gil = main_interp->ceval.gil; + assert(gil_created(gil)); + tstate->interp->ceval.gil = gil; + tstate->interp->ceval.own_gil = 0; return _PyStatus_OK(); } - struct _gil_runtime_state *gil = &tstate->interp->runtime->ceval.gil; + struct _gil_runtime_state *gil = &tstate->interp->_gil; assert(!gil_created(gil)); PyThread_init_thread(); create_gil(gil); - - take_gil(tstate); - assert(gil_created(gil)); + tstate->interp->ceval.gil = gil; + tstate->interp->ceval.own_gil = 1; + take_gil(tstate); return _PyStatus_OK(); } void _PyEval_FiniGIL(PyInterpreterState *interp) { - if (!_Py_IsMainInterpreter(interp)) { - /* Currently, the GIL is shared by all interpreters, - and only the main interpreter is responsible to create - and destroy it. */ + struct _gil_runtime_state *gil = interp->ceval.gil; + if (gil == NULL) { + /* It was already finalized (or hasn't been initialized yet). */ + assert(!interp->ceval.own_gil); + return; + } + else if (!interp->ceval.own_gil) { + PyInterpreterState *main_interp = _PyInterpreterState_Main(); + assert(main_interp != NULL && interp != main_interp); + assert(interp->ceval.gil == main_interp->ceval.gil); + interp->ceval.gil = NULL; return; } - struct _gil_runtime_state *gil = &interp->runtime->ceval.gil; if (!gil_created(gil)) { /* First Py_InitializeFromConfig() call: the GIL doesn't exist yet: do nothing. */ @@ -528,6 +551,7 @@ _PyEval_FiniGIL(PyInterpreterState *interp) destroy_gil(gil); assert(!gil_created(gil)); + interp->ceval.gil = NULL; } void @@ -561,17 +585,15 @@ PyEval_ReleaseLock(void) /* This function must succeed when the current thread state is NULL. We therefore avoid PyThreadState_Get() which dumps a fatal error in debug mode. */ - struct _ceval_runtime_state *ceval = &runtime->ceval; - struct _ceval_state *ceval2 = &tstate->interp->ceval; - drop_gil(ceval, ceval2, tstate); + struct _ceval_state *ceval = &tstate->interp->ceval; + drop_gil(ceval, tstate); } void _PyEval_ReleaseLock(PyThreadState *tstate) { - struct _ceval_runtime_state *ceval = &tstate->interp->runtime->ceval; - struct _ceval_state *ceval2 = &tstate->interp->ceval; - drop_gil(ceval, ceval2, tstate); + struct _ceval_state *ceval = &tstate->interp->ceval; + drop_gil(ceval, tstate); } void @@ -596,9 +618,8 @@ PyEval_ReleaseThread(PyThreadState *tstate) if (new_tstate != tstate) { Py_FatalError("wrong thread state"); } - struct _ceval_runtime_state *ceval = &runtime->ceval; - struct _ceval_state *ceval2 = &tstate->interp->ceval; - drop_gil(ceval, ceval2, tstate); + struct _ceval_state *ceval = &tstate->interp->ceval; + drop_gil(ceval, tstate); } #ifdef HAVE_FORK @@ -608,9 +629,9 @@ PyEval_ReleaseThread(PyThreadState *tstate) PyStatus _PyEval_ReInitThreads(PyThreadState *tstate) { - _PyRuntimeState *runtime = tstate->interp->runtime; + assert(tstate->interp == _PyInterpreterState_Main()); - struct _gil_runtime_state *gil = &runtime->ceval.gil; + struct _gil_runtime_state *gil = tstate->interp->ceval.gil; if (!gil_created(gil)) { return _PyStatus_OK(); } @@ -645,10 +666,9 @@ PyEval_SaveThread(void) PyThreadState *tstate = _PyThreadState_Swap(runtime, NULL); _Py_EnsureTstateNotNULL(tstate); - struct _ceval_runtime_state *ceval = &runtime->ceval; - struct _ceval_state *ceval2 = &tstate->interp->ceval; - assert(gil_created(&ceval->gil)); - drop_gil(ceval, ceval2, tstate); + struct _ceval_state *ceval = &tstate->interp->ceval; + assert(gil_created(ceval->gil)); + drop_gil(ceval, tstate); return tstate; } @@ -902,20 +922,13 @@ Py_MakePendingCalls(void) return 0; } -/* The interpreter's recursion limit */ - void -_PyEval_InitRuntimeState(struct _ceval_runtime_state *ceval) +_PyEval_InitState(PyInterpreterState *interp, PyThread_type_lock pending_lock) { - _gil_initialize(&ceval->gil); -} + _gil_initialize(&interp->_gil); -void -_PyEval_InitState(struct _ceval_state *ceval, PyThread_type_lock pending_lock) -{ - struct _pending_calls *pending = &ceval->pending; + struct _pending_calls *pending = &interp->ceval.pending; assert(pending->lock == NULL); - pending->lock = pending_lock; } @@ -965,7 +978,7 @@ _Py_HandlePending(PyThreadState *tstate) if (_PyThreadState_Swap(runtime, NULL) != tstate) { Py_FatalError("tstate mix-up"); } - drop_gil(ceval, interp_ceval_state, tstate); + drop_gil(interp_ceval_state, tstate); /* Other threads may run now */ diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index d6627bc6b7e86b..9b6f2316f9026f 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -574,7 +574,7 @@ init_interp_settings(PyInterpreterState *interp, const _PyInterpreterConfig *con static PyStatus -init_interp_create_gil(PyThreadState *tstate) +init_interp_create_gil(PyThreadState *tstate, int own_gil) { PyStatus status; @@ -589,7 +589,7 @@ init_interp_create_gil(PyThreadState *tstate) } /* Create the GIL and take it */ - status = _PyEval_InitGIL(tstate); + status = _PyEval_InitGIL(tstate, own_gil); if (_PyStatus_EXCEPTION(status)) { return status; } @@ -621,7 +621,9 @@ pycore_create_interpreter(_PyRuntimeState *runtime, return status; } - const _PyInterpreterConfig config = _PyInterpreterConfig_LEGACY_INIT; + _PyInterpreterConfig config = _PyInterpreterConfig_LEGACY_INIT; + // The main interpreter always has its own GIL. + config.own_gil = 1; init_interp_settings(interp, &config); PyThreadState *tstate = _PyThreadState_New(interp); @@ -631,7 +633,7 @@ pycore_create_interpreter(_PyRuntimeState *runtime, _PyThreadState_Bind(tstate); (void) PyThreadState_Swap(tstate); - status = init_interp_create_gil(tstate); + status = init_interp_create_gil(tstate, config.own_gil); if (_PyStatus_EXCEPTION(status)) { return status; } @@ -2043,7 +2045,7 @@ new_interpreter(PyThreadState **tstate_p, const _PyInterpreterConfig *config) init_interp_settings(interp, config); - status = init_interp_create_gil(tstate); + status = init_interp_create_gil(tstate, config->own_gil); if (_PyStatus_EXCEPTION(status)) { goto error; } diff --git a/Python/pystate.c b/Python/pystate.c index d09c1d5743a4c6..b2fb4f2f05ed1f 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -399,8 +399,6 @@ init_runtime(_PyRuntimeState *runtime, runtime->open_code_userdata = open_code_userdata; runtime->audit_hook_head = audit_hook_head; - _PyEval_InitRuntimeState(&runtime->ceval); - PyPreConfig_InitPythonConfig(&runtime->preconfig); PyThread_type_lock *lockptrs[NUMLOCKS] = { @@ -646,7 +644,7 @@ init_interpreter(PyInterpreterState *interp, assert(next != NULL || (interp == runtime->interpreters.main)); interp->next = next; - _PyEval_InitState(&interp->ceval, pending_lock); + _PyEval_InitState(interp, pending_lock); _PyGC_InitState(&interp->gc); PyConfig_InitPythonConfig(&interp->config); _PyType_InitCache(interp); @@ -2119,7 +2117,7 @@ PyGILState_Ensure(void) /* Ensure that _PyEval_InitThreads() and _PyGILState_Init() have been called by Py_Initialize() */ - assert(_PyEval_ThreadsInitialized(runtime)); + assert(_PyEval_ThreadsInitialized()); assert(gilstate_tss_initialized(runtime)); assert(runtime->gilstate.autoInterpreterState != NULL);