From 048a35659aa8074afe7d7d054e7cea1f8ee6d366 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 5 Nov 2020 00:45:56 +0100 Subject: [PATCH] bpo-42260: Add _PyInterpreterState_SetConfig() (GH-23158) * Inline _PyInterpreterState_SetConfig(): replace it with _PyConfig_Copy(). * Add _PyErr_SetFromPyStatus() * Add _PyInterpreterState_GetConfigCopy() * Add a new _PyInterpreterState_SetConfig() function. * Add an unit which gets, modifies, and sets the config. --- Doc/c-api/init_config.rst | 2 + Include/cpython/pystate.h | 30 ++++++++++++ Include/internal/pycore_initconfig.h | 2 + Include/internal/pycore_interp.h | 6 --- Lib/test/test_embed.py | 9 ++++ Programs/_testembed.c | 50 ++++++++++++++++++++ Python/initconfig.c | 20 +++++++- Python/pylifecycle.c | 70 ++++++++++++++++++++++++++-- Python/pystate.c | 16 +++++-- 9 files changed, 189 insertions(+), 16 deletions(-) diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst index dad1f90bea5..c957d6c0f72 100644 --- a/Doc/c-api/init_config.rst +++ b/Doc/c-api/init_config.rst @@ -128,6 +128,8 @@ PyStatus Initialization error with a message. + *err_msg* must not be ``NULL``. + .. c:function:: PyStatus PyStatus_NoMemory(void) Memory allocation failure (out of memory). diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 25522b4dbec..0e6cc290912 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -193,6 +193,36 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( PyAPI_FUNC(const PyConfig*) _PyInterpreterState_GetConfig(PyInterpreterState *interp); +/* Get a copy of the current interpreter configuration. + + Return 0 on success. Raise an exception and return -1 on error. + + The caller must initialize 'config', using PyConfig_InitPythonConfig() + for example. + + Python must be preinitialized to call this method. + The caller must hold the GIL. */ +PyAPI_FUNC(int) _PyInterpreterState_GetConfigCopy( + struct PyConfig *config); + +/* Set the configuration of the current interpreter. + + This function should be called during or just after the Python + initialization. + + Update the sys module with the new configuration. If the sys module was + modified directly after the Python initialization, these changes are lost. + + Some configuration like faulthandler or warnoptions can be updated in the + configuration, but don't reconfigure Python (don't enable/disable + faulthandler and don't reconfigure warnings filters). + + Return 0 on success. Raise an exception and return -1 on error. + + The configuration should come from _PyInterpreterState_GetConfigCopy(). */ +PyAPI_FUNC(int) _PyInterpreterState_SetConfig( + const struct PyConfig *config); + // Get the configuration of the currrent interpreter. // The caller must hold the GIL. PyAPI_FUNC(const PyConfig*) _Py_GetConfig(void); diff --git a/Include/internal/pycore_initconfig.h b/Include/internal/pycore_initconfig.h index 457a005860b..df7ad779f47 100644 --- a/Include/internal/pycore_initconfig.h +++ b/Include/internal/pycore_initconfig.h @@ -44,6 +44,8 @@ struct pyruntimestate; #define _PyStatus_UPDATE_FUNC(err) \ do { err.func = _PyStatus_GET_FUNC(); } while (0) +PyObject* _PyErr_SetFromPyStatus(PyStatus status); + /* --- PyWideStringList ------------------------------------------------ */ #define _PyWideStringList_INIT (PyWideStringList){.length = 0, .items = NULL} diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 9923b6b03da..4b67a86a25a 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -263,13 +263,7 @@ struct _is { struct ast_state ast; }; -/* Used by _PyImport_Cleanup() */ extern void _PyInterpreterState_ClearModules(PyInterpreterState *interp); - -extern PyStatus _PyInterpreterState_SetConfig( - PyInterpreterState *interp, - const PyConfig *config); - extern void _PyInterpreterState_Clear(PyThreadState *tstate); diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 31dc39fd9e8..36a0e77e14c 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -1394,6 +1394,15 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): self.check_all_configs("test_init_warnoptions", config, preconfig, api=API_PYTHON) + def test_init_set_config(self): + config = { + '_init_main': 0, + 'bytes_warning': 2, + 'warnoptions': ['error::BytesWarning'], + } + self.check_all_configs("test_init_set_config", config, + api=API_ISOLATED) + def test_get_argc_argv(self): self.run_embedded_interpreter("test_get_argc_argv") # ignore output diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 5aad16a6f7c..cb3a23a101e 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -1526,6 +1526,55 @@ static int test_init_warnoptions(void) } +static int tune_config(void) +{ + PyConfig config; + PyConfig_InitPythonConfig(&config); + if (_PyInterpreterState_GetConfigCopy(&config) < 0) { + PyConfig_Clear(&config); + PyErr_Print(); + return -1; + } + + config.bytes_warning = 2; + + if (_PyInterpreterState_SetConfig(&config) < 0) { + PyConfig_Clear(&config); + return -1; + } + PyConfig_Clear(&config); + return 0; +} + + +static int test_set_config(void) +{ + // Initialize core + PyConfig config; + PyConfig_InitIsolatedConfig(&config); + config_set_string(&config, &config.program_name, PROGRAM_NAME); + config._init_main = 0; + config.bytes_warning = 0; + init_from_config_clear(&config); + + // Tune the configuration using _PyInterpreterState_SetConfig() + if (tune_config() < 0) { + PyErr_Print(); + return 1; + } + + // Finish initialization: main part + PyStatus status = _Py_InitializeMain(); + if (PyStatus_Exception(status)) { + Py_ExitStatusException(status); + } + + dump_config(); + Py_Finalize(); + return 0; +} + + static void configure_init_main(PyConfig *config) { wchar_t* argv[] = { @@ -1693,6 +1742,7 @@ static struct TestCase TestCases[] = { {"test_init_setpath_config", test_init_setpath_config}, {"test_init_setpythonhome", test_init_setpythonhome}, {"test_init_warnoptions", test_init_warnoptions}, + {"test_init_set_config", test_set_config}, {"test_run_main", test_run_main}, {"test_get_argc_argv", test_get_argc_argv}, diff --git a/Python/initconfig.c b/Python/initconfig.c index 15fb3e4d287..de496ac7b52 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -242,8 +242,9 @@ PyStatus PyStatus_Ok(void) PyStatus PyStatus_Error(const char *err_msg) { + assert(err_msg != NULL); return (PyStatus){._type = _PyStatus_TYPE_ERROR, - .err_msg = err_msg}; + .err_msg = err_msg}; } PyStatus PyStatus_NoMemory(void) @@ -262,6 +263,23 @@ int PyStatus_IsExit(PyStatus status) int PyStatus_Exception(PyStatus status) { return _PyStatus_EXCEPTION(status); } +PyObject* +_PyErr_SetFromPyStatus(PyStatus status) +{ + if (!_PyStatus_IS_ERROR(status)) { + PyErr_Format(PyExc_SystemError, + "%s() expects an error PyStatus", + _PyStatus_GET_FUNC()); + } + else if (status.func) { + PyErr_Format(PyExc_ValueError, "%s: %s", status.func, status.err_msg); + } + else { + PyErr_Format(PyExc_ValueError, "%s", status.err_msg); + } + return NULL; +} + /* --- PyWideStringList ------------------------------------------------ */ diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 1f826d7f6c4..e34d6471e17 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -428,6 +428,67 @@ _Py_SetLocaleFromEnv(int category) } +static int +interpreter_set_config(const PyConfig *config) +{ + PyThreadState *tstate = PyThreadState_Get(); + + PyStatus status = _PyConfig_Write(config, tstate->interp->runtime); + if (_PyStatus_EXCEPTION(status)) { + _PyErr_SetFromPyStatus(status); + return -1; + } + + status = _PyConfig_Copy(&tstate->interp->config, config); + if (_PyStatus_EXCEPTION(status)) { + _PyErr_SetFromPyStatus(status); + return -1; + } + config = &tstate->interp->config; + + if (config->_install_importlib && _Py_IsMainInterpreter(tstate)) { + status = _PyConfig_WritePathConfig(config); + if (_PyStatus_EXCEPTION(status)) { + _PyErr_SetFromPyStatus(status); + return -1; + } + } + + // Update the sys module for the new configuration + if (_PySys_UpdateConfig(tstate) < 0) { + return -1; + } + return 0; +} + + +int +_PyInterpreterState_SetConfig(const PyConfig *src_config) +{ + int res = -1; + + PyConfig config; + PyConfig_InitPythonConfig(&config); + PyStatus status = _PyConfig_Copy(&config, src_config); + if (_PyStatus_EXCEPTION(status)) { + _PyErr_SetFromPyStatus(status); + goto done; + } + + status = PyConfig_Read(&config); + if (_PyStatus_EXCEPTION(status)) { + _PyErr_SetFromPyStatus(status); + goto done; + } + + res = interpreter_set_config(&config); + +done: + PyConfig_Clear(&config); + return res; +} + + /* Global initializations. Can be undone by Py_Finalize(). Don't call this twice without an intervening Py_Finalize() call. @@ -462,7 +523,7 @@ pyinit_core_reconfigure(_PyRuntimeState *runtime, return status; } - status = _PyInterpreterState_SetConfig(interp, config); + status = _PyConfig_Copy(&interp->config, config); if (_PyStatus_EXCEPTION(status)) { return status; } @@ -550,7 +611,7 @@ pycore_create_interpreter(_PyRuntimeState *runtime, return _PyStatus_ERR("can't make main interpreter"); } - PyStatus status = _PyInterpreterState_SetConfig(interp, config); + PyStatus status = _PyConfig_Copy(&interp->config, config); if (_PyStatus_EXCEPTION(status)) { return status; } @@ -917,7 +978,7 @@ pyinit_core(_PyRuntimeState *runtime, } PyConfig config; - _PyConfig_InitCompatConfig(&config); + PyConfig_InitPythonConfig(&config); status = _PyConfig_Copy(&config, src_config); if (_PyStatus_EXCEPTION(status)) { @@ -1835,7 +1896,8 @@ new_interpreter(PyThreadState **tstate_p, int isolated_subinterpreter) config = _PyInterpreterState_GetConfig(main_interp); } - status = _PyInterpreterState_SetConfig(interp, config); + + status = _PyConfig_Copy(&interp->config, config); if (_PyStatus_EXCEPTION(status)) { goto error; } diff --git a/Python/pystate.c b/Python/pystate.c index c9882a7f74b..600cc5e03a1 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -778,7 +778,7 @@ PyState_RemoveModule(struct PyModuleDef* def) return PyList_SetItem(interp->modules_by_index, index, Py_None); } -/* Used by PyImport_Cleanup() */ +// Used by finalize_modules() void _PyInterpreterState_ClearModules(PyInterpreterState *interp) { @@ -1920,11 +1920,17 @@ _PyInterpreterState_GetConfig(PyInterpreterState *interp) } -PyStatus -_PyInterpreterState_SetConfig(PyInterpreterState *interp, - const PyConfig *config) +int +_PyInterpreterState_GetConfigCopy(PyConfig *config) { - return _PyConfig_Copy(&interp->config, config); + PyInterpreterState *interp = PyInterpreterState_Get(); + + PyStatus status = _PyConfig_Copy(config, &interp->config); + if (PyStatus_Exception(status)) { + _PyErr_SetFromPyStatus(status); + return -1; + } + return 0; }