Skip to content

Commit e30053b

Browse files
Add _PyInterpreterConfig.check_multi_interp_extensions and Py_RTFLAGS_MULTI_INTERP_EXTENSIONS.
1 parent 3040531 commit e30053b

File tree

8 files changed

+64
-42
lines changed

8 files changed

+64
-42
lines changed

Include/cpython/initconfig.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ typedef struct {
248248
int allow_exec;
249249
int allow_threads;
250250
int allow_daemon_threads;
251+
int check_multi_interp_extensions;
251252
} _PyInterpreterConfig;
252253

253254
#define _PyInterpreterConfig_INIT \
@@ -256,6 +257,7 @@ typedef struct {
256257
.allow_exec = 0, \
257258
.allow_threads = 1, \
258259
.allow_daemon_threads = 0, \
260+
.check_multi_interp_extensions = 1, \
259261
}
260262

261263
#define _PyInterpreterConfig_LEGACY_INIT \
@@ -264,6 +266,7 @@ typedef struct {
264266
.allow_exec = 1, \
265267
.allow_threads = 1, \
266268
.allow_daemon_threads = 1, \
269+
.check_multi_interp_extensions = 0, \
267270
}
268271

269272
/* --- Helper functions --------------------------------------- */

Include/cpython/pystate.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ is available in a given context. For example, forking the process
1111
might not be allowed in the current interpreter (i.e. os.fork() would fail).
1212
*/
1313

14+
/* Set if import should check a module for subinterpreter support. */
15+
#define Py_RTFLAGS_MULTI_INTERP_EXTENSIONS (1UL << 8)
16+
1417
/* Set if threads are allowed. */
1518
#define Py_RTFLAGS_THREADS (1UL << 10)
1619

