[Python-checkins] bpo-40453: Add PyConfig._isolated_subinterpreter (GH-19820)

Victor Stinner webhook-mailer at python.org
Fri May 1 05:33:52 EDT 2020


https://github.com/python/cpython/commit/252346acd937ddba4845331994b8ff4f90349625
commit: 252346acd937ddba4845331994b8ff4f90349625
branch: master
author: Victor Stinner <vstinner at python.org>
committer: GitHub <noreply at github.com>
date: 2020-05-01T11:33:44+02:00
summary:

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.

files:
A Misc/NEWS.d/next/Library/2020-04-30-22-04-58.bpo-40453.ggz7sl.rst
M Doc/c-api/init_config.rst
M Include/cpython/initconfig.h
M Include/cpython/pylifecycle.h
M Lib/test/test__xxsubinterpreters.py
M Lib/test/test_embed.py
M Modules/_posixsubprocess.c
M Modules/_threadmodule.c
M Modules/_winapi.c
M Modules/_xxsubinterpretersmodule.c
M Modules/posixmodule.c
M Programs/_testembed.c
M Python/initconfig.c
M Python/pylifecycle.c

diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst
index 49507c8bad3ed..fc82c3eb59024 100644
--- a/Doc/c-api/init_config.rst
+++ b/Doc/c-api/init_config.rst
@@ -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)
 
diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h
index 8326c235702bd..df93a5539d48b 100644
--- a/Include/cpython/initconfig.h
+++ b/Include/cpython/initconfig.h
@@ -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);
diff --git a/Include/cpython/pylifecycle.h b/Include/cpython/pylifecycle.h
index a01e9c94f12d7..eb523b82e182d 100644
--- a/Include/cpython/pylifecycle.h
+++ b/Include/cpython/pylifecycle.h
@@ -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
diff --git a/Lib/test/test__xxsubinterpreters.py b/Lib/test/test__xxsubinterpreters.py
index 80eff19152f15..e17bfde2c2f75 100644
--- a/Lib/test/test__xxsubinterpreters.py
+++ b/Lib/test/test__xxsubinterpreters.py
@@ -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():
@@ -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!')
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index 0bdfae1b7e387..3d60b2f330c62 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -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 @@ 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)
diff --git a/Misc/NEWS.d/next/Library/2020-04-30-22-04-58.bpo-40453.ggz7sl.rst b/Misc/NEWS.d/next/Library/2020-04-30-22-04-58.bpo-40453.ggz7sl.rst
new file mode 100644
index 0000000000000..f20c666d3e27f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-04-30-22-04-58.bpo-40453.ggz7sl.rst
@@ -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()``.
diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c
index 60dd78d92a4f5..add2962189b1c 100644
--- a/Modules/_posixsubprocess.c
+++ b/Modules/_posixsubprocess.c
@@ -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;
diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c
index b3d90b22c5a66..77baba4847897 100644
--- a/Modules/_threadmodule.c
+++ b/Modules/_threadmodule.c
@@ -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();
diff --git a/Modules/_winapi.c b/Modules/_winapi.c
index 1b28adb0b3983..e1672c478522e 100644
--- a/Modules/_winapi.c
+++ b/Modules/_winapi.c
@@ -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);
 
diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c
index 15e80559ec6f6..de11c090870f9 100644
--- a/Modules/_xxsubinterpretersmodule.c
+++ b/Modules/_xxsubinterpretersmodule.c
@@ -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,
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 3d3f6ac969926..0163b0757aefa 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -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) {
diff --git a/Programs/_testembed.c b/Programs/_testembed.c
index 2cf0d71b470bf..5c83678f650d0 100644
--- a/Programs/_testembed.c
+++ b/Programs/_testembed.c
@@ -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();
diff --git a/Python/initconfig.c b/Python/initconfig.c
index 58cca562f336d..185935c05fb28 100644
--- a/Python/initconfig.c
+++ b/Python/initconfig.c
@@ -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;
 
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index 7909cdbf5b772..5726a559cfcb7 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -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 @@ 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);
     }
@@ -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.



More information about the Python-checkins mailing list