gh-99113: Add PyInterpreterConfig.own_gil (gh-104204)

We also add PyInterpreterState.ceval.own_gil to record if the interpreter actually has its own GIL.

Note that for now we don't actually respect own_gil; all interpreters still share the one GIL.  However, PyInterpreterState.ceval.own_gil does reflect PyInterpreterConfig.own_gil.  That lie is a temporary one that we will fix when the GIL really becomes per-interpreter.
This commit is contained in:
Eric Snow 2023-05-05 15:59:20 -06:00 committed by GitHub
parent 66558d2a16
commit f3e7eb48f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 79 additions and 18 deletions

View file

@ -252,6 +252,7 @@ typedef struct {
int allow_threads; int allow_threads;
int allow_daemon_threads; int allow_daemon_threads;
int check_multi_interp_extensions; int check_multi_interp_extensions;
int own_gil;
} PyInterpreterConfig; } PyInterpreterConfig;
#define _PyInterpreterConfig_INIT \ #define _PyInterpreterConfig_INIT \
@ -262,6 +263,7 @@ typedef struct {
.allow_threads = 1, \ .allow_threads = 1, \
.allow_daemon_threads = 0, \ .allow_daemon_threads = 0, \
.check_multi_interp_extensions = 1, \ .check_multi_interp_extensions = 1, \
.own_gil = 1, \
} }
#define _PyInterpreterConfig_LEGACY_INIT \ #define _PyInterpreterConfig_LEGACY_INIT \
@ -272,6 +274,7 @@ typedef struct {
.allow_threads = 1, \ .allow_threads = 1, \
.allow_daemon_threads = 1, \ .allow_daemon_threads = 1, \
.check_multi_interp_extensions = 0, \ .check_multi_interp_extensions = 0, \
.own_gil = 0, \
} }
/* --- Helper functions --------------------------------------- */ /* --- Helper functions --------------------------------------- */

View file

@ -97,7 +97,7 @@ _PyEval_Vector(PyThreadState *tstate,
PyObject *kwnames); PyObject *kwnames);
extern int _PyEval_ThreadsInitialized(void); extern int _PyEval_ThreadsInitialized(void);
extern PyStatus _PyEval_InitGIL(PyThreadState *tstate); extern PyStatus _PyEval_InitGIL(PyThreadState *tstate, int own_gil);
extern void _PyEval_FiniGIL(PyInterpreterState *interp); extern void _PyEval_FiniGIL(PyInterpreterState *interp);
extern void _PyEval_ReleaseLock(PyThreadState *tstate); extern void _PyEval_ReleaseLock(PyThreadState *tstate);

View file