Lib/test/test_capi.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,17 +1147,20 @@ def test_configured_settings(self):
11471147
"""
11481148
import json
11491149

1150+
EXTENSIONS = 1<<8
11501151
THREADS = 1<<10
11511152
DAEMON_THREADS = 1<<11
11521153
FORK = 1<<15
11531154
EXEC = 1<<16
11541155

1155-
features = ['fork', 'exec', 'threads', 'daemon_threads']
1156+
features = ['fork', 'exec', 'threads', 'daemon_threads', 'extensions']
11561157
kwlist = [f'allow_{n}' for n in features]
1158+
kwlist[-1] = 'check_multi_interp_extensions'
11571159
for config, expected in {
1158-
(True, True, True, True): FORK | EXEC | THREADS | DAEMON_THREADS,
1159-
(False, False, False, False): 0,
1160-
(False, False, True, False): THREADS,
1160+
(True, True, True, True, True):
1161+
FORK | EXEC | THREADS | DAEMON_THREADS | EXTENSIONS,
1162+
(False, False, False, False, False): 0,
1163+
(False, False, True, False, True): THREADS | EXTENSIONS,
11611164
}.items():
11621165
kwargs = dict(zip(kwlist, config))
11631166
expected = {

Lib/test/test_embed.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1654,13 +1654,15 @@ def test_init_use_frozen_modules(self):
16541654
api=API_PYTHON, env=env)
16551655

16561656
def test_init_main_interpreter_settings(self):
1657+
EXTENSIONS = 1<<8
16571658
THREADS = 1<<10
16581659
DAEMON_THREADS = 1<<11
16591660
FORK = 1<<15
16601661
EXEC = 1<<16
16611662
expected = {
16621663
# All optional features should be enabled.
1663-
'feature_flags': FORK | EXEC | THREADS | DAEMON_THREADS,
1664+
'feature_flags':
1665+
FORK | EXEC | THREADS | DAEMON_THREADS,
16641666
}
16651667
out, err = self.run_embedded_interpreter(
16661668
'test_init_main_interpreter_settings',

Lib/test/test_import/__init__.py

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1433,13 +1433,17 @@ def import_script(self, name, fd):
14331433
os.write({fd}, text.encode('utf-8'))
14341434
''')
14351435

1436-
def check_compatible_shared(self, name):
1436+
def check_compatible_shared(self, name, *, strict=False):
14371437
# Verify that the named module may be imported in a subinterpreter.
14381438
#
14391439
# The subinterpreter will be in the current process.
14401440
# The module will have already been imported in the main interpreter.
14411441
# Thus, for extension/builtin modules, the module definition will
14421442
# have been loaded already and cached globally.
1443+
#
1444+
# "strict" determines whether or not the interpreter will be
1445+
# configured to check for modules that are not compatible
1446+
# with use in multiple interpreters.
14431447

14441448
# This check should always pass for all modules if not strict.
14451449

@@ -1449,12 +1453,13 @@ def check_compatible_shared(self, name):
14491453
ret = run_in_subinterp_with_config(
14501454
self.import_script(name, w),
14511455
**self.RUN_KWARGS,
1456+
check_multi_interp_extensions=strict,
14521457
)
14531458
self.assertEqual(ret, 0)
14541459
out = os.read(r, 100)
14551460
self.assertEqual(out, b'okay')
14561461

1457-
def check_compatible_isolated(self, name):
1462+
def check_compatible_isolated(self, name, *, strict=False):
14581463
# Differences from check_compatible_shared():
14591464
# * subinterpreter in a new process
14601465
# * module has never been imported before in that process
@@ -1468,67 +1473,60 @@ def check_compatible_isolated(self, name):
14681473
ret = _testcapi.run_in_subinterp_with_config(
14691474
{self.import_script(name, "sys.stdout.fileno()")!r},
14701475
**{self.RUN_KWARGS},
1476+
check_multi_interp_extensions={strict},
14711477
)
14721478
assert ret == 0, ret
14731479
'''))
14741480
self.assertEqual(err, b'')
14751481
self.assertEqual(out, b'okay')
14761482

1477-
def check_incompatible_isolated(self, name):
1478-
# Differences from check_compatible_isolated():
1479-
# * verify that import fails
1480-
_, out, err = script_helper.assert_python_ok('-c', textwrap.dedent(f'''
1481-
import _testcapi, sys
1482-
assert {name!r} not in sys.modules, {name!r}
1483-
ret = _testcapi.run_in_subinterp_with_config(
1484-
{self.import_script(name, "sys.stdout.fileno()")!r},
1485-
**{self.RUN_KWARGS},
1486-
)
1487-
assert ret == 0, ret
1488-
'''))
1489-
self.assertEqual(err, b'')
1490-
self.assertEqual(
1491-
out.decode('utf-8'),
1492-
f'ImportError: module {name} does not support loading in subinterpreters',
1493-
)
1494-
14951483
def test_builtin_compat(self):
14961484
module = 'sys'
1497-
with self.subTest(f'{module}: shared'):
1498-
self.check_compatible_shared(module)
1485+
with self.subTest(f'{module}: not strict'):
1486+
self.check_compatible_shared(module, strict=False)
1487+
with self.subTest(f'{module}: strict, shared'):
1488+
self.check_compatible_shared(module, strict=True)
14991489

15001490
@cpython_only
15011491
def test_frozen_compat(self):
15021492
module = '_frozen_importlib'
15031493
if __import__(module).__spec__.origin != 'frozen':
15041494
raise unittest.SkipTest(f'{module} is unexpectedly not frozen')
1505-
with self.subTest(f'{module}: shared'):
1506-
self.check_compatible_shared(module)
1495+
with self.subTest(f'{module}: not strict'):
1496+
self.check_compatible_shared(module, strict=False)
1497+
with self.subTest(f'{module}: strict, shared'):
1498+
self.check_compatible_shared(module, strict=True)
15071499

1508-
@unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module")
1500+
@unittest.skipIf(_testsinglephase is None, "test requires _testsinglphase module")
15091501
def test_single_init_extension_compat(self):
15101502
module = '_testsinglephase'
1511-
with self.subTest(f'{module}: shared'):
1503+
with self.subTest(f'{module}: not strict'):
1504+
self.check_compatible_shared(module, strict=False)
1505+
with self.subTest(f'{module}: strict, shared'):
15121506
self.check_compatible_shared(module)
1513-
with self.subTest(f'{module}: isolated'):
1507+
with self.subTest(f'{module}: strict, isolated'):
15141508
self.check_compatible_isolated(module)
15151509

15161510
@unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
15171511
def test_multi_init_extension_compat(self):
15181512
module = '_testmultiphase'
1519-
with self.subTest(f'{module}: shared'):
1520-
self.check_compatible_shared(module)
1521-
with self.subTest(f'{module}: isolated'):
1522-
self.check_compatible_isolated(module)
1513+
with self.subTest(f'{module}: not strict'):
1514+
self.check_compatible_shared(module, strict=False)
1515+
with self.subTest(f'{module}: strict, shared'):
1516+
self.check_compatible_shared(module, strict=True)
1517+
with self.subTest(f'{module}: strict, isolated'):
1518+
self.check_compatible_isolated(module, strict=True)
15231519

15241520
def test_python_compat(self):
15251521
module = 'threading'
15261522
if __import__(module).__spec__.origin == 'frozen':
15271523
raise unittest.SkipTest(f'{module} is unexpectedly frozen')
1528-
with self.subTest(f'{module}: shared'):
1529-
self.check_compatible_shared(module)
1530-
with self.subTest(f'{module}: isolated'):
1531-
self.check_compatible_isolated(module)
1524+
with self.subTest(f'{module}: not strict'):
1525+
self.check_compatible_shared(module, strict=False)
1526+
with self.subTest(f'{module}: strict, shared'):
1527+
self.check_compatible_shared(module, strict=True)
1528+
with self.subTest(f'{module}: strict, isolated'):
1529+
self.check_compatible_isolated(module, strict=True)
15321530

15331531

15341532
if __name__ == '__main__':

Lib/test/test_threading.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,6 +1327,7 @@ def func():
13271327
allow_exec=True,
13281328
allow_threads={allowed},
13291329
allow_daemon_threads={daemon_allowed},
1330+
check_multi_interp_extensions=False,
13301331
)
13311332
""")
13321333
with test.support.SuppressCrashReport():

