Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we talked about, this is a good place to start. :)


#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 @@ -814,6 +814,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 @@ -824,7 +825,7 @@ def f():
t.join()
""")
with file:
interpreters.run_string(self.id, script)
interpreters.run_string(subinterp, script)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be worth having a test that assertRaises() when isolated is True. Likewise for the other restrictions.

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