Skip to content

Commit

Permalink
bpo-42260: Add _PyInterpreterState_SetConfig() (GH-23158)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
vstinner committed Nov 4, 2020
1 parent 100964e commit 048a356
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 16 deletions.
2 changes: 2 additions & 0 deletions Doc/c-api/init_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
30 changes: 30 additions & 0 deletions Include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
6 changes: 0 additions & 6 deletions Include/internal/pycore_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);


Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -1394,6 +1394,15 @@ def test_init_warnoptions(self):
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
Expand Down
50 changes: 50 additions & 0 deletions Programs/_testembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = {
Expand Down Expand Up @@ -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},

Expand Down
20 changes: 19 additions & 1 deletion Python/initconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 ------------------------------------------------ */

Expand Down
70 changes: 66 additions & 4 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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;
}
Expand Down
16 changes: 11 additions & 5 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
}


Expand Down

0 comments on commit 048a356

Please sign in to comment.