Skip to content

Commit 25f6ff5

Browse files
authored
gh-117649: Raise ImportError for unsupported modules in free-threaded build (#117651)
The free-threaded build does not currently support the combination of single-phase init modules and non-isolated subinterpreters. Ensure that `check_multi_interp_extensions` is always `True` for subinterpreters in the free-threaded build so that importing these modules raises an `ImportError`.
1 parent 39d381f commit 25f6ff5

File tree

9 files changed

+103
-32
lines changed

9 files changed

+103
-32
lines changed

Include/cpython/pylifecycle.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,23 @@ typedef struct {
6363
.gil = PyInterpreterConfig_OWN_GIL, \
6464
}
6565

66+
// gh-117649: The free-threaded build does not currently support single-phase
67+
// init extensions in subinterpreters. For now, we ensure that
68+
// `check_multi_interp_extensions` is always `1`, even in the legacy config.
69+
#ifdef Py_GIL_DISABLED
70+
# define _PyInterpreterConfig_LEGACY_CHECK_MULTI_INTERP_EXTENSIONS 1
71+
#else
72+
# define _PyInterpreterConfig_LEGACY_CHECK_MULTI_INTERP_EXTENSIONS 0
73+
#endif
74+
6675
#define _PyInterpreterConfig_LEGACY_INIT \
6776
{ \
6877
.use_main_obmalloc = 1, \
6978
.allow_fork = 1, \
7079
.allow_exec = 1, \
7180
.allow_threads = 1, \
7281
.allow_daemon_threads = 1, \
73-
.check_multi_interp_extensions = 0, \
82+
.check_multi_interp_extensions = _PyInterpreterConfig_LEGACY_CHECK_MULTI_INTERP_EXTENSIONS, \
7483
.gil = PyInterpreterConfig_SHARED_GIL, \
7584
}
7685

Lib/test/support/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,12 @@ def requires_gil_enabled(msg="needs the GIL enabled"):
842842
"""Decorator for skipping tests on the free-threaded build."""
843843
return unittest.skipIf(Py_GIL_DISABLED, msg)
844844

845+
def expected_failure_if_gil_disabled():
846+
"""Expect test failure if the GIL is disabled."""
847+
if Py_GIL_DISABLED:
848+
return unittest.expectedFailure
849+
return lambda test_case: test_case
850+
845851
if Py_GIL_DISABLED:
846852
_header = 'PHBBInP'
847853
else:

Lib/test/test_capi/test_misc.py

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
from test.support import threading_helper
2727
from test.support import warnings_helper
2828
from test.support import requires_limited_api
29+
from test.support import requires_gil_enabled, expected_failure_if_gil_disabled
30+
from test.support import Py_GIL_DISABLED
2931
from test.support.script_helper import assert_python_failure, assert_python_ok, run_python_until_end
3032
try:
3133
import _posixsubprocess
@@ -2023,15 +2025,30 @@ def test_configured_settings(self):
20232025
kwlist[-2] = 'check_multi_interp_extensions'
20242026
kwlist[-1] = 'own_gil'
20252027

2026-
# expected to work
2027-
for config, expected in {
2028+
expected_to_work = {
20282029
(True, True, True, True, True, True, True):
20292030
(ALL_FLAGS, True),
20302031
(True, False, False, False, False, False, False):
20312032
(OBMALLOC, False),
20322033
(False, False, False, True, False, True, False):
20332034
(THREADS | EXTENSIONS, False),
2034-
}.items():
2035+
}
2036+
2037+
expected_to_fail = {
2038+
(False, False, False, False, False, False, False),
2039+
}
2040+
2041+
# gh-117649: The free-threaded build does not currently allow
2042+
# setting check_multi_interp_extensions to False.
2043+
if Py_GIL_DISABLED:
2044+
for config in list(expected_to_work.keys()):
2045+
kwargs = dict(zip(kwlist, config))
2046+
if not kwargs['check_multi_interp_extensions']:
2047+
del expected_to_work[config]
2048+
expected_to_fail.add(config)
2049+
2050+
# expected to work
2051+
for config, expected in expected_to_work.items():
20352052
kwargs = dict(zip(kwlist, config))
20362053
exp_flags, exp_gil = expected
20372054
expected = {
@@ -2055,9 +2072,7 @@ def test_configured_settings(self):
20552072
self.assertEqual(settings, expected)
20562073

20572074
# expected to fail
2058-
for config in [
2059-
(False, False, False, False, False, False, False),
2060-
]:
2075+
for config in expected_to_fail:
20612076
kwargs = dict(zip(kwlist, config))
20622077
with self.subTest(config):
20632078
script = textwrap.dedent(f'''
@@ -2070,6 +2085,9 @@ def test_configured_settings(self):
20702085

20712086
@unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module")
20722087
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
2088+
# gh-117649: The free-threaded build does not currently allow overriding
2089+
# the check_multi_interp_extensions setting.
2090+
@expected_failure_if_gil_disabled()
20732091
def test_overridden_setting_extensions_subinterp_check(self):
20742092
"""
20752093
PyInterpreterConfig.check_multi_interp_extensions can be overridden
@@ -2165,6 +2183,9 @@ def test_mutate_exception(self):
21652183
self.assertFalse(hasattr(binascii.Error, "foobar"))
21662184

21672185
@unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
2186+
# gh-117649: The free-threaded build does not currently support sharing
2187+
# extension module state between interpreters.
2188+
@expected_failure_if_gil_disabled()
21682189
def test_module_state_shared_in_global(self):
21692190
"""
21702191
bpo-44050: Extension module state should be shared between interpreters
@@ -2223,7 +2244,7 @@ class InterpreterConfigTests(unittest.TestCase):
22232244
allow_exec=True,
22242245
allow_threads=True,
22252246
allow_daemon_threads=True,
2226-
check_multi_interp_extensions=False,
2247+
check_multi_interp_extensions=bool(Py_GIL_DISABLED),
22272248
gil='shared',
22282249
),
22292250
'empty': types.SimpleNamespace(
@@ -2386,6 +2407,8 @@ def test_interp_init(self):
23862407
check_multi_interp_extensions=False
23872408
),
23882409
]
2410+
if Py_GIL_DISABLED:
2411+
invalid.append(dict(check_multi_interp_extensions=False))
23892412
def match(config, override_cases):
23902413
ns = vars(config)
23912414
for overrides in override_cases:
@@ -2427,6 +2450,8 @@ def new_interp(config):
24272450
with self.subTest('main'):
24282451
expected = _interpreters.new_config('legacy')
24292452
expected.gil = 'own'
2453+
if Py_GIL_DISABLED:
2454+
expected.check_multi_interp_extensions = False
24302455
interpid, *_ = _interpreters.get_main()
24312456
config = _interpreters.get_config(interpid)
24322457
self.assert_ns_equal(config, expected)
@@ -2448,6 +2473,7 @@ def new_interp(config):
24482473
'empty',
24492474
use_main_obmalloc=True,
24502475
gil='shared',
2476+
check_multi_interp_extensions=bool(Py_GIL_DISABLED),
24512477
)
24522478
with new_interp(orig) as interpid:
24532479
config = _interpreters.get_config(interpid)

Lib/test/test_import/__init__.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
from test.support import os_helper
3131
from test.support import (
3232
STDLIB_DIR, swap_attr, swap_item, cpython_only, is_apple_mobile, is_emscripten,
33-
is_wasi, run_in_subinterp, run_in_subinterp_with_config, Py_TRACE_REFS)
33+
is_wasi, run_in_subinterp, run_in_subinterp_with_config, Py_TRACE_REFS,
34+
requires_gil_enabled, Py_GIL_DISABLED)
3435
from test.support.import_helper import (
3536
forget, make_legacy_pyc, unlink, unload, ready_to_import,
3637
DirsOnSysPath, CleanImport, import_module)
@@ -158,6 +159,9 @@ def meth(self, _meth=meth):
158159
finally:
159160
restore__testsinglephase()
160161
meth = cpython_only(meth)
162+
# gh-117649: free-threaded build does not currently support single-phase
163+
# init modules in subinterpreters.
164+
meth = requires_gil_enabled(meth)
161165
return unittest.skipIf(_testsinglephase is None,
162166
'test requires _testsinglephase module')(meth)
163167

@@ -1876,8 +1880,9 @@ def test_builtin_compat(self):
18761880
# since they still don't implement multi-phase init.
18771881
module = '_imp'
18781882
require_builtin(module)
1879-
with self.subTest(f'{module}: not strict'):
1880-
self.check_compatible_here(module, strict=False)
1883+
if not Py_GIL_DISABLED:
1884+
with self.subTest(f'{module}: not strict'):
1885+
self.check_compatible_here(module, strict=False)
18811886
with self.subTest(f'{module}: strict, not fresh'):
18821887
self.check_compatible_here(module, strict=True)
18831888

@@ -1888,8 +1893,9 @@ def test_frozen_compat(self):
18881893
require_frozen(module, skip=True)
18891894
if __import__(module).__spec__.origin != 'frozen':
18901895
raise unittest.SkipTest(f'{module} is unexpectedly not frozen')
1891-
with self.subTest(f'{module}: not strict'):
1892-
self.check_compatible_here(module, strict=False)
1896+
if not Py_GIL_DISABLED:
1897+
with self.subTest(f'{module}: not strict'):
1898+
self.check_compatible_here(module, strict=False)
18931899
with self.subTest(f'{module}: strict, not fresh'):
18941900
self.check_compatible_here(module, strict=True)
18951901

@@ -1908,8 +1914,9 @@ def test_single_init_extension_compat(self):
19081914
def test_multi_init_extension_compat(self):
19091915
module = '_testmultiphase'
19101916
require_extension(module)
1911-
with self.subTest(f'{module}: not strict'):
1912-
self.check_compatible_here(module, strict=False)
1917+
if not Py_GIL_DISABLED:
1918+
with self.subTest(f'{module}: not strict'):
1919+
self.check_compatible_here(module, strict=False)
19131920
with self.subTest(f'{module}: strict, not fresh'):
19141921
self.check_compatible_here(module, strict=True)
19151922
with self.subTest(f'{module}: strict, fresh'):
@@ -1930,8 +1937,9 @@ def test_multi_init_extension_non_isolated_compat(self):
19301937
self.check_incompatible_here(modname, filename, isolated=True)
19311938
with self.subTest(f'{modname}: not isolated'):
19321939
self.check_incompatible_here(modname, filename, isolated=False)
1933-
with self.subTest(f'{modname}: not strict'):
1934-
self.check_compatible_here(modname, filename, strict=False)
1940+
if not Py_GIL_DISABLED:
1941+
with self.subTest(f'{modname}: not strict'):
1942+
self.check_compatible_here(modname, filename, strict=False)
19351943

19361944
@unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
19371945
def test_multi_init_extension_per_interpreter_gil_compat(self):
@@ -1949,16 +1957,18 @@ def test_multi_init_extension_per_interpreter_gil_compat(self):
19491957
with self.subTest(f'{modname}: not isolated, strict'):
19501958
self.check_compatible_here(modname, filename,
19511959
strict=True, isolated=False)
1952-
with self.subTest(f'{modname}: not isolated, not strict'):
1953-
self.check_compatible_here(modname, filename,
1954-
strict=False, isolated=False)
1960+
if not Py_GIL_DISABLED:
1961+
with self.subTest(f'{modname}: not isolated, not strict'):
1962+
self.check_compatible_here(modname, filename,
1963+
strict=False, isolated=False)
19551964

19561965
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
19571966
def test_python_compat(self):
19581967
module = 'threading'
19591968
require_pure_python(module)
1960-
with self.subTest(f'{module}: not strict'):
1961-
self.check_compatible_here(module, strict=False)
1969+
if not Py_GIL_DISABLED:
1970+
with self.subTest(f'{module}: not strict'):
1971+
self.check_compatible_here(module, strict=False)
19621972
with self.subTest(f'{module}: strict, not fresh'):
19631973
self.check_compatible_here(module, strict=True)
19641974
with self.subTest(f'{module}: strict, fresh'):

Lib/test/test_importlib/test_util.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,9 @@ def ensure_destroyed():
682682
raise ImportError(excsnap.msg)
683683

684684
@unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module")
685+
# gh-117649: single-phase init modules are not currently supported in
686+
# subinterpreters in the free-threaded build
687+
@support.expected_failure_if_gil_disabled()
685688
def test_single_phase_init_module(self):
686689
script = textwrap.dedent('''
687690
from importlib.util import _incompatible_extension_module_restrictions
@@ -706,6 +709,7 @@ def test_single_phase_init_module(self):
706709
self.run_with_own_gil(script)
707710

708711
@unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
712+
@support.requires_gil_enabled("gh-117649: not supported in free-threaded build")
709713
def test_incomplete_multi_phase_init_module(self):
710714
# Apple extensions must be distributed as frameworks. This requires
711715
# a specialist loader.

Lib/test/test_interpreters/test_api.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from test.support import import_helper
1111
# Raise SkipTest if subinterpreters not supported.
1212
_interpreters = import_helper.import_module('_xxsubinterpreters')
13+
from test.support import Py_GIL_DISABLED
1314
from test.support import interpreters
1415
from test.support.interpreters import (
1516
InterpreterError, InterpreterNotFoundError, ExecutionFailed,
@@ -1162,7 +1163,7 @@ def test_new_config(self):
11621163
allow_exec=True,
11631164
allow_threads=True,
11641165
allow_daemon_threads=True,
1165-
check_multi_interp_extensions=False,
1166+
check_multi_interp_extensions=bool(Py_GIL_DISABLED),
11661167
gil='shared',
11671168
),
11681169
'empty': types.SimpleNamespace(
@@ -1361,6 +1362,7 @@ def test_create(self):
13611362
with self.subTest('custom'):
13621363
orig = _interpreters.new_config('empty')
13631364
orig.use_main_obmalloc = True
1365+
orig.check_multi_interp_extensions = bool(Py_GIL_DISABLED)
13641366
orig.gil = 'shared'
13651367
interpid = _interpreters.create(orig)
13661368
config = _interpreters.get_config(interpid)
@@ -1410,13 +1412,8 @@ def test_get_config(self):
14101412
with self.subTest('main'):
14111413
expected = _interpreters.new_config('legacy')
14121414
expected.gil = 'own'
1413-
interpid, *_ = _interpreters.get_main()
1414-
config = _interpreters.get_config(interpid)
1415-
self.assert_ns_equal(config, expected)
1416-
1417-
with self.subTest('main'):
1418-
expected = _interpreters.new_config('legacy')
1419-
expected.gil = 'own'
1415+
if Py_GIL_DISABLED:
1416+
expected.check_multi_interp_extensions = False
14201417
interpid, *_ = _interpreters.get_main()
14211418
config = _interpreters.get_config(interpid)
14221419
self.assert_ns_equal(config, expected)

Lib/test/test_threading.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,7 @@ def func():
15271527
{before_start}
15281528
t.start()
15291529
""")
1530+
check_multi_interp_extensions = bool(support.Py_GIL_DISABLED)
15301531
script = textwrap.dedent(f"""
15311532
import test.support
15321533
test.support.run_in_subinterp_with_config(
@@ -1536,7 +1537,7 @@ def func():
15361537
allow_exec=True,
15371538
allow_threads={allowed},
15381539
allow_daemon_threads={daemon_allowed},
1539-
check_multi_interp_extensions=False,
1540+
check_multi_interp_extensions={check_multi_interp_extensions},
15401541
own_gil=False,
15411542
)
15421543
""")

Python/import.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3696,9 +3696,16 @@ _imp__override_multi_interp_extensions_check_impl(PyObject *module,
36963696
"cannot be used in the main interpreter");
36973697
return NULL;
36983698
}
3699+
#ifdef Py_GIL_DISABLED
3700+
PyErr_SetString(PyExc_RuntimeError,
3701+
"_imp._override_multi_interp_extensions_check() "
3702+
"cannot be used in the free-threaded build");
3703+
return NULL;
3704+
#else
36993705
int oldvalue = OVERRIDE_MULTI_INTERP_EXTENSIONS_CHECK(interp);
37003706
OVERRIDE_MULTI_INTERP_EXTENSIONS_CHECK(interp) = override;
37013707
return PyLong_FromLong(oldvalue);
3708+
#endif
37023709
}
37033710

37043711
#ifdef HAVE_DYNAMIC_LOADING

Python/pylifecycle.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,15 @@ init_interp_settings(PyInterpreterState *interp,
559559
return _PyStatus_ERR("per-interpreter obmalloc does not support "
560560
"single-phase init extension modules");
561561
}
562+
#ifdef Py_GIL_DISABLED
563+
if (!_Py_IsMainInterpreter(interp) &&
564+
!config->check_multi_interp_extensions)
565+
{
566+
return _PyStatus_ERR("The free-threaded build does not support "
567+
"single-phase init extension modules in "
568+
"subinterpreters");
569+
}
570+
#endif
562571

563572
if (config->allow_fork) {
564573
interp->feature_flags |= Py_RTFLAGS_FORK;
@@ -647,8 +656,10 @@ pycore_create_interpreter(_PyRuntimeState *runtime,
647656
}
648657

649658
PyInterpreterConfig config = _PyInterpreterConfig_LEGACY_INIT;
650-
// The main interpreter always has its own GIL.
659+
// The main interpreter always has its own GIL and supports single-phase
660+
// init extensions.
651661
config.gil = PyInterpreterConfig_OWN_GIL;
662+
config.check_multi_interp_extensions = 0;
652663
status = init_interp_settings(interp, &config);
653664
if (_PyStatus_EXCEPTION(status)) {
654665
return status;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy