Skip to content

Commit

Permalink
bpo-40453: Add PyConfig._isolated_subinterpreter (pythonGH-19820)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
vstinner authored May 1, 2020
1 parent 8bcfd31 commit 252346a
Show file tree
Hide file tree
Showing 14 changed files with 68 additions and 12 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 @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions Include/cpython/initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions Include/cpython/pylifecycle.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 2 additions & 1 deletion Lib/test/test__xxsubinterpreters.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,7 @@ def f():
self.assertEqual(out, 'it worked!')

def test_create_thread(self):
subinterp = interpreters.create(isolated=False)
script, file = _captured_script("""
import threading
def f():
Expand All @@ -804,7 +805,7 @@ def f():
t.join()
""")
with file:
interpreters.run_string(self.id, script)
interpreters.run_string(subinterp, script)
out = file.read()

self.assertEqual(out, 'it worked!')
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -766,6 +767,8 @@ 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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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()``.
8 changes: 8 additions & 0 deletions Modules/_posixsubprocess.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions Modules/_threadmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
8 changes: 8 additions & 0 deletions Modules/_winapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
14 changes: 9 additions & 5 deletions Modules/_xxsubinterpretersmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 4 additions & 3 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions Programs/_testembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
3 changes: 3 additions & 0 deletions Python/initconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down
13 changes: 10 additions & 3 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -1526,7 +1526,7 @@ Py_Finalize(void)
*/

static PyStatus
new_interpreter(PyThreadState **tstate_p)
new_interpreter(PyThreadState **tstate_p, int isolated_subinterpreter)
{
PyStatus status;

Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -1606,17 +1607,23 @@ new_interpreter(PyThreadState **tstate_p)
}

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);
}
return tstate;

}

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.
Expand Down

0 comments on commit 252346a

Please sign in to comment.