Modules/_testcapimodule.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3234,6 +3234,7 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
32343234
int allow_exec = -1;
32353235
int allow_threads = -1;
32363236
int allow_daemon_threads = -1;
3237+
int check_multi_interp_extensions = -1;
32373238
int r;
32383239
PyThreadState *substate, *mainstate;
32393240
/* only initialise 'cflags.cf_flags' to test backwards compatibility */
@@ -3244,11 +3245,13 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
32443245
"allow_exec",
32453246
"allow_threads",
32463247
"allow_daemon_threads",
3248+
"check_multi_interp_extensions",
32473249
NULL};
32483250
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
3249-
"s$pppp:run_in_subinterp_with_config", kwlist,
3251+
"s$ppppp:run_in_subinterp_with_config", kwlist,
32503252
&code, &allow_fork, &allow_exec,
3251-
&allow_threads, &allow_daemon_threads)) {
3253+
&allow_threads, &allow_daemon_threads,
3254+
&check_multi_interp_extensions)) {
32523255
return NULL;
32533256
}
32543257
if (allow_fork < 0) {
@@ -3267,6 +3270,10 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
32673270
PyErr_SetString(PyExc_ValueError, "missing allow_daemon_threads");
32683271
return NULL;
32693272
}
3273+
if (check_multi_interp_extensions < 0) {
3274+
PyErr_SetString(PyExc_ValueError, "missing check_multi_interp_extensions");
3275+
return NULL;
3276+
}
32703277

32713278
mainstate = PyThreadState_Get();
32723279

@@ -3277,6 +3284,7 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
32773284
.allow_exec = allow_exec,
32783285
.allow_threads = allow_threads,
32793286
.allow_daemon_threads = allow_daemon_threads,
3287+
.check_multi_interp_extensions = check_multi_interp_extensions,
32803288
};
32813289
substate = _Py_NewInterpreterFromConfig(&config);
32823290
if (substate == NULL) {

Python/pylifecycle.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,10 @@ init_interp_settings(PyInterpreterState *interp, const _PyInterpreterConfig *con
630630
if (config->allow_daemon_threads) {
631631
interp->feature_flags |= Py_RTFLAGS_DAEMON_THREADS;
632632
}
633+
634+
if (config->check_multi_interp_extensions) {
635+
interp->feature_flags |= Py_RTFLAGS_MULTI_INTERP_EXTENSIONS;
636+
}
633637
}
634638

635639

0 commit comments

Comments
 (0)