@ -86,6 +86,7 @@ struct _pending_calls {
struct _ceval_state { struct _ceval_state {
int recursion_limit; int recursion_limit;
struct _gil_runtime_state *gil; struct _gil_runtime_state *gil;
int own_gil;
/* This single variable consolidates all requests to break out of /* This single variable consolidates all requests to break out of
the fast path in the eval loop. */ the fast path in the eval loop. */
_Py_atomic_int eval_breaker; _Py_atomic_int eval_breaker;

View file

@ -1401,23 +1401,37 @@ class SubinterpreterTest(unittest.TestCase):
DAEMON_THREADS = 1<<11 DAEMON_THREADS = 1<<11
FORK = 1<<15 FORK = 1<<15
EXEC = 1<<16 EXEC = 1<<16
ALL_FLAGS = (OBMALLOC | FORK | EXEC | THREADS | DAEMON_THREADS
| EXTENSIONS);
features = ['obmalloc', 'fork', 'exec', 'threads', 'daemon_threads', features = [
'extensions'] 'obmalloc',
'fork',
'exec',
'threads',
'daemon_threads',
'extensions',
'own_gil',
]
kwlist = [f'allow_{n}' for n in features] kwlist = [f'allow_{n}' for n in features]
kwlist[0] = 'use_main_obmalloc' kwlist[0] = 'use_main_obmalloc'
kwlist[-1] = 'check_multi_interp_extensions' kwlist[-2] = 'check_multi_interp_extensions'
kwlist[-1] = 'own_gil'
# expected to work # expected to work
for config, expected in { for config, expected in {
(True, True, True, True, True, True): (True, True, True, True, True, True, True):
OBMALLOC | FORK | EXEC | THREADS | DAEMON_THREADS | EXTENSIONS, (ALL_FLAGS, True),
(True, False, False, False, False, False): OBMALLOC, (True, False, False, False, False, False, False):
(False, False, False, True, False, True): THREADS | EXTENSIONS, (OBMALLOC, False),
(False, False, False, True, False, True, False):
(THREADS | EXTENSIONS, False),
}.items(): }.items():
kwargs = dict(zip(kwlist, config)) kwargs = dict(zip(kwlist, config))
exp_flags, exp_gil = expected
expected = { expected = {
'feature_flags': expected, 'feature_flags': exp_flags,
'own_gil': exp_gil,
} }
with self.subTest(config): with self.subTest(config):
r, w = os.pipe() r, w = os.pipe()
@ -1437,7 +1451,7 @@ class SubinterpreterTest(unittest.TestCase):
# expected to fail # expected to fail
for config in [ for config in [
(False, False, False, False, False, False), (False, False, False, False, False, False, False),
]: ]:
kwargs = dict(zip(kwlist, config)) kwargs = dict(zip(kwlist, config))
with self.subTest(config): with self.subTest(config):
@ -1473,6 +1487,7 @@ class SubinterpreterTest(unittest.TestCase):
'allow_exec': True, 'allow_exec': True,
'allow_threads': True, 'allow_threads': True,
'allow_daemon_threads': True, 'allow_daemon_threads': True,
'own_gil': False,
} }
def check(enabled, override): def check(enabled, override):
@ -1483,6 +1498,7 @@ class SubinterpreterTest(unittest.TestCase):
flags = BASE_FLAGS | EXTENSIONS if enabled else BASE_FLAGS flags = BASE_FLAGS | EXTENSIONS if enabled else BASE_FLAGS
settings = { settings = {
'feature_flags': flags, 'feature_flags': flags,
'own_gil': False,
} }
expected = { expected = {

View file

@ -1666,6 +1666,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
# All optional features should be enabled. # All optional features should be enabled.
'feature_flags': 'feature_flags':
OBMALLOC | FORK | EXEC | THREADS | DAEMON_THREADS, OBMALLOC | FORK | EXEC | THREADS | DAEMON_THREADS,
'own_gil': True,
} }
out, err = self.run_embedded_interpreter( out, err = self.run_embedded_interpreter(
'test_init_main_interpreter_settings', 'test_init_main_interpreter_settings',

View file

@ -1640,6 +1640,7 @@ class SubinterpImportTests(unittest.TestCase):
) )
ISOLATED = dict( ISOLATED = dict(
use_main_obmalloc=False, use_main_obmalloc=False,
own_gil=True,
) )
NOT_ISOLATED = {k: not v for k, v in ISOLATED.items()} NOT_ISOLATED = {k: not v for k, v in ISOLATED.items()}

View file

@ -1349,6 +1349,7 @@ class SubinterpThreadingTests(BaseTestCase):
allow_threads={allowed}, allow_threads={allowed},
allow_daemon_threads={daemon_allowed}, allow_daemon_threads={daemon_allowed},
check_multi_interp_extensions=False, check_multi_interp_extensions=False,
own_gil=False,
) )
""") """)
with test.support.SuppressCrashReport(): with test.support.SuppressCrashReport():

View file

@ -1488,6 +1488,7 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
int allow_threads = -1; int allow_threads = -1;
int allow_daemon_threads = -1; int allow_daemon_threads = -1;
int check_multi_interp_extensions = -1; int check_multi_interp_extensions = -1;
int own_gil = -1;
int r; int r;
PyThreadState *substate, *mainstate; PyThreadState *substate, *mainstate;
/* only initialise 'cflags.cf_flags' to test backwards compatibility */ /* only initialise 'cflags.cf_flags' to test backwards compatibility */
@ -1500,13 +1501,15 @@ 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",
NULL}; NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"s$pppppp:run_in_subinterp_with_config", kwlist, "s$ppppppp:run_in_subinterp_with_config", kwlist,
&code, &use_main_obmalloc, &code, &use_main_obmalloc,
&allow_fork, &allow_exec, &allow_fork, &allow_exec,
&allow_threads, &allow_daemon_threads, &allow_threads, &allow_daemon_threads,
&check_multi_interp_extensions)) { &check_multi_interp_extensions,
&own_gil)) {
return NULL; return NULL;
} }
if (use_main_obmalloc < 0) { if (use_main_obmalloc < 0) {
@ -1525,6 +1528,10 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
PyErr_SetString(PyExc_ValueError, "missing allow_threads"); PyErr_SetString(PyExc_ValueError, "missing allow_threads");
return NULL; return NULL;
} }
if (own_gil < 0) {
PyErr_SetString(PyExc_ValueError, "missing own_gil");
return NULL;
}
if (allow_daemon_threads < 0) { if (allow_daemon_threads < 0) {
PyErr_SetString(PyExc_ValueError, "missing allow_daemon_threads"); PyErr_SetString(PyExc_ValueError, "missing allow_daemon_threads");
return NULL; return NULL;
@ -1545,6 +1552,7 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
.allow_threads = allow_threads, .allow_threads = allow_threads,
.allow_daemon_threads = allow_daemon_threads, .allow_daemon_threads = allow_daemon_threads,
.check_multi_interp_extensions = check_multi_interp_extensions, .check_multi_interp_extensions = check_multi_interp_extensions,
.own_gil = own_gil,
}; };
PyStatus status = Py_NewInterpreterFromConfig(&substate, &config); PyStatus status = Py_NewInterpreterFromConfig(&substate, &config);
if (PyStatus_Exception(status)) { if (PyStatus_Exception(status)) {

View file

@ -729,6 +729,13 @@ get_interp_settings(PyObject *self, PyObject *args)
return NULL; 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; return settings;
} }

