* :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)
// 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;
-
// If non-zero, we believe we're running from a source tree.
int _is_python_build;
} PyConfig;
Py_ssize_t length, wchar_t **items);
+/* --- PyInterpreterConfig ------------------------------------ */
+
+typedef struct {
+ int allow_fork;
+ int allow_subprocess;
+ int allow_threads;
+} _PyInterpreterConfig;
+
+#define _PyInterpreterConfig_LEGACY_INIT \
+ { \
+ .allow_fork = 1, \
+ .allow_subprocess = 1, \
+ .allow_threads = 1, \
+ }
+
/* --- Helper functions --------------------------------------- */
/* Get the original command line arguments, before Python modified them.
PyAPI_FUNC(int) _Py_LegacyLocaleDetected(int warn);
PyAPI_FUNC(char *) _Py_SetLocaleFromEnv(int category);
-PyAPI_FUNC(PyThreadState *) _Py_NewInterpreter(int isolated_subinterpreter);
+PyAPI_FUNC(PyThreadState *) _Py_NewInterpreterFromConfig(
+ const _PyInterpreterConfig *);
#endif
+/*
+Runtime Feature Flags
+
+Each flag indicate whether or not a specific runtime feature
+is available in a given context. For example, forking the process
+might not be allowed in the current interpreter (i.e. os.fork() would fail).
+*/
+
+// We leave the first 10 for less-specific features.
+
+/* Set if threads are allowed. */
+#define Py_RTFLAGS_THREADS (1UL << 10)
+
+/* Set if os.fork() is allowed. */
+#define Py_RTFLAGS_FORK (1UL << 15)
+
+/* Set if subprocesses are allowed. */
+#define Py_RTFLAGS_SUBPROCESS (1UL << 16)
+
+
+PyAPI_FUNC(int) _PyInterpreterState_HasFeature(PyInterpreterState *interp,
+ unsigned long feature);
+
+
+/* private interpreter helpers */
+
PyAPI_FUNC(int) _PyInterpreterState_RequiresIDRef(PyInterpreterState *);
PyAPI_FUNC(void) _PyInterpreterState_RequireIDRef(PyInterpreterState *, int);
PyAPI_FUNC(PyObject *) _PyInterpreterState_GetMainModule(PyInterpreterState *);
+
/* State unique per thread */
/* Py_tracefunc return -1 when raising an exception, or 0 for success. */
#ifdef HAVE_DLOPEN
int dlopenflags;
#endif
+ unsigned long feature_flags;
PyObject *dict; /* Stores per-interpreter state */
'skip_source_first_line',
'_install_importlib',
'_init_main',
- '_isolated_interpreter',
]
if MS_WINDOWS:
options.append('legacy_windows_stdio')
Run code in a subinterpreter. Raise unittest.SkipTest if the tracemalloc
module is enabled.
"""
+ _check_tracemalloc()
+ import _testcapi
+ return _testcapi.run_in_subinterp(code)
+
+
+def run_in_subinterp_with_config(code, **config):
+ """
+ Run code in a subinterpreter. Raise unittest.SkipTest if the tracemalloc
+ module is enabled.
+ """
+ _check_tracemalloc()
+ import _testcapi
+ return _testcapi.run_in_subinterp_with_config(code, **config)
+
+
+def _check_tracemalloc():
# Issue #10915, #15751: PyGILState_*() functions don't work with
# sub-interpreters, the tracemalloc module uses these functions internally
try:
raise unittest.SkipTest("run_in_subinterp() cannot be used "
"if tracemalloc module is tracing "
"memory allocations")
- import _testcapi
- return _testcapi.run_in_subinterp(code)
def check_free_after_iterating(test, iter, cls, args=()):
# test fails, assume that the environment in this process may
# be altered and suspect.
+ @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
+ def test_configured_settings(self):
+ """
+ The config with which an interpreter is created corresponds
+ 1-to-1 with the new interpreter's settings. This test verifies
+ that they match.
+ """
+ import json
+
+ THREADS = 1<<10
+ FORK = 1<<15
+ SUBPROCESS = 1<<16
+
+ features = ['fork', 'subprocess', 'threads']
+ kwlist = [f'allow_{n}' for n in features]
+ for config, expected in {
+ (True, True, True): FORK | SUBPROCESS | THREADS,
+ (False, False, False): 0,
+ (False, True, True): SUBPROCESS | THREADS,
+ }.items():
+ kwargs = dict(zip(kwlist, config))
+ expected = {
+ 'feature_flags': expected,
+ }
+ with self.subTest(config):
+ r, w = os.pipe()
+ script = textwrap.dedent(f'''
+ import _testinternalcapi, json, os
+ settings = _testinternalcapi.get_interp_settings()
+ with os.fdopen({w}, "w") as stdin:
+ json.dump(settings, stdin)
+ ''')
+ with os.fdopen(r) as stdout:
+ support.run_in_subinterp_with_config(script, **kwargs)
+ out = stdout.read()
+ settings = json.loads(out)
+
+ self.assertEqual(settings, expected)
+
def test_mutate_exception(self):
"""
Exceptions saved in global module state get shared between
'check_hash_pycs_mode': 'default',
'pathconfig_warnings': 1,
'_init_main': 1,
- '_isolated_interpreter': 0,
'use_frozen_modules': not support.Py_DEBUG,
'safe_path': 0,
'_is_python_build': IGNORE_CONFIG,
'check_hash_pycs_mode': 'always',
'pathconfig_warnings': 0,
-
- '_isolated_interpreter': 1,
}
self.check_all_configs("test_init_from_config", config, preconfig,
api=API_COMPAT)
self.check_all_configs("test_init_use_frozen_modules", config,
api=API_PYTHON, env=env)
+ def test_init_main_interpreter_settings(self):
+ THREADS = 1<<10
+ FORK = 1<<15
+ SUBPROCESS = 1<<16
+ expected = {
+ # All optional features should be enabled.
+ 'feature_flags': THREADS | FORK | SUBPROCESS,
+ }
+ out, err = self.run_embedded_interpreter(
+ 'test_init_main_interpreter_settings',
+ )
+ self.assertEqual(err, '')
+ try:
+ out = json.loads(out)
+ except json.JSONDecodeError:
+ self.fail(f'fail to decode stdout: {out!r}')
+
+ self.assertEqual(out, expected)
+
class SetConfigTests(unittest.TestCase):
def test_set_config(self):
--- /dev/null
+A ``_PyInterpreterConfig`` has been added and ``_Py_NewInterpreter()`` has
+been renamed to ``_Py_NewInterpreterFromConfig()``. The
+"isolated_subinterpreters" argument is now a granular config that captures
+the previous behavior. Note that this is all "private" API.
}
PyInterpreterState *interp = PyInterpreterState_Get();
- const PyConfig *config = _PyInterpreterState_GetConfig(interp);
- if (config->_isolated_interpreter) {
+ if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_SUBPROCESS)) {
PyErr_SetString(PyExc_RuntimeError,
"subprocess not supported for isolated subinterpreters");
return NULL;
return PyLong_FromLong(r);
}
+/* To run some code in a sub-interpreter. */
+static PyObject *
+run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char *code;
+ int allow_fork = -1;
+ int allow_subprocess = -1;
+ int allow_threads = -1;
+ int r;
+ PyThreadState *substate, *mainstate;
+ /* only initialise 'cflags.cf_flags' to test backwards compatibility */
+ PyCompilerFlags cflags = {0};
+
+ static char *kwlist[] = {"code",
+ "allow_fork", "allow_subprocess", "allow_threads",
+ NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+ "s$ppp:run_in_subinterp_with_config", kwlist,
+ &code, &allow_fork, &allow_subprocess, &allow_threads)) {
+ return NULL;
+ }
+ if (allow_fork < 0) {
+ PyErr_SetString(PyExc_ValueError, "missing allow_fork");
+ return NULL;
+ }
+ if (allow_subprocess < 0) {
+ PyErr_SetString(PyExc_ValueError, "missing allow_subprocess");
+ return NULL;
+ }
+ if (allow_threads < 0) {
+ PyErr_SetString(PyExc_ValueError, "missing allow_threads");
+ return NULL;
+ }
+
+ mainstate = PyThreadState_Get();
+
+ PyThreadState_Swap(NULL);
+
+ const _PyInterpreterConfig config = {
+ .allow_fork = allow_fork,
+ .allow_subprocess = allow_subprocess,
+ .allow_threads = allow_threads,
+ };
+ substate = _Py_NewInterpreterFromConfig(&config);
+ if (substate == NULL) {
+ /* Since no new thread state was created, there is no exception to
+ propagate; raise a fresh one after swapping in the old thread
+ state. */
+ PyThreadState_Swap(mainstate);
+ PyErr_SetString(PyExc_RuntimeError, "sub-interpreter creation failed");
+ return NULL;
+ }
+ r = PyRun_SimpleStringFlags(code, &cflags);
+ Py_EndInterpreter(substate);
+
+ PyThreadState_Swap(mainstate);
+
+ return PyLong_FromLong(r);
+}
+
static int
check_time_rounding(int round)
{
METH_NOARGS},
{"crash_no_current_thread", crash_no_current_thread, METH_NOARGS},
{"run_in_subinterp", run_in_subinterp, METH_VARARGS},
+ {"run_in_subinterp_with_config",
+ _PyCFunction_CAST(run_in_subinterp_with_config),
+ METH_VARARGS | METH_KEYWORDS},
{"pytime_object_to_time_t", test_pytime_object_to_time_t, METH_VARARGS},
{"pytime_object_to_timeval", test_pytime_object_to_timeval, METH_VARARGS},
{"pytime_object_to_timespec", test_pytime_object_to_timespec, METH_VARARGS},
}
+static PyObject *
+get_interp_settings(PyObject *self, PyObject *args)
+{
+ int interpid = -1;
+ if (!PyArg_ParseTuple(args, "|i:get_interp_settings", &interpid)) {
+ return NULL;
+ }
+
+ PyInterpreterState *interp = NULL;
+ if (interpid < 0) {
+ PyThreadState *tstate = _PyThreadState_GET();
+ interp = tstate ? tstate->interp : _PyInterpreterState_Main();
+ }
+ else if (interpid == 0) {
+ interp = _PyInterpreterState_Main();
+ }
+ else {
+ PyErr_Format(PyExc_NotImplementedError,
+ "%zd", interpid);
+ return NULL;
+ }
+ assert(interp != NULL);
+
+ PyObject *settings = PyDict_New();
+ if (settings == NULL) {
+ return NULL;
+ }
+
+ /* Add the feature flags. */
+ PyObject *flags = PyLong_FromUnsignedLong(interp->feature_flags);
+ if (flags == NULL) {
+ Py_DECREF(settings);
+ return NULL;
+ }
+ int res = PyDict_SetItemString(settings, "feature_flags", flags);
+ Py_DECREF(flags);
+ if (res != 0) {
+ Py_DECREF(settings);
+ return NULL;
+ }
+
+ return settings;
+}
+
+
static PyMethodDef TestMethods[] = {
{"get_configs", get_configs, METH_NOARGS},
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
{"set_eval_frame_default", set_eval_frame_default, METH_NOARGS, NULL},
{"set_eval_frame_record", set_eval_frame_record, METH_O, NULL},
_TESTINTERNALCAPI_OPTIMIZE_CFG_METHODDEF
+ {"get_interp_settings", get_interp_settings, METH_VARARGS, NULL},
{NULL, NULL} /* sentinel */
};
}
PyInterpreterState *interp = _PyInterpreterState_GET();
- if (interp->config._isolated_interpreter) {
+ if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_THREADS)) {
PyErr_SetString(PyExc_RuntimeError,
"thread is not supported for isolated subinterpreters");
return NULL;
}
PyInterpreterState *interp = PyInterpreterState_Get();
- const PyConfig *config = _PyInterpreterState_GetConfig(interp);
- if (config->_isolated_interpreter) {
+ if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_SUBPROCESS)) {
PyErr_SetString(PyExc_RuntimeError,
"subprocess not supported for isolated subinterpreters");
return NULL;
// Create and initialize the new interpreter.
PyThreadState *save_tstate = _PyThreadState_GET();
+ const _PyInterpreterConfig config = {
+ .allow_fork = !isolated,
+ .allow_subprocess = !isolated,
+ .allow_threads = !isolated,
+ };
// XXX Possible GILState issues?
- PyThreadState *tstate = _Py_NewInterpreter(isolated);
+ PyThreadState *tstate = _Py_NewInterpreterFromConfig(&config);
PyThreadState_Swap(save_tstate);
if (tstate == NULL) {
/* Since no new thread state was created, there is no exception to
{
pid_t pid;
PyInterpreterState *interp = _PyInterpreterState_GET();
- if (interp->config._isolated_interpreter) {
+ if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_FORK)) {
PyErr_SetString(PyExc_RuntimeError,
"fork not supported for isolated subinterpreters");
return NULL;
config.safe_path = 1;
- config._isolated_interpreter = 1;
-
putenv("PYTHONINTMAXSTRDIGITS=6666");
config.int_max_str_digits = 31337;
}
+static int test_init_main_interpreter_settings(void)
+{
+ _testembed_Py_Initialize();
+ (void) PyRun_SimpleStringFlags(
+ "import _testinternalcapi, json; "
+ "print(json.dumps(_testinternalcapi.get_interp_settings(0)))",
+ 0);
+ Py_Finalize();
+ return 0;
+}
+
+
#ifndef MS_WINDOWS
#include "test_frozenmain.h" // M_test_frozenmain
{"test_run_main_loop", test_run_main_loop},
{"test_get_argc_argv", test_get_argc_argv},
{"test_init_use_frozen_modules", test_init_use_frozen_modules},
+ {"test_init_main_interpreter_settings", test_init_main_interpreter_settings},
// Audit
{"test_open_code_hook", test_open_code_hook},
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
COPY_WSTR_ATTR(check_hash_pycs_mode);
COPY_ATTR(pathconfig_warnings);
COPY_ATTR(_init_main);
- COPY_ATTR(_isolated_interpreter);
COPY_ATTR(use_frozen_modules);
COPY_ATTR(safe_path);
COPY_WSTRLIST(orig_argv);
SET_ITEM_WSTR(check_hash_pycs_mode);
SET_ITEM_INT(pathconfig_warnings);
SET_ITEM_INT(_init_main);
- SET_ITEM_INT(_isolated_interpreter);
SET_ITEM_WSTRLIST(orig_argv);
SET_ITEM_INT(use_frozen_modules);
SET_ITEM_INT(safe_path);
GET_UINT(_install_importlib);
GET_UINT(_init_main);
- GET_UINT(_isolated_interpreter);
GET_UINT(use_frozen_modules);
GET_UINT(safe_path);
GET_UINT(_is_python_build);
}
+static void
+init_interp_settings(PyInterpreterState *interp, const _PyInterpreterConfig *config)
+{
+ assert(interp->feature_flags == 0);
+ if (config->allow_fork) {
+ interp->feature_flags |= Py_RTFLAGS_FORK;
+ }
+ if (config->allow_subprocess) {
+ interp->feature_flags |= Py_RTFLAGS_SUBPROCESS;
+ }
+ if (config->allow_threads) {
+ interp->feature_flags |= Py_RTFLAGS_THREADS;
+ }
+}
+
+
static PyStatus
init_interp_create_gil(PyThreadState *tstate)
{
static PyStatus
pycore_create_interpreter(_PyRuntimeState *runtime,
- const PyConfig *config,
+ const PyConfig *src_config,
PyThreadState **tstate_p)
{
/* Auto-thread-state API */
}
assert(_Py_IsMainInterpreter(interp));
- status = _PyConfig_Copy(&interp->config, config);
+ status = _PyConfig_Copy(&interp->config, src_config);
if (_PyStatus_EXCEPTION(status)) {
return status;
}
+ const _PyInterpreterConfig config = _PyInterpreterConfig_LEGACY_INIT;
+ init_interp_settings(interp, &config);
+
PyThreadState *tstate = PyThreadState_New(interp);
if (tstate == NULL) {
return _PyStatus_ERR("can't make first thread");
*/
static PyStatus
-new_interpreter(PyThreadState **tstate_p, int isolated_subinterpreter)
+new_interpreter(PyThreadState **tstate_p, const _PyInterpreterConfig *config)
{
PyStatus status;
PyThreadState *save_tstate = PyThreadState_Swap(tstate);
/* Copy the current interpreter config into the new interpreter */
- const PyConfig *config;
+ const PyConfig *src_config;
if (save_tstate != NULL) {
- config = _PyInterpreterState_GetConfig(save_tstate->interp);
+ src_config = _PyInterpreterState_GetConfig(save_tstate->interp);
}
else
{
/* No current thread state, copy from the main interpreter */
PyInterpreterState *main_interp = _PyInterpreterState_Main();
- config = _PyInterpreterState_GetConfig(main_interp);
+ src_config = _PyInterpreterState_GetConfig(main_interp);
}
-
- status = _PyConfig_Copy(&interp->config, config);
+ status = _PyConfig_Copy(&interp->config, src_config);
if (_PyStatus_EXCEPTION(status)) {
goto error;
}
- interp->config._isolated_interpreter = isolated_subinterpreter;
+
+ init_interp_settings(interp, config);
status = init_interp_create_gil(tstate);
if (_PyStatus_EXCEPTION(status)) {
}
PyThreadState *
-_Py_NewInterpreter(int isolated_subinterpreter)
+_Py_NewInterpreterFromConfig(const _PyInterpreterConfig *config)
{
PyThreadState *tstate = NULL;
- PyStatus status = new_interpreter(&tstate, isolated_subinterpreter);
+ PyStatus status = new_interpreter(&tstate, config);
if (_PyStatus_EXCEPTION(status)) {
Py_ExitStatusException(status);
}
return tstate;
-
}
PyThreadState *
Py_NewInterpreter(void)
{
- return _Py_NewInterpreter(0);
+ const _PyInterpreterConfig config = _PyInterpreterConfig_LEGACY_INIT;
+ return _Py_NewInterpreterFromConfig(&config);
}
/* Delete an interpreter and its last thread. This requires that the
return _PyInterpreterState_GetConfig(tstate->interp);
}
+
+int
+_PyInterpreterState_HasFeature(PyInterpreterState *interp, unsigned long feature)
+{
+ return ((interp->feature_flags & feature) != 0);
+}
+
+
#define MINIMUM_OVERHEAD 1000
static PyObject **