From 5766b5b0b1cc50b56f92d75049b6354fb308a4b5 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 21 Oct 2022 13:05:08 -0600 Subject: [PATCH 01/16] Add _PyInterpreterState_HasFeature(). --- Include/cpython/pystate.h | 27 +++++++++++++++++++++++++++ Modules/_posixsubprocess.c | 3 +-- Modules/_threadmodule.c | 2 +- Modules/_winapi.c | 3 +-- Modules/posixmodule.c | 2 +- Python/pystate.c | 9 +++++++++ 6 files changed, 40 insertions(+), 6 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 7722a384cbfa01..7996bd34eac9d7 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -3,11 +3,38 @@ #endif +/* +Runtime Feature Flags + +Each flag indicate whether or not a specific runtime feature +is available in a given context. For example, forking the process +might not be allowed in the current interpreter (i.e. os.fork() would fail). +*/ + +// We leave the first 10 for less-specific features. + +/* Set if threads are allowed. */ +#define Py_RTFLAGS_THREADS (1UL << 10) + +/* Set if os.fork() is allowed. */ +#define Py_RTFLAGS_FORK (1UL << 15) + +/* Set if subprocesses are allowed. */ +#define Py_RTFLAGS_SUBPROCESS (1UL << 16) + + +PyAPI_FUNC(int) _PyInterpreterState_HasFeature(PyInterpreterState *interp, + unsigned long feature); + + +/* private interpreter helpers */ + PyAPI_FUNC(int) _PyInterpreterState_RequiresIDRef(PyInterpreterState *); PyAPI_FUNC(void) _PyInterpreterState_RequireIDRef(PyInterpreterState *, int); PyAPI_FUNC(PyObject *) _PyInterpreterState_GetMainModule(PyInterpreterState *); + /* State unique per thread */ /* Py_tracefunc return -1 when raising an exception, or 0 for success. */ diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c index 44e60d7c14954d..8275b116093d5e 100644 --- a/Modules/_posixsubprocess.c +++ b/Modules/_posixsubprocess.c @@ -842,8 +842,7 @@ subprocess_fork_exec(PyObject *module, PyObject *args) } PyInterpreterState *interp = PyInterpreterState_Get(); - const PyConfig *config = _PyInterpreterState_GetConfig(interp); - if (config->_isolated_interpreter) { + if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_SUBPROCESS)) { PyErr_SetString(PyExc_RuntimeError, "subprocess not supported for isolated subinterpreters"); return NULL; diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 4ac90dc8068934..93b3b8d85d6604 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1128,7 +1128,7 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs) } PyInterpreterState *interp = _PyInterpreterState_GET(); - if (interp->config._isolated_interpreter) { + if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_THREADS)) { PyErr_SetString(PyExc_RuntimeError, "thread is not supported for isolated subinterpreters"); return NULL; diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 4845b4e6d4ad7c..2a916cc9f46767 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -1090,8 +1090,7 @@ _winapi_CreateProcess_impl(PyObject *module, } PyInterpreterState *interp = PyInterpreterState_Get(); - const PyConfig *config = _PyInterpreterState_GetConfig(interp); - if (config->_isolated_interpreter) { + if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_SUBPROCESS)) { PyErr_SetString(PyExc_RuntimeError, "subprocess not supported for isolated subinterpreters"); return NULL; diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 56ea319ecb3af6..a5eb8663121184 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -6760,7 +6760,7 @@ os_fork_impl(PyObject *module) { pid_t pid; PyInterpreterState *interp = _PyInterpreterState_GET(); - if (interp->config._isolated_interpreter) { + if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_FORK)) { PyErr_SetString(PyExc_RuntimeError, "fork not supported for isolated subinterpreters"); return NULL; diff --git a/Python/pystate.c b/Python/pystate.c index c74868ddfa20f3..7efbb6c692db3d 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2177,6 +2177,15 @@ _Py_GetConfig(void) return _PyInterpreterState_GetConfig(tstate->interp); } + +int +_PyInterpreterState_HasFeature(PyInterpreterState *interp, unsigned long feature) +{ + assert(feature & (Py_RTFLAGS_FORK | Py_RTFLAGS_SUBPROCESS | Py_RTFLAGS_THREADS)); + return interp->config._isolated_interpreter; +} + + #define MINIMUM_OVERHEAD 1000 static PyObject ** From cb52b63f797bd08b6a2bd9b0088bcc7198087b4f Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 21 Oct 2022 13:11:27 -0600 Subject: [PATCH 02/16] Add PyInterpreterState.feature_flags. --- Include/internal/pycore_interp.h | 1 + Python/pylifecycle.c | 15 ++++++++++++++- Python/pystate.c | 3 +-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index e643c7e9e4edd1..6deb1a13cdeffe 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -143,6 +143,7 @@ struct _is { #ifdef HAVE_DLOPEN int dlopenflags; #endif + unsigned long feature_flags; PyObject *dict; /* Stores per-interpreter state */ diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 4195a9dbca812f..5aa46663f4b0a6 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1947,6 +1947,18 @@ Py_Finalize(void) } +static void +init_interp_set_flags(PyInterpreterState *interp, const PyConfig *config) +{ + assert(interp->feature_flags == 0); + if (!config->_isolated_interpreter) { + interp->feature_flags |= Py_RTFLAGS_FORK; + interp->feature_flags |= Py_RTFLAGS_SUBPROCESS; + interp->feature_flags |= Py_RTFLAGS_THREADS; + } +} + + /* Create and initialize a new interpreter and thread, and return the new thread. This requires that Py_Initialize() has been called first. @@ -2006,13 +2018,14 @@ new_interpreter(PyThreadState **tstate_p, int isolated_subinterpreter) config = _PyInterpreterState_GetConfig(main_interp); } - status = _PyConfig_Copy(&interp->config, config); if (_PyStatus_EXCEPTION(status)) { goto error; } interp->config._isolated_interpreter = isolated_subinterpreter; + init_interp_set_flags(interp, config); + status = init_interp_create_gil(tstate); if (_PyStatus_EXCEPTION(status)) { goto error; diff --git a/Python/pystate.c b/Python/pystate.c index 7efbb6c692db3d..dd6d6e92eca89a 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2181,8 +2181,7 @@ _Py_GetConfig(void) int _PyInterpreterState_HasFeature(PyInterpreterState *interp, unsigned long feature) { - assert(feature & (Py_RTFLAGS_FORK | Py_RTFLAGS_SUBPROCESS | Py_RTFLAGS_THREADS)); - return interp->config._isolated_interpreter; + return ((interp->feature_flags & feature) != 0); } From cbb8a46d497f9a08d0b7bb60f3b116a7abe68388 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 20 Oct 2022 10:42:46 -0600 Subject: [PATCH 03/16] Add PyInterpreterConfig, for _Py_NewInterpreter(). --- Include/cpython/initconfig.h | 6 ++++++ Include/cpython/pylifecycle.h | 2 +- Modules/_xxsubinterpretersmodule.c | 5 ++++- Python/pylifecycle.c | 8 ++++---- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h index c22c8d52b4f2ec..9a5d5409aaa1ff 100644 --- a/Include/cpython/initconfig.h +++ b/Include/cpython/initconfig.h @@ -245,6 +245,12 @@ PyAPI_FUNC(PyStatus) PyConfig_SetWideStringList(PyConfig *config, Py_ssize_t length, wchar_t **items); +/* --- PyInterpreterConfig ------------------------------------ */ + +typedef struct { + int isolated; +} PyInterpreterConfig; + /* --- Helper functions --------------------------------------- */ /* Get the original command line arguments, before Python modified them. diff --git a/Include/cpython/pylifecycle.h b/Include/cpython/pylifecycle.h index bb5b07ef5901c8..9c9d0e4b0f1896 100644 --- a/Include/cpython/pylifecycle.h +++ b/Include/cpython/pylifecycle.h @@ -62,4 +62,4 @@ PyAPI_FUNC(int) _Py_CoerceLegacyLocale(int warn); PyAPI_FUNC(int) _Py_LegacyLocaleDetected(int warn); PyAPI_FUNC(char *) _Py_SetLocaleFromEnv(int category); -PyAPI_FUNC(PyThreadState *) _Py_NewInterpreter(int isolated_subinterpreter); +PyAPI_FUNC(PyThreadState *) _Py_NewInterpreter(const PyInterpreterConfig *); diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index f40601ad3a1a72..adf200c021cf34 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -2003,8 +2003,11 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds) // Create and initialize the new interpreter. PyThreadState *save_tstate = _PyThreadState_GET(); + PyInterpreterConfig config = { + .isolated = isolated, + }; // XXX Possible GILState issues? - PyThreadState *tstate = _Py_NewInterpreter(isolated); + PyThreadState *tstate = _Py_NewInterpreter(&config); PyThreadState_Swap(save_tstate); if (tstate == NULL) { /* Since no new thread state was created, there is no exception to diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 5aa46663f4b0a6..49c01404ac902b 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2058,21 +2058,21 @@ new_interpreter(PyThreadState **tstate_p, int isolated_subinterpreter) } PyThreadState * -_Py_NewInterpreter(int isolated_subinterpreter) +_Py_NewInterpreter(const PyInterpreterConfig *config) { PyThreadState *tstate = NULL; - PyStatus status = new_interpreter(&tstate, isolated_subinterpreter); + PyStatus status = new_interpreter(&tstate, config->isolated); if (_PyStatus_EXCEPTION(status)) { Py_ExitStatusException(status); } return tstate; - } PyThreadState * Py_NewInterpreter(void) { - return _Py_NewInterpreter(0); + PyInterpreterConfig config = { 0 }; + return _Py_NewInterpreter(&config); } /* Delete an interpreter and its last thread. This requires that the From 1fe394fad1be1301b6d20bc318c76fca76b47a45 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 21 Oct 2022 13:34:03 -0600 Subject: [PATCH 04/16] Pass the interpreter config to new_interpreter(). --- Python/pylifecycle.c | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 49c01404ac902b..a7f7301c7abd63 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -611,6 +611,18 @@ pycore_init_runtime(_PyRuntimeState *runtime, } +static void +init_interp_settings(PyInterpreterState *interp, const PyConfig *config) +{ + assert(interp->feature_flags == 0); + if (!config->_isolated_interpreter) { + interp->feature_flags |= Py_RTFLAGS_FORK; + interp->feature_flags |= Py_RTFLAGS_SUBPROCESS; + interp->feature_flags |= Py_RTFLAGS_THREADS; + } +} + + static PyStatus init_interp_create_gil(PyThreadState *tstate) { @@ -658,6 +670,8 @@ pycore_create_interpreter(_PyRuntimeState *runtime, return status; } + init_interp_settings(interp, config); + PyThreadState *tstate = PyThreadState_New(interp); if (tstate == NULL) { return _PyStatus_ERR("can't make first thread"); @@ -1947,18 +1961,6 @@ Py_Finalize(void) } -static void -init_interp_set_flags(PyInterpreterState *interp, const PyConfig *config) -{ - assert(interp->feature_flags == 0); - if (!config->_isolated_interpreter) { - interp->feature_flags |= Py_RTFLAGS_FORK; - interp->feature_flags |= Py_RTFLAGS_SUBPROCESS; - interp->feature_flags |= Py_RTFLAGS_THREADS; - } -} - - /* Create and initialize a new interpreter and thread, and return the new thread. This requires that Py_Initialize() has been called first. @@ -1973,7 +1975,7 @@ init_interp_set_flags(PyInterpreterState *interp, const PyConfig *config) */ static PyStatus -new_interpreter(PyThreadState **tstate_p, int isolated_subinterpreter) +new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) { PyStatus status; @@ -2007,24 +2009,24 @@ new_interpreter(PyThreadState **tstate_p, int isolated_subinterpreter) PyThreadState *save_tstate = PyThreadState_Swap(tstate); /* Copy the current interpreter config into the new interpreter */ - const PyConfig *config; + const PyConfig *src_config; if (save_tstate != NULL) { - config = _PyInterpreterState_GetConfig(save_tstate->interp); + src_config = _PyInterpreterState_GetConfig(save_tstate->interp); } else { /* No current thread state, copy from the main interpreter */ PyInterpreterState *main_interp = _PyInterpreterState_Main(); - config = _PyInterpreterState_GetConfig(main_interp); + src_config = _PyInterpreterState_GetConfig(main_interp); } - status = _PyConfig_Copy(&interp->config, config); + status = _PyConfig_Copy(&interp->config, src_config); if (_PyStatus_EXCEPTION(status)) { goto error; } - interp->config._isolated_interpreter = isolated_subinterpreter; + interp->config._isolated_interpreter = config->isolated; - init_interp_set_flags(interp, config); + init_interp_settings(interp, src_config); status = init_interp_create_gil(tstate); if (_PyStatus_EXCEPTION(status)) { @@ -2061,7 +2063,7 @@ PyThreadState * _Py_NewInterpreter(const PyInterpreterConfig *config) { PyThreadState *tstate = NULL; - PyStatus status = new_interpreter(&tstate, config->isolated); + PyStatus status = new_interpreter(&tstate, config); if (_PyStatus_EXCEPTION(status)) { Py_ExitStatusException(status); } From c302ae5fb0f371dcc5933c733d91d40cab9f4b96 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 21 Oct 2022 13:41:13 -0600 Subject: [PATCH 05/16] Pass the interpreter config to init_interp_settings(). --- Python/pylifecycle.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index a7f7301c7abd63..95a743ca1e0d56 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -612,10 +612,10 @@ pycore_init_runtime(_PyRuntimeState *runtime, static void -init_interp_settings(PyInterpreterState *interp, const PyConfig *config) +init_interp_settings(PyInterpreterState *interp, const PyInterpreterConfig *config) { assert(interp->feature_flags == 0); - if (!config->_isolated_interpreter) { + if (!config->isolated) { interp->feature_flags |= Py_RTFLAGS_FORK; interp->feature_flags |= Py_RTFLAGS_SUBPROCESS; interp->feature_flags |= Py_RTFLAGS_THREADS; @@ -650,7 +650,7 @@ init_interp_create_gil(PyThreadState *tstate) static PyStatus pycore_create_interpreter(_PyRuntimeState *runtime, - const PyConfig *config, + const PyConfig *src_config, PyThreadState **tstate_p) { /* Auto-thread-state API */ @@ -665,12 +665,15 @@ pycore_create_interpreter(_PyRuntimeState *runtime, } assert(_Py_IsMainInterpreter(interp)); - status = _PyConfig_Copy(&interp->config, config); + status = _PyConfig_Copy(&interp->config, src_config); if (_PyStatus_EXCEPTION(status)) { return status; } - init_interp_settings(interp, config); + const PyInterpreterConfig config = { + .isolated = 0, + }; + init_interp_settings(interp, &config); PyThreadState *tstate = PyThreadState_New(interp); if (tstate == NULL) { @@ -2026,7 +2029,7 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) } interp->config._isolated_interpreter = config->isolated; - init_interp_settings(interp, src_config); + init_interp_settings(interp, config); status = init_interp_create_gil(tstate); if (_PyStatus_EXCEPTION(status)) { From ad2d878bfe055934ef17cc354347898264a7bdaa Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 21 Oct 2022 13:44:47 -0600 Subject: [PATCH 06/16] Drop PyConfig._isolated_interpreter. --- Doc/c-api/init_config.rst | 2 -- Include/cpython/initconfig.h | 4 ---- Lib/test/_test_embed_set_config.py | 1 - Lib/test/test_embed.py | 3 --- Programs/_testembed.c | 2 -- Python/initconfig.c | 4 ---- Python/pylifecycle.c | 1 - 7 files changed, 17 deletions(-) diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst index ea76315fc09b62..64bdfefd6494ff 100644 --- a/Doc/c-api/init_config.rst +++ b/Doc/c-api/init_config.rst @@ -1571,8 +1571,6 @@ Private provisional API: * :c:member:`PyConfig._init_main`: if set to ``0``, :c:func:`Py_InitializeFromConfig` stops at the "Core" initialization phase. -* :c:member:`PyConfig._isolated_interpreter`: if non-zero, - disallow threads, subprocesses and fork. .. c:function:: PyStatus _Py_InitializeMain(void) diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h index 9a5d5409aaa1ff..d8f31da6cdc0b9 100644 --- a/Include/cpython/initconfig.h +++ b/Include/cpython/initconfig.h @@ -213,10 +213,6 @@ typedef struct PyConfig { // If equal to 0, stop Python initialization before the "main" phase. int _init_main; - // If non-zero, disallow threads, subprocesses, and fork. - // Default: 0. - int _isolated_interpreter; - // If non-zero, we believe we're running from a source tree. int _is_python_build; } PyConfig; diff --git a/Lib/test/_test_embed_set_config.py b/Lib/test/_test_embed_set_config.py index 7ff641b37bf188..0c016b5d75d734 100644 --- a/Lib/test/_test_embed_set_config.py +++ b/Lib/test/_test_embed_set_config.py @@ -84,7 +84,6 @@ def test_set_invalid(self): 'skip_source_first_line', '_install_importlib', '_init_main', - '_isolated_interpreter', ] if MS_WINDOWS: options.append('legacy_windows_stdio') diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index c5aeb9459848e4..bd51e96374bd22 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -496,7 +496,6 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'check_hash_pycs_mode': 'default', 'pathconfig_warnings': 1, '_init_main': 1, - '_isolated_interpreter': 0, 'use_frozen_modules': not support.Py_DEBUG, 'safe_path': 0, '_is_python_build': IGNORE_CONFIG, @@ -881,8 +880,6 @@ def test_init_from_config(self): 'check_hash_pycs_mode': 'always', 'pathconfig_warnings': 0, - - '_isolated_interpreter': 1, } self.check_all_configs("test_init_from_config", config, preconfig, api=API_COMPAT) diff --git a/Programs/_testembed.c b/Programs/_testembed.c index d635c5a4abe34a..36c696d9fc418f 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -681,8 +681,6 @@ static int test_init_from_config(void) config.safe_path = 1; - config._isolated_interpreter = 1; - putenv("PYTHONINTMAXSTRDIGITS=6666"); config.int_max_str_digits = 31337; diff --git a/Python/initconfig.c b/Python/initconfig.c index bbc2ebb09fd039..4b784290b014d2 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -780,7 +780,6 @@ _PyConfig_InitCompatConfig(PyConfig *config) config->check_hash_pycs_mode = NULL; config->pathconfig_warnings = -1; config->_init_main = 1; - config->_isolated_interpreter = 0; #ifdef MS_WINDOWS config->legacy_windows_stdio = -1; #endif @@ -1015,7 +1014,6 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2) COPY_WSTR_ATTR(check_hash_pycs_mode); COPY_ATTR(pathconfig_warnings); COPY_ATTR(_init_main); - COPY_ATTR(_isolated_interpreter); COPY_ATTR(use_frozen_modules); COPY_ATTR(safe_path); COPY_WSTRLIST(orig_argv); @@ -1123,7 +1121,6 @@ _PyConfig_AsDict(const PyConfig *config) SET_ITEM_WSTR(check_hash_pycs_mode); SET_ITEM_INT(pathconfig_warnings); SET_ITEM_INT(_init_main); - SET_ITEM_INT(_isolated_interpreter); SET_ITEM_WSTRLIST(orig_argv); SET_ITEM_INT(use_frozen_modules); SET_ITEM_INT(safe_path); @@ -1418,7 +1415,6 @@ _PyConfig_FromDict(PyConfig *config, PyObject *dict) GET_UINT(_install_importlib); GET_UINT(_init_main); - GET_UINT(_isolated_interpreter); GET_UINT(use_frozen_modules); GET_UINT(safe_path); GET_UINT(_is_python_build); diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 95a743ca1e0d56..105fc9508f58f5 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2027,7 +2027,6 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) if (_PyStatus_EXCEPTION(status)) { goto error; } - interp->config._isolated_interpreter = config->isolated; init_interp_settings(interp, config); From 5a004065cbd25cb3ec415d9bb0ccf0004c32b0f4 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 21 Oct 2022 13:53:53 -0600 Subject: [PATCH 07/16] PyInterpreterConfig -> _PyInterpreterConfig. --- Include/cpython/initconfig.h | 2 +- Include/cpython/pylifecycle.h | 2 +- Modules/_xxsubinterpretersmodule.c | 2 +- Python/pylifecycle.c | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h index d8f31da6cdc0b9..ba5bc91bf91ab3 100644 --- a/Include/cpython/initconfig.h +++ b/Include/cpython/initconfig.h @@ -245,7 +245,7 @@ PyAPI_FUNC(PyStatus) PyConfig_SetWideStringList(PyConfig *config, typedef struct { int isolated; -} PyInterpreterConfig; +} _PyInterpreterConfig; /* --- Helper functions --------------------------------------- */ diff --git a/Include/cpython/pylifecycle.h b/Include/cpython/pylifecycle.h index 9c9d0e4b0f1896..ec50d31842f6b2 100644 --- a/Include/cpython/pylifecycle.h +++ b/Include/cpython/pylifecycle.h @@ -62,4 +62,4 @@ PyAPI_FUNC(int) _Py_CoerceLegacyLocale(int warn); PyAPI_FUNC(int) _Py_LegacyLocaleDetected(int warn); PyAPI_FUNC(char *) _Py_SetLocaleFromEnv(int category); -PyAPI_FUNC(PyThreadState *) _Py_NewInterpreter(const PyInterpreterConfig *); +PyAPI_FUNC(PyThreadState *) _Py_NewInterpreter(const _PyInterpreterConfig *); diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index adf200c021cf34..242a0df117c1d0 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -2003,7 +2003,7 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds) // Create and initialize the new interpreter. PyThreadState *save_tstate = _PyThreadState_GET(); - PyInterpreterConfig config = { + _PyInterpreterConfig config = { .isolated = isolated, }; // XXX Possible GILState issues? diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 105fc9508f58f5..4a44b42b7b9c06 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -612,7 +612,7 @@ pycore_init_runtime(_PyRuntimeState *runtime, static void -init_interp_settings(PyInterpreterState *interp, const PyInterpreterConfig *config) +init_interp_settings(PyInterpreterState *interp, const _PyInterpreterConfig *config) { assert(interp->feature_flags == 0); if (!config->isolated) { @@ -670,7 +670,7 @@ pycore_create_interpreter(_PyRuntimeState *runtime, return status; } - const PyInterpreterConfig config = { + const _PyInterpreterConfig config = { .isolated = 0, }; init_interp_settings(interp, &config); @@ -1978,7 +1978,7 @@ Py_Finalize(void) */ static PyStatus -new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) +new_interpreter(PyThreadState **tstate_p, const _PyInterpreterConfig *config) { PyStatus status; @@ -2062,7 +2062,7 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) } PyThreadState * -_Py_NewInterpreter(const PyInterpreterConfig *config) +_Py_NewInterpreter(const _PyInterpreterConfig *config) { PyThreadState *tstate = NULL; PyStatus status = new_interpreter(&tstate, config); @@ -2075,7 +2075,7 @@ _Py_NewInterpreter(const PyInterpreterConfig *config) PyThreadState * Py_NewInterpreter(void) { - PyInterpreterConfig config = { 0 }; + _PyInterpreterConfig config = { 0 }; return _Py_NewInterpreter(&config); } From 658b5861db2371c10d200ba9d9de5629098108fc Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 21 Oct 2022 13:56:37 -0600 Subject: [PATCH 08/16] _Py_NewInterpreter() -> _Py_NewInterpreterFromConfig() --- Include/cpython/pylifecycle.h | 3 ++- Modules/_xxsubinterpretersmodule.c | 2 +- Python/pylifecycle.c | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Include/cpython/pylifecycle.h b/Include/cpython/pylifecycle.h index ec50d31842f6b2..e1f83acbffc360 100644 --- a/Include/cpython/pylifecycle.h +++ b/Include/cpython/pylifecycle.h @@ -62,4 +62,5 @@ PyAPI_FUNC(int) _Py_CoerceLegacyLocale(int warn); PyAPI_FUNC(int) _Py_LegacyLocaleDetected(int warn); PyAPI_FUNC(char *) _Py_SetLocaleFromEnv(int category); -PyAPI_FUNC(PyThreadState *) _Py_NewInterpreter(const _PyInterpreterConfig *); +PyAPI_FUNC(PyThreadState *) _Py_NewInterpreterFromConfig( + const _PyInterpreterConfig *); diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 242a0df117c1d0..427de6d5e2d3c1 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -2007,7 +2007,7 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds) .isolated = isolated, }; // XXX Possible GILState issues? - PyThreadState *tstate = _Py_NewInterpreter(&config); + PyThreadState *tstate = _Py_NewInterpreterFromConfig(&config); PyThreadState_Swap(save_tstate); if (tstate == NULL) { /* Since no new thread state was created, there is no exception to diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 4a44b42b7b9c06..6b439664074b2d 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2062,7 +2062,7 @@ new_interpreter(PyThreadState **tstate_p, const _PyInterpreterConfig *config) } PyThreadState * -_Py_NewInterpreter(const _PyInterpreterConfig *config) +_Py_NewInterpreterFromConfig(const _PyInterpreterConfig *config) { PyThreadState *tstate = NULL; PyStatus status = new_interpreter(&tstate, config); @@ -2076,7 +2076,7 @@ PyThreadState * Py_NewInterpreter(void) { _PyInterpreterConfig config = { 0 }; - return _Py_NewInterpreter(&config); + return _Py_NewInterpreterFromConfig(&config); } /* Delete an interpreter and its last thread. This requires that the From 56b4fa43526d5a007da4ae00bbf6c2a08814fb36 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 21 Oct 2022 14:06:12 -0600 Subject: [PATCH 09/16] Split up _PyInterpreterConfig.isolated. --- Include/cpython/initconfig.h | 11 ++++++++++- Modules/_xxsubinterpretersmodule.c | 7 +++---- Python/pylifecycle.c | 12 +++++++----- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h index ba5bc91bf91ab3..64748cf78beb72 100644 --- a/Include/cpython/initconfig.h +++ b/Include/cpython/initconfig.h @@ -244,9 +244,18 @@ PyAPI_FUNC(PyStatus) PyConfig_SetWideStringList(PyConfig *config, /* --- PyInterpreterConfig ------------------------------------ */ typedef struct { - int isolated; + int allow_fork; + int allow_subprocess; + int allow_threads; } _PyInterpreterConfig; +#define _PyInterpreterConfig_LEGACY_INIT \ + { \ + .allow_fork = 1, \ + .allow_subprocess = 1, \ + .allow_threads = 1, \ + } + /* --- Helper functions --------------------------------------- */ /* Get the original command line arguments, before Python modified them. diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 427de6d5e2d3c1..43370003022d7c 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -2001,11 +2001,10 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds) return NULL; } - // Create and initialize the new interpreter. + // Create and initialize the new interpreter + // (with all optional features disabled). PyThreadState *save_tstate = _PyThreadState_GET(); - _PyInterpreterConfig config = { - .isolated = isolated, - }; + _PyInterpreterConfig config = { 0 }; // XXX Possible GILState issues? PyThreadState *tstate = _Py_NewInterpreterFromConfig(&config); PyThreadState_Swap(save_tstate); diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 6b439664074b2d..334abfb191fee1 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -615,9 +615,13 @@ static void init_interp_settings(PyInterpreterState *interp, const _PyInterpreterConfig *config) { assert(interp->feature_flags == 0); - if (!config->isolated) { + if (config->allow_fork) { interp->feature_flags |= Py_RTFLAGS_FORK; + } + if (config->allow_subprocess) { interp->feature_flags |= Py_RTFLAGS_SUBPROCESS; + } + if (config->allow_threads) { interp->feature_flags |= Py_RTFLAGS_THREADS; } } @@ -670,9 +674,7 @@ pycore_create_interpreter(_PyRuntimeState *runtime, return status; } - const _PyInterpreterConfig config = { - .isolated = 0, - }; + const _PyInterpreterConfig config = _PyInterpreterConfig_LEGACY_INIT; init_interp_settings(interp, &config); PyThreadState *tstate = PyThreadState_New(interp); @@ -2075,7 +2077,7 @@ _Py_NewInterpreterFromConfig(const _PyInterpreterConfig *config) PyThreadState * Py_NewInterpreter(void) { - _PyInterpreterConfig config = { 0 }; + const _PyInterpreterConfig config = _PyInterpreterConfig_LEGACY_INIT; return _Py_NewInterpreterFromConfig(&config); } From 0f94f397ff166fa34beb5caa32536b5ae30d40b8 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 21 Oct 2022 15:01:18 -0600 Subject: [PATCH 10/16] Add a test to verify the main interpreter's feature flags. --- Lib/test/test_embed.py | 19 +++++++++++++++++++ Modules/_testinternalcapi.c | 24 ++++++++++++++++++++++++ Programs/_testembed.c | 13 +++++++++++++ 3 files changed, 56 insertions(+) diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index bd51e96374bd22..f622d443257f0f 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -1647,6 +1647,25 @@ def test_init_use_frozen_modules(self): self.check_all_configs("test_init_use_frozen_modules", config, api=API_PYTHON, env=env) + def test_init_main_interpreter_settings(self): + THREADS = 1<<10 + FORK = 1<<15 + SUBPROCESS = 1<<16 + expected = { + # All optional features should be enabled. + 'feature_flags': THREADS | FORK | SUBPROCESS, + } + out, err = self.run_embedded_interpreter( + 'test_init_main_interpreter_settings', + ) + self.assertEqual(err, '') + try: + out = json.loads(out) + except json.JSONDecodeError: + self.fail(f'fail to decode stdout: {out!r}') + + self.assertEqual(out, expected) + class SetConfigTests(unittest.TestCase): def test_set_config(self): diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 5724bd5f200f4c..1d6527e654d340 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -550,6 +550,29 @@ _testinternalcapi_optimize_cfg_impl(PyObject *module, PyObject *instructions, } +static PyObject * +get_interp_settings(PyObject *self, PyObject *Py_UNUSED(args)) +{ + PyInterpreterState *interp = _PyInterpreterState_Main(); + PyObject *flags = PyLong_FromUnsignedLong(interp->feature_flags); + if (flags == NULL) { + return NULL; + } + PyObject *settings = PyDict_New(); + if (settings == NULL) { + Py_DECREF(flags); + return NULL; + } + int res = PyDict_SetItemString(settings, "feature_flags", flags); + Py_DECREF(flags); + if (res != 0) { + Py_DECREF(settings); + return NULL; + } + return settings; +} + + static PyMethodDef TestMethods[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, @@ -569,6 +592,7 @@ static PyMethodDef TestMethods[] = { {"set_eval_frame_default", set_eval_frame_default, METH_NOARGS, NULL}, {"set_eval_frame_record", set_eval_frame_record, METH_O, NULL}, _TESTINTERNALCAPI_OPTIMIZE_CFG_METHODDEF + {"get_interp_settings", get_interp_settings, METH_NOARGS, NULL}, {NULL, NULL} /* sentinel */ }; diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 36c696d9fc418f..23232d391ddb65 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -1899,6 +1899,18 @@ static int test_unicode_id_init(void) } +static int test_init_main_interpreter_settings(void) +{ + _testembed_Py_Initialize(); + (void) PyRun_SimpleStringFlags( + "import _testinternalcapi, json; " + "print(json.dumps(_testinternalcapi.get_interp_settings()))", + 0); + Py_Finalize(); + return 0; +} + + #ifndef MS_WINDOWS #include "test_frozenmain.h" // M_test_frozenmain @@ -2085,6 +2097,7 @@ static struct TestCase TestCases[] = { {"test_run_main_loop", test_run_main_loop}, {"test_get_argc_argv", test_get_argc_argv}, {"test_init_use_frozen_modules", test_init_use_frozen_modules}, + {"test_init_main_interpreter_settings", test_init_main_interpreter_settings}, // Audit {"test_open_code_hook", test_open_code_hook}, From bb3c1b84ce846c46b8811521b071ee602a47f0c5 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 21 Oct 2022 17:00:17 -0600 Subject: [PATCH 11/16] Add tests for _Py_NewInterpreterFromConfig(). --- Lib/test/support/__init__.py | 18 +++++++++-- Lib/test/test_capi.py | 40 +++++++++++++++++++++++ Modules/_testcapimodule.c | 63 ++++++++++++++++++++++++++++++++++++ Modules/_testinternalcapi.c | 34 +++++++++++++++---- Programs/_testembed.c | 2 +- 5 files changed, 148 insertions(+), 9 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 9fdad641232c4f..3e6a787e617a31 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -1793,6 +1793,22 @@ def run_in_subinterp(code): Run code in a subinterpreter. Raise unittest.SkipTest if the tracemalloc module is enabled. """ + _check_tracemalloc() + import _testcapi + return _testcapi.run_in_subinterp(code) + + +def run_in_subinterp_with_config(code, **config): + """ + Run code in a subinterpreter. Raise unittest.SkipTest if the tracemalloc + module is enabled. + """ + _check_tracemalloc() + import _testcapi + return _testcapi.run_in_subinterp_with_config(code, **config) + + +def _check_tracemalloc(): # Issue #10915, #15751: PyGILState_*() functions don't work with # sub-interpreters, the tracemalloc module uses these functions internally try: @@ -1804,8 +1820,6 @@ def run_in_subinterp(code): raise unittest.SkipTest("run_in_subinterp() cannot be used " "if tracemalloc module is tracing " "memory allocations") - import _testcapi - return _testcapi.run_in_subinterp(code) def check_free_after_iterating(test, iter, cls, args=()): diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index a2183cfb0fdf6a..450551475ec113 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -1084,6 +1084,46 @@ def test_py_config_isoloated_per_interpreter(self): # test fails, assume that the environment in this process may # be altered and suspect. + def test_configured_settings(self): + """ + The config with which an interpreter is created corresponds + 1-to-1 with the new interpreter's settings. This test verifies + that they match. + """ + import json + + THREADS = 1<<10 + FORK = 1<<15 + SUBPROCESS = 1<<16 + + for config, expected in { + (True, True, True): THREADS | FORK | SUBPROCESS, + (False, False, False): 0, + (False, True, True): THREADS | SUBPROCESS, + }.items(): + allow_fork, allow_subprocess, allow_threads = config + expected = { + 'feature_flags': expected, + } + with self.subTest(config): + r, w = os.pipe() + with os.fdopen(r) as stdout: + support.run_in_subinterp_with_config( + textwrap.dedent(f''' + import _testinternalcapi, json, os + settings = _testinternalcapi.get_interp_settings() + with os.fdopen({w}, "w") as stdin: + json.dump(settings, stdin) + '''), + allow_fork=allow_fork, + allow_subprocess=allow_subprocess, + allow_threads=allow_threads, + ) + out = stdout.read() + settings = json.loads(out) + + self.assertEqual(settings, expected) + def test_mutate_exception(self): """ Exceptions saved in global module state get shared between diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 624e878b20d822..c9a9729de2c58e 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3225,6 +3225,66 @@ run_in_subinterp(PyObject *self, PyObject *args) return PyLong_FromLong(r); } +/* To run some code in a sub-interpreter. */ +static PyObject * +run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char *code; + int allow_fork = -1; + int allow_subprocess = -1; + int allow_threads = -1; + int r; + PyThreadState *substate, *mainstate; + /* only initialise 'cflags.cf_flags' to test backwards compatibility */ + PyCompilerFlags cflags = {0}; + + static char *kwlist[] = {"code", + "allow_fork", "allow_subprocess", "allow_threads", + NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "s$ppp:run_in_subinterp_with_config", kwlist, + &code, &allow_fork, &allow_subprocess, &allow_threads)) { + return NULL; + } + if (allow_fork < 0) { + PyErr_SetString(PyExc_ValueError, "missing allow_fork"); + return NULL; + } + if (allow_subprocess < 0) { + PyErr_SetString(PyExc_ValueError, "missing allow_subprocess"); + return NULL; + } + if (allow_threads < 0) { + PyErr_SetString(PyExc_ValueError, "missing allow_threads"); + return NULL; + } + + mainstate = PyThreadState_Get(); + + PyThreadState_Swap(NULL); + + _PyInterpreterConfig config = { + .allow_fork = allow_fork, + .allow_subprocess = allow_subprocess, + .allow_threads = allow_threads, + }; + substate = _Py_NewInterpreterFromConfig(&config); + if (substate == NULL) { + /* Since no new thread state was created, there is no exception to + propagate; raise a fresh one after swapping in the old thread + state. */ + PyThreadState_Swap(mainstate); + PyErr_SetString(PyExc_RuntimeError, "sub-interpreter creation failed"); + return NULL; + } + r = PyRun_SimpleStringFlags(code, &cflags); + Py_EndInterpreter(substate); + + PyThreadState_Swap(mainstate); + + return PyLong_FromLong(r); +} + static int check_time_rounding(int round) { @@ -5842,6 +5902,9 @@ static PyMethodDef TestMethods[] = { METH_NOARGS}, {"crash_no_current_thread", crash_no_current_thread, METH_NOARGS}, {"run_in_subinterp", run_in_subinterp, METH_VARARGS}, + {"run_in_subinterp_with_config", + _PyCFunction_CAST(run_in_subinterp_with_config), + METH_VARARGS | METH_KEYWORDS}, {"pytime_object_to_time_t", test_pytime_object_to_time_t, METH_VARARGS}, {"pytime_object_to_timeval", test_pytime_object_to_timeval, METH_VARARGS}, {"pytime_object_to_timespec", test_pytime_object_to_timespec, METH_VARARGS}, diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 1d6527e654d340..d74f92808d3aba 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -551,16 +551,37 @@ _testinternalcapi_optimize_cfg_impl(PyObject *module, PyObject *instructions, static PyObject * -get_interp_settings(PyObject *self, PyObject *Py_UNUSED(args)) +get_interp_settings(PyObject *self, PyObject *args) { - PyInterpreterState *interp = _PyInterpreterState_Main(); - PyObject *flags = PyLong_FromUnsignedLong(interp->feature_flags); - if (flags == NULL) { + int interpid = -1; + if (!PyArg_ParseTuple(args, "|i:get_interp_settings", &interpid)) { + return NULL; + } + + PyInterpreterState *interp = NULL; + if (interpid < 0) { + PyThreadState *tstate = _PyThreadState_GET(); + interp = tstate ? tstate->interp : _PyInterpreterState_Main(); + } + else if (interpid == 0) { + interp = _PyInterpreterState_Main(); + } + else { + PyErr_Format(PyExc_NotImplementedError, + "%zd", interpid); return NULL; } + assert(interp != NULL); + PyObject *settings = PyDict_New(); if (settings == NULL) { - Py_DECREF(flags); + return NULL; + } + + /* Add the feature flags. */ + PyObject *flags = PyLong_FromUnsignedLong(interp->feature_flags); + if (flags == NULL) { + Py_DECREF(settings); return NULL; } int res = PyDict_SetItemString(settings, "feature_flags", flags); @@ -569,6 +590,7 @@ get_interp_settings(PyObject *self, PyObject *Py_UNUSED(args)) Py_DECREF(settings); return NULL; } + return settings; } @@ -592,7 +614,7 @@ static PyMethodDef TestMethods[] = { {"set_eval_frame_default", set_eval_frame_default, METH_NOARGS, NULL}, {"set_eval_frame_record", set_eval_frame_record, METH_O, NULL}, _TESTINTERNALCAPI_OPTIMIZE_CFG_METHODDEF - {"get_interp_settings", get_interp_settings, METH_NOARGS, NULL}, + {"get_interp_settings", get_interp_settings, METH_VARARGS, NULL}, {NULL, NULL} /* sentinel */ }; diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 23232d391ddb65..ba599591d3c4d6 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -1904,7 +1904,7 @@ static int test_init_main_interpreter_settings(void) _testembed_Py_Initialize(); (void) PyRun_SimpleStringFlags( "import _testinternalcapi, json; " - "print(json.dumps(_testinternalcapi.get_interp_settings()))", + "print(json.dumps(_testinternalcapi.get_interp_settings(0)))", 0); Py_Finalize(); return 0; From bd5295604ec6ca241c3174f5bad9c39b6df1872a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 24 Oct 2022 11:27:05 -0600 Subject: [PATCH 12/16] Add a NEWS entry. --- .../next/C API/2022-10-24-11-26-55.gh-issue-98608._Q2lNV.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/C API/2022-10-24-11-26-55.gh-issue-98608._Q2lNV.rst diff --git a/Misc/NEWS.d/next/C API/2022-10-24-11-26-55.gh-issue-98608._Q2lNV.rst b/Misc/NEWS.d/next/C API/2022-10-24-11-26-55.gh-issue-98608._Q2lNV.rst new file mode 100644 index 00000000000000..5abbd96a4261f7 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2022-10-24-11-26-55.gh-issue-98608._Q2lNV.rst @@ -0,0 +1,4 @@ +A ``_PyInterpreterConfig`` has been added and ``_Py_NewInterpreter()`` has +been renamed to ``_Py_NewInterpreterFromConfig()``. The +"isolated_subinterpreters" argument is now a granular config that captures +the previous behavior. Note that this is all "private" API. From b1402e35808ba661792e3163e08cbd9681be698b Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 24 Oct 2022 12:27:36 -0600 Subject: [PATCH 13/16] Fix _xxsubinterpreters. --- Modules/_xxsubinterpretersmodule.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 43370003022d7c..991b2927288c9d 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -2004,7 +2004,11 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds) // Create and initialize the new interpreter // (with all optional features disabled). PyThreadState *save_tstate = _PyThreadState_GET(); - _PyInterpreterConfig config = { 0 }; + _PyInterpreterConfig config = { + .allow_fork = !isolated, + .allow_subprocess = !isolated, + .allow_threads = !isolated, + }; // XXX Possible GILState issues? PyThreadState *tstate = _Py_NewInterpreterFromConfig(&config); PyThreadState_Swap(save_tstate); From 1172199c15d05e8bc454c8d33dec270270517d7d Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 25 Oct 2022 12:00:44 -0600 Subject: [PATCH 14/16] Fix a comment. --- Modules/_xxsubinterpretersmodule.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 991b2927288c9d..1324a92666777c 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -2001,8 +2001,7 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds) return NULL; } - // Create and initialize the new interpreter - // (with all optional features disabled). + // Create and initialize the new interpreter. PyThreadState *save_tstate = _PyThreadState_GET(); _PyInterpreterConfig config = { .allow_fork = !isolated, From c97e0af85bea3fe6bbc471e7dfd6d7c3dbb6952d Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 25 Oct 2022 12:02:31 -0600 Subject: [PATCH 15/16] Always use a const _PyInterpreterConfig. --- Modules/_testcapimodule.c | 2 +- Modules/_xxsubinterpretersmodule.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index c9a9729de2c58e..49229a6315a256 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3263,7 +3263,7 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) PyThreadState_Swap(NULL); - _PyInterpreterConfig config = { + const _PyInterpreterConfig config = { .allow_fork = allow_fork, .allow_subprocess = allow_subprocess, .allow_threads = allow_threads, diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 1324a92666777c..f38de57f69e2be 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -2003,7 +2003,7 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds) // Create and initialize the new interpreter. PyThreadState *save_tstate = _PyThreadState_GET(); - _PyInterpreterConfig config = { + const _PyInterpreterConfig config = { .allow_fork = !isolated, .allow_subprocess = !isolated, .allow_threads = !isolated, From 39402e2fcaa987781bb9380de1436e4d2c53c451 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 25 Oct 2022 12:12:21 -0600 Subject: [PATCH 16/16] Tweak test_configured_settings. --- Lib/test/test_capi.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index 450551475ec113..0350b6b3b11650 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -1084,6 +1084,7 @@ def test_py_config_isoloated_per_interpreter(self): # test fails, assume that the environment in this process may # be altered and suspect. + @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") def test_configured_settings(self): """ The config with which an interpreter is created corresponds @@ -1096,29 +1097,27 @@ def test_configured_settings(self): FORK = 1<<15 SUBPROCESS = 1<<16 + features = ['fork', 'subprocess', 'threads'] + kwlist = [f'allow_{n}' for n in features] for config, expected in { - (True, True, True): THREADS | FORK | SUBPROCESS, + (True, True, True): FORK | SUBPROCESS | THREADS, (False, False, False): 0, - (False, True, True): THREADS | SUBPROCESS, + (False, True, True): SUBPROCESS | THREADS, }.items(): - allow_fork, allow_subprocess, allow_threads = config + kwargs = dict(zip(kwlist, config)) expected = { 'feature_flags': expected, } with self.subTest(config): r, w = os.pipe() + script = textwrap.dedent(f''' + import _testinternalcapi, json, os + settings = _testinternalcapi.get_interp_settings() + with os.fdopen({w}, "w") as stdin: + json.dump(settings, stdin) + ''') with os.fdopen(r) as stdout: - support.run_in_subinterp_with_config( - textwrap.dedent(f''' - import _testinternalcapi, json, os - settings = _testinternalcapi.get_interp_settings() - with os.fdopen({w}, "w") as stdin: - json.dump(settings, stdin) - '''), - allow_fork=allow_fork, - allow_subprocess=allow_subprocess, - allow_threads=allow_threads, - ) + support.run_in_subinterp_with_config(script, **kwargs) out = stdout.read() settings = json.loads(out)