bpo-40453: Add PyConfig._isolated_subinterpreter (GH-19820)
An isolated subinterpreter cannot spawn threads, spawn a child process or call os.fork(). * Add private _Py_NewInterpreter(isolated_subinterpreter) function. * Add isolated=True keyword-only parameter to _xxsubinterpreters.create(). * Allow again os.fork() in "non-isolated" subinterpreters.
This commit is contained in:
parent
8bcfd31cc0
commit
252346acd9
|
@ -1004,6 +1004,8 @@ Private provisional API:
|
||||||
|
|
||||||
* :c:member:`PyConfig._init_main`: if set to 0,
|
* :c:member:`PyConfig._init_main`: if set to 0,
|
||||||
:c:func:`Py_InitializeFromConfig` stops at the "Core" initialization phase.
|
: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)
|
.. c:function:: PyStatus _Py_InitializeMain(void)
|
||||||
|
|
||||||
|
|
|
@ -409,6 +409,10 @@ typedef struct {
|
||||||
|
|
||||||
/* If equal to 0, stop Python initialization before the "main" phase */
|
/* If equal to 0, stop Python initialization before the "main" phase */
|
||||||
int _init_main;
|
int _init_main;
|
||||||
|
|
||||||
|
/* If non-zero, disallow threads, subprocesses, and fork.
|
||||||
|
Default: 0. */
|
||||||
|
int _isolated_interpreter;
|
||||||
} PyConfig;
|
} PyConfig;
|
||||||
|
|
||||||
PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config);
|
PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config);
|
||||||
|
|
|
@ -65,6 +65,8 @@ PyAPI_FUNC(int) _Py_CoerceLegacyLocale(int warn);
|
||||||
PyAPI_FUNC(int) _Py_LegacyLocaleDetected(int warn);
|
PyAPI_FUNC(int) _Py_LegacyLocaleDetected(int warn);
|
||||||
PyAPI_FUNC(char *) _Py_SetLocaleFromEnv(int category);
|
PyAPI_FUNC(char *) _Py_SetLocaleFromEnv(int category);
|
||||||
|
|
||||||
|
PyAPI_FUNC(PyThreadState *) _Py_NewInterpreter(int isolated_subinterpreter);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -794,6 +794,7 @@ class RunStringTests(TestBase):
|
||||||
self.assertEqual(out, 'it worked!')
|
self.assertEqual(out, 'it worked!')
|
||||||
|
|
||||||
def test_create_thread(self):
|
def test_create_thread(self):
|
||||||
|
subinterp = interpreters.create(isolated=False)
|
||||||
script, file = _captured_script("""
|
script, file = _captured_script("""
|
||||||
import threading
|
import threading
|
||||||
def f():
|
def f():
|
||||||
|
@ -804,7 +805,7 @@ class RunStringTests(TestBase):
|
||||||
t.join()
|
t.join()
|
||||||
""")
|
""")
|
||||||
with file:
|
with file:
|
||||||
interpreters.run_string(self.id, script)
|
interpreters.run_string(subinterp, script)
|
||||||
out = file.read()
|
out = file.read()
|
||||||
|
|
||||||
self.assertEqual(out, 'it worked!')
|
self.assertEqual(out, 'it worked!')
|
||||||
|
|
|
@ -406,6 +406,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
|
||||||
'check_hash_pycs_mode': 'default',
|
'check_hash_pycs_mode': 'default',
|
||||||
'pathconfig_warnings': 1,
|
'pathconfig_warnings': 1,
|
||||||
'_init_main': 1,
|
'_init_main': 1,
|
||||||
|
'_isolated_interpreter': 0,
|
||||||
}
|
}
|
||||||
if MS_WINDOWS:
|
if MS_WINDOWS:
|
||||||
CONFIG_COMPAT.update({
|
CONFIG_COMPAT.update({
|
||||||
|
@ -766,6 +767,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
|
||||||
|
|
||||||
'check_hash_pycs_mode': 'always',
|
'check_hash_pycs_mode': 'always',
|
||||||
'pathconfig_warnings': 0,
|
'pathconfig_warnings': 0,
|
||||||
|
|
||||||
|
'_isolated_interpreter': 1,
|
||||||
}
|
}
|
||||||
self.check_all_configs("test_init_from_config", config, preconfig,
|
self.check_all_configs("test_init_from_config", config, preconfig,
|
||||||
api=API_COMPAT)
|
api=API_COMPAT)
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Add ``isolated=True`` keyword-only parameter to
|
||||||
|
``_xxsubinterpreters.create()``. An isolated subinterpreter cannot spawn
|
||||||
|
threads, spawn a child process or call ``os.fork()``.
|
|
@ -663,6 +663,14 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyInterpreterState *interp = PyInterpreterState_Get();
|
||||||
|
const PyConfig *config = _PyInterpreterState_GetConfig(interp);
|
||||||
|
if (config->_isolated_interpreter) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError,
|
||||||
|
"subprocess not supported for isolated subinterpreters");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* We need to call gc.disable() when we'll be calling preexec_fn */
|
/* We need to call gc.disable() when we'll be calling preexec_fn */
|
||||||
if (preexec_fn != Py_None) {
|
if (preexec_fn != Py_None) {
|
||||||
PyObject *result;
|
PyObject *result;
|
||||||
|
|
|
@ -1085,6 +1085,14 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
|
||||||
"optional 3rd arg must be a dictionary");
|
"optional 3rd arg must be a dictionary");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
|
if (interp->config._isolated_interpreter) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError,
|
||||||
|
"thread is not supported for isolated subinterpreters");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
boot = PyMem_NEW(struct bootstate, 1);
|
boot = PyMem_NEW(struct bootstate, 1);
|
||||||
if (boot == NULL)
|
if (boot == NULL)
|
||||||
return PyErr_NoMemory();
|
return PyErr_NoMemory();
|
||||||
|
|
|
@ -1080,6 +1080,14 @@ _winapi_CreateProcess_impl(PyObject *module,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyInterpreterState *interp = PyInterpreterState_Get();
|
||||||
|
const PyConfig *config = _PyInterpreterState_GetConfig(interp);
|
||||||
|
if (config->_isolated_interpreter) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError,
|
||||||
|
"subprocess not supported for isolated subinterpreters");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
ZeroMemory(&si, sizeof(si));
|
ZeroMemory(&si, sizeof(si));
|
||||||
si.StartupInfo.cb = sizeof(si);
|
si.StartupInfo.cb = sizeof(si);
|
||||||
|
|
||||||
|
|
|
@ -1999,16 +1999,20 @@ _global_channels(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
interp_create(PyObject *self, PyObject *args)
|
interp_create(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
{
|
{
|
||||||
if (!PyArg_UnpackTuple(args, "create", 0, 0)) {
|
|
||||||
|
static char *kwlist[] = {"isolated", NULL};
|
||||||
|
int isolated = 1;
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|$i:create", kwlist,
|
||||||
|
&isolated)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and initialize the new interpreter.
|
// Create and initialize the new interpreter.
|
||||||
PyThreadState *save_tstate = PyThreadState_Swap(NULL);
|
PyThreadState *save_tstate = PyThreadState_Swap(NULL);
|
||||||
// XXX Possible GILState issues?
|
// XXX Possible GILState issues?
|
||||||
PyThreadState *tstate = Py_NewInterpreter();
|
PyThreadState *tstate = _Py_NewInterpreter(isolated);
|
||||||
PyThreadState_Swap(save_tstate);
|
PyThreadState_Swap(save_tstate);
|
||||||
if (tstate == NULL) {
|
if (tstate == NULL) {
|
||||||
/* Since no new thread state was created, there is no exception to
|
/* Since no new thread state was created, there is no exception to
|
||||||
|
@ -2547,8 +2551,8 @@ channel__channel_id(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyMethodDef module_functions[] = {
|
static PyMethodDef module_functions[] = {
|
||||||
{"create", (PyCFunction)interp_create,
|
{"create", (PyCFunction)(void(*)(void))interp_create,
|
||||||
METH_VARARGS, create_doc},
|
METH_VARARGS | METH_KEYWORDS, create_doc},
|
||||||
{"destroy", (PyCFunction)(void(*)(void))interp_destroy,
|
{"destroy", (PyCFunction)(void(*)(void))interp_destroy,
|
||||||
METH_VARARGS | METH_KEYWORDS, destroy_doc},
|
METH_VARARGS | METH_KEYWORDS, destroy_doc},
|
||||||
{"list_all", interp_list_all,
|
{"list_all", interp_list_all,
|
||||||
|
|
|
@ -6243,9 +6243,10 @@ os_fork_impl(PyObject *module)
|
||||||
/*[clinic end generated code: output=3626c81f98985d49 input=13c956413110eeaa]*/
|
/*[clinic end generated code: output=3626c81f98985d49 input=13c956413110eeaa]*/
|
||||||
{
|
{
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
if (_PyInterpreterState_GET() != PyInterpreterState_Main()) {
|
if (interp->config._isolated_interpreter) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, "fork not supported for subinterpreters");
|
PyErr_SetString(PyExc_RuntimeError,
|
||||||
|
"fork not supported for isolated subinterpreters");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (PySys_Audit("os.fork", NULL) < 0) {
|
if (PySys_Audit("os.fork", NULL) < 0) {
|
||||||
|
|
|
@ -603,6 +603,8 @@ static int test_init_from_config(void)
|
||||||
Py_FrozenFlag = 0;
|
Py_FrozenFlag = 0;
|
||||||
config.pathconfig_warnings = 0;
|
config.pathconfig_warnings = 0;
|
||||||
|
|
||||||
|
config._isolated_interpreter = 1;
|
||||||
|
|
||||||
init_from_config_clear(&config);
|
init_from_config_clear(&config);
|
||||||
|
|
||||||
dump_config();
|
dump_config();
|
||||||
|
|
|
@ -632,6 +632,7 @@ _PyConfig_InitCompatConfig(PyConfig *config)
|
||||||
config->check_hash_pycs_mode = NULL;
|
config->check_hash_pycs_mode = NULL;
|
||||||
config->pathconfig_warnings = -1;
|
config->pathconfig_warnings = -1;
|
||||||
config->_init_main = 1;
|
config->_init_main = 1;
|
||||||
|
config->_isolated_interpreter = 0;
|
||||||
#ifdef MS_WINDOWS
|
#ifdef MS_WINDOWS
|
||||||
config->legacy_windows_stdio = -1;
|
config->legacy_windows_stdio = -1;
|
||||||
#endif
|
#endif
|
||||||
|
@ -850,6 +851,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
|
||||||
COPY_WSTR_ATTR(check_hash_pycs_mode);
|
COPY_WSTR_ATTR(check_hash_pycs_mode);
|
||||||
COPY_ATTR(pathconfig_warnings);
|
COPY_ATTR(pathconfig_warnings);
|
||||||
COPY_ATTR(_init_main);
|
COPY_ATTR(_init_main);
|
||||||
|
COPY_ATTR(_isolated_interpreter);
|
||||||
|
|
||||||
#undef COPY_ATTR
|
#undef COPY_ATTR
|
||||||
#undef COPY_WSTR_ATTR
|
#undef COPY_WSTR_ATTR
|
||||||
|
@ -949,6 +951,7 @@ config_as_dict(const PyConfig *config)
|
||||||
SET_ITEM_WSTR(check_hash_pycs_mode);
|
SET_ITEM_WSTR(check_hash_pycs_mode);
|
||||||
SET_ITEM_INT(pathconfig_warnings);
|
SET_ITEM_INT(pathconfig_warnings);
|
||||||
SET_ITEM_INT(_init_main);
|
SET_ITEM_INT(_init_main);
|
||||||
|
SET_ITEM_INT(_isolated_interpreter);
|
||||||
|
|
||||||
return dict;
|
return dict;
|
||||||
|
|
||||||
|
|
|
@ -1526,7 +1526,7 @@ Py_Finalize(void)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static PyStatus
|
static PyStatus
|
||||||
new_interpreter(PyThreadState **tstate_p)
|
new_interpreter(PyThreadState **tstate_p, int isolated_subinterpreter)
|
||||||
{
|
{
|
||||||
PyStatus status;
|
PyStatus status;
|
||||||
|
|
||||||
|
@ -1573,6 +1573,7 @@ new_interpreter(PyThreadState **tstate_p)
|
||||||
if (_PyStatus_EXCEPTION(status)) {
|
if (_PyStatus_EXCEPTION(status)) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
interp->config._isolated_interpreter = isolated_subinterpreter;
|
||||||
|
|
||||||
status = pycore_interp_init(tstate);
|
status = pycore_interp_init(tstate);
|
||||||
if (_PyStatus_EXCEPTION(status)) {
|
if (_PyStatus_EXCEPTION(status)) {
|
||||||
|
@ -1606,10 +1607,10 @@ error:
|
||||||
}
|
}
|
||||||
|
|
||||||
PyThreadState *
|
PyThreadState *
|
||||||
Py_NewInterpreter(void)
|
_Py_NewInterpreter(int isolated_subinterpreter)
|
||||||
{
|
{
|
||||||
PyThreadState *tstate = NULL;
|
PyThreadState *tstate = NULL;
|
||||||
PyStatus status = new_interpreter(&tstate);
|
PyStatus status = new_interpreter(&tstate, isolated_subinterpreter);
|
||||||
if (_PyStatus_EXCEPTION(status)) {
|
if (_PyStatus_EXCEPTION(status)) {
|
||||||
Py_ExitStatusException(status);
|
Py_ExitStatusException(status);
|
||||||
}
|
}
|
||||||
|
@ -1617,6 +1618,12 @@ Py_NewInterpreter(void)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyThreadState *
|
||||||
|
Py_NewInterpreter(void)
|
||||||
|
{
|
||||||
|
return _Py_NewInterpreter(0);
|
||||||
|
}
|
||||||
|
|
||||||
/* Delete an interpreter and its last thread. This requires that the
|
/* Delete an interpreter and its last thread. This requires that the
|
||||||
given thread state is current, that the thread has no remaining
|
given thread state is current, that the thread has no remaining
|
||||||
frames, and that it is its interpreter's only remaining thread.
|
frames, and that it is its interpreter's only remaining thread.
|
||||||
|
|
Loading…
Reference in New Issue