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: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)
|
||||
|
||||
|
|
|
@ -409,6 +409,10 @@ typedef struct {
|
|||
|
||||
/* 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;
|
||||
} PyConfig;
|
||||
|
||||
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(char *) _Py_SetLocaleFromEnv(int category);
|
||||
|
||||
PyAPI_FUNC(PyThreadState *) _Py_NewInterpreter(int isolated_subinterpreter);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -794,6 +794,7 @@ class RunStringTests(TestBase):
|
|||
self.assertEqual(out, 'it worked!')
|
||||
|
||||
def test_create_thread(self):
|
||||
subinterp = interpreters.create(isolated=False)
|
||||
script, file = _captured_script("""
|
||||
import threading
|
||||
def f():
|
||||
|
@ -804,7 +805,7 @@ class RunStringTests(TestBase):
|
|||
t.join()
|
||||
""")
|
||||
with file:
|
||||
interpreters.run_string(self.id, script)
|
||||
interpreters.run_string(subinterp, script)
|
||||
out = file.read()
|
||||
|
||||
self.assertEqual(out, 'it worked!')
|
||||
|
|
|
@ -406,6 +406,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
|
|||
'check_hash_pycs_mode': 'default',
|
||||
'pathconfig_warnings': 1,
|
||||
'_init_main': 1,
|
||||
'_isolated_interpreter': 0,
|
||||
}
|
||||
if MS_WINDOWS:
|
||||
CONFIG_COMPAT.update({
|
||||
|
@ -766,6 +767,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
|
|||
|
||||
'check_hash_pycs_mode': 'always',
|
||||
'pathconfig_warnings': 0,
|
||||
|
||||
'_isolated_interpreter': 1,
|
||||
}
|
||||
self.check_all_configs("test_init_from_config", config, preconfig,
|
||||
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;
|
||||
}
|
||||
|
||||
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 */
|
||||
if (preexec_fn != Py_None) {
|
||||
PyObject *result;
|
||||
|
|
|
@ -1085,6 +1085,14 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
|
|||
"optional 3rd arg must be a dictionary");
|
||||
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);
|
||||
if (boot == NULL)
|
||||
return PyErr_NoMemory();
|
||||
|
|
|
@ -1080,6 +1080,14 @@ _winapi_CreateProcess_impl(PyObject *module,
|
|||
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));
|
||||
si.StartupInfo.cb = sizeof(si);
|
||||
|
||||
|
|
|
@ -1999,16 +1999,20 @@ _global_channels(void) {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Create and initialize the new interpreter.
|
||||
PyThreadState *save_tstate = PyThreadState_Swap(NULL);
|
||||
// XXX Possible GILState issues?
|
||||
PyThreadState *tstate = Py_NewInterpreter();
|
||||
PyThreadState *tstate = _Py_NewInterpreter(isolated);
|
||||
PyThreadState_Swap(save_tstate);
|
||||
if (tstate == NULL) {
|
||||
/* 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[] = {
|
||||
{"create", (PyCFunction)interp_create,
|
||||
METH_VARARGS, create_doc},
|
||||
{"create", (PyCFunction)(void(*)(void))interp_create,
|
||||
METH_VARARGS | METH_KEYWORDS, create_doc},
|
||||
{"destroy", (PyCFunction)(void(*)(void))interp_destroy,
|
||||
METH_VARARGS | METH_KEYWORDS, destroy_doc},
|
||||
{"list_all", interp_list_all,
|
||||
|
|
|
@ -6243,9 +6243,10 @@ os_fork_impl(PyObject *module)
|
|||
/*[clinic end generated code: output=3626c81f98985d49 input=13c956413110eeaa]*/
|
||||
{
|
||||
pid_t pid;
|
||||
|
||||
if (_PyInterpreterState_GET() != PyInterpreterState_Main()) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "fork not supported for subinterpreters");
|
||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||
if (interp->config._isolated_interpreter) {
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"fork not supported for isolated subinterpreters");
|
||||
return NULL;
|
||||
}
|
||||
if (PySys_Audit("os.fork", NULL) < 0) {
|
||||
|
|
|
@ -603,6 +603,8 @@ static int test_init_from_config(void)
|
|||
Py_FrozenFlag = 0;
|
||||
config.pathconfig_warnings = 0;
|
||||
|
||||
config._isolated_interpreter = 1;
|
||||
|
||||
init_from_config_clear(&config);
|
||||
|
||||
dump_config();
|
||||
|
|
|
@ -632,6 +632,7 @@ _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
|
||||
|
@ -850,6 +851,7 @@ _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);
|
||||
|
||||
#undef COPY_ATTR
|
||||
#undef COPY_WSTR_ATTR
|
||||
|
@ -949,6 +951,7 @@ config_as_dict(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);
|
||||
|
||||
return dict;
|
||||
|
||||
|
|
|
@ -1526,7 +1526,7 @@ Py_Finalize(void)
|
|||
*/
|
||||
|
||||
static PyStatus
|
||||
new_interpreter(PyThreadState **tstate_p)
|
||||
new_interpreter(PyThreadState **tstate_p, int isolated_subinterpreter)
|
||||
{
|
||||
PyStatus status;
|
||||
|
||||
|
@ -1573,6 +1573,7 @@ new_interpreter(PyThreadState **tstate_p)
|
|||
if (_PyStatus_EXCEPTION(status)) {
|
||||
goto error;
|
||||
}
|
||||
interp->config._isolated_interpreter = isolated_subinterpreter;
|
||||
|
||||
status = pycore_interp_init(tstate);
|
||||
if (_PyStatus_EXCEPTION(status)) {
|
||||
|
@ -1606,10 +1607,10 @@ error:
|
|||
}
|
||||
|
||||
PyThreadState *
|
||||
Py_NewInterpreter(void)
|
||||
_Py_NewInterpreter(int isolated_subinterpreter)
|
||||
{
|
||||
PyThreadState *tstate = NULL;
|
||||
PyStatus status = new_interpreter(&tstate);
|
||||
PyStatus status = new_interpreter(&tstate, isolated_subinterpreter);
|
||||
if (_PyStatus_EXCEPTION(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
|
||||
given thread state is current, that the thread has no remaining
|
||||
frames, and that it is its interpreter's only remaining thread.
|
||||
|
|
Loading…
Reference in New Issue