View file

@ -500,9 +500,18 @@ PyEval_ThreadsInitialized(void)
} }
PyStatus PyStatus
_PyEval_InitGIL(PyThreadState *tstate) _PyEval_InitGIL(PyThreadState *tstate, int own_gil)
{ {
assert(tstate->interp->ceval.gil == NULL); assert(tstate->interp->ceval.gil == NULL);
if (!own_gil) {
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();
}
/* XXX per-interpreter GIL */ /* XXX per-interpreter GIL */
struct _gil_runtime_state *gil = &tstate->interp->runtime->ceval.gil; struct _gil_runtime_state *gil = &tstate->interp->runtime->ceval.gil;
@ -512,8 +521,11 @@ _PyEval_InitGIL(PyThreadState *tstate)
and destroy it. */ and destroy it. */
assert(gil_created(gil)); assert(gil_created(gil));
tstate->interp->ceval.gil = gil; tstate->interp->ceval.gil = gil;
// XXX For now we lie.
tstate->interp->ceval.own_gil = 1;
return _PyStatus_OK(); return _PyStatus_OK();
} }
assert(own_gil);
assert(!gil_created(gil)); assert(!gil_created(gil));
@ -521,6 +533,7 @@ _PyEval_InitGIL(PyThreadState *tstate)
create_gil(gil); create_gil(gil);
assert(gil_created(gil)); assert(gil_created(gil));
tstate->interp->ceval.gil = gil; tstate->interp->ceval.gil = gil;
tstate->interp->ceval.own_gil = 1;
take_gil(tstate); take_gil(tstate);
return _PyStatus_OK(); return _PyStatus_OK();
} }
@ -530,6 +543,14 @@ _PyEval_FiniGIL(PyInterpreterState *interp)
{ {
if (interp->ceval.gil == NULL) { if (interp->ceval.gil == NULL) {
/* It was already finalized (or hasn't been initialized yet). */ /* 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(interp != main_interp);
assert(interp->ceval.gil == main_interp->ceval.gil);
interp->ceval.gil = NULL;
return; return;
} }

View file

@ -585,7 +585,7 @@ init_interp_settings(PyInterpreterState *interp,
static PyStatus static PyStatus
init_interp_create_gil(PyThreadState *tstate) init_interp_create_gil(PyThreadState *tstate, int own_gil)
{ {
PyStatus status; PyStatus status;
@ -600,7 +600,7 @@ init_interp_create_gil(PyThreadState *tstate)
} }
/* Create the GIL and take it */ /* Create the GIL and take it */
status = _PyEval_InitGIL(tstate); status = _PyEval_InitGIL(tstate, own_gil);
if (_PyStatus_EXCEPTION(status)) { if (_PyStatus_EXCEPTION(status)) {
return status; return status;
} }
@ -632,7 +632,9 @@ pycore_create_interpreter(_PyRuntimeState *runtime,
return status; 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;
status = init_interp_settings(interp, &config); status = init_interp_settings(interp, &config);
if (_PyStatus_EXCEPTION(status)) { if (_PyStatus_EXCEPTION(status)) {
return status; return status;
@ -645,7 +647,7 @@ pycore_create_interpreter(_PyRuntimeState *runtime,
_PyThreadState_Bind(tstate); _PyThreadState_Bind(tstate);
(void) PyThreadState_Swap(tstate); (void) PyThreadState_Swap(tstate);
status = init_interp_create_gil(tstate); status = init_interp_create_gil(tstate, config.own_gil);
if (_PyStatus_EXCEPTION(status)) { if (_PyStatus_EXCEPTION(status)) {
return status; return status;
} }
@ -2047,7 +2049,7 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config)
goto error; goto error;
} }
status = init_interp_create_gil(tstate); status = init_interp_create_gil(tstate, config->own_gil);
if (_PyStatus_EXCEPTION(status)) { if (_PyStatus_EXCEPTION(status)) {
goto error; goto error;
} }