Skip to content

Commit 1c420e1

Browse files
gh-104108: Add the Py_mod_multiple_interpreters Module Def Slot (gh-104148)
I'll be adding a value to indicate support for per-interpreter GIL in gh-99114.
1 parent 55671fe commit 1c420e1

File tree

5 files changed

+122
-22
lines changed

5 files changed

+122
-22
lines changed

Include/moduleobject.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,16 @@ struct PyModuleDef_Slot {
7878

7979
#define Py_mod_create 1
8080
#define Py_mod_exec 2
81+
#define Py_mod_multiple_interpreters 3
8182

8283
#ifndef Py_LIMITED_API
83-
#define _Py_mod_LAST_SLOT 2
84+
#define _Py_mod_LAST_SLOT 3
8485
#endif
8586

87+
/* for Py_mod_multiple_interpreters: */
88+
#define Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED ((void *)0)
89+
#define Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED ((void *)1)
90+
8691
#endif /* New in 3.5 */
8792

8893
struct PyModuleDef {

Lib/test/test_import/__init__.py

+61-21
Original file line numberDiff line numberDiff line change
@@ -1652,26 +1652,44 @@ def pipe(self):
16521652
os.set_blocking(r, False)
16531653
return (r, w)
16541654

1655-
def import_script(self, name, fd, check_override=None):
1655+
def import_script(self, name, fd, filename=None, check_override=None):
16561656
override_text = ''
16571657
if check_override is not None:
16581658
override_text = f'''
1659-
import _imp
1660-
_imp._override_multi_interp_extensions_check({check_override})
1661-
'''
1662-
return textwrap.dedent(f'''
1663-
import os, sys
1664-
{override_text}
1665-
try:
1666-
import {name}
1667-
except ImportError as exc:
1668-
text = 'ImportError: ' + str(exc)
1669-
else:
1670-
text = 'okay'
1671-
os.write({fd}, text.encode('utf-8'))
1672-
''')
1659+
import _imp
1660+
_imp._override_multi_interp_extensions_check({check_override})
1661+
'''
1662+
if filename:
1663+
return textwrap.dedent(f'''
1664+
from importlib.util import spec_from_loader, module_from_spec
1665+
from importlib.machinery import ExtensionFileLoader
1666+
import os, sys
1667+
{override_text}
1668+
loader = ExtensionFileLoader({name!r}, {filename!r})
1669+
spec = spec_from_loader({name!r}, loader)
1670+
try:
1671+
module = module_from_spec(spec)
1672+
loader.exec_module(module)
1673+
except ImportError as exc:
1674+
text = 'ImportError: ' + str(exc)
1675+
else:
1676+
text = 'okay'
1677+
os.write({fd}, text.encode('utf-8'))
1678+
''')
1679+
else:
1680+
return textwrap.dedent(f'''
1681+
import os, sys
1682+
{override_text}
1683+
try:
1684+
import {name}
1685+
except ImportError as exc:
1686+
text = 'ImportError: ' + str(exc)
1687+
else:
1688+
text = 'okay'
1689+
os.write({fd}, text.encode('utf-8'))
1690+
''')
16731691

1674-
def run_here(self, name, *,
1692+
def run_here(self, name, filename=None, *,
16751693
check_singlephase_setting=False,
16761694
check_singlephase_override=None,
16771695
isolated=False,
@@ -1700,26 +1718,30 @@ def run_here(self, name, *,
17001718
)
17011719

17021720
r, w = self.pipe()
1703-
script = self.import_script(name, w, check_singlephase_override)
1721+
script = self.import_script(name, w, filename,
1722+
check_singlephase_override)
17041723

17051724
ret = run_in_subinterp_with_config(script, **kwargs)
17061725
self.assertEqual(ret, 0)
17071726
return os.read(r, 100)
17081727

1709-
def check_compatible_here(self, name, *, strict=False, isolated=False):
1728+
def check_compatible_here(self, name, filename=None, *,
1729+
strict=False,
1730+
isolated=False,
1731+
):
17101732
# Verify that the named module may be imported in a subinterpreter.
17111733
# (See run_here() for more info.)
1712-
out = self.run_here(name,
1734+
out = self.run_here(name, filename,
17131735
check_singlephase_setting=strict,
17141736
isolated=isolated,
17151737
)
17161738
self.assertEqual(out, b'okay')
17171739

1718-
def check_incompatible_here(self, name, *, isolated=False):
1740+
def check_incompatible_here(self, name, filename=None, *, isolated=False):
17191741
# Differences from check_compatible_here():
17201742
# * verify that import fails
17211743
# * "strict" is always True
1722-
out = self.run_here(name,
1744+
out = self.run_here(name, filename,
17231745
check_singlephase_setting=True,
17241746
isolated=isolated,
17251747
)
@@ -1820,6 +1842,24 @@ def test_multi_init_extension_compat(self):
18201842
with self.subTest(f'{module}: strict, fresh'):
18211843
self.check_compatible_fresh(module, strict=True)
18221844

1845+
@unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
1846+
def test_multi_init_extension_non_isolated_compat(self):
1847+
modname = '_test_non_isolated'
1848+
filename = _testmultiphase.__file__
1849+
loader = ExtensionFileLoader(modname, filename)
1850+
spec = importlib.util.spec_from_loader(modname, loader)
1851+
module = importlib.util.module_from_spec(spec)
1852+
loader.exec_module(module)
1853+
sys.modules[modname] = module
1854+
1855+
require_extension(module)
1856+
with self.subTest(f'{modname}: isolated'):
1857+
self.check_incompatible_here(modname, filename, isolated=True)
1858+
with self.subTest(f'{modname}: not isolated'):
1859+
self.check_incompatible_here(modname, filename, isolated=False)
1860+
with self.subTest(f'{modname}: not strict'):
1861+
self.check_compatible_here(modname, filename, strict=False)
1862+
18231863
def test_python_compat(self):
18241864
module = 'threading'
18251865
require_pure_python(module)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Multi-phase init extension modules may now indicate whether or not they
2+
actually support multiple interpreters. By default such modules are
3+
expected to support use in multiple interpreters. In the uncommon case that
4+
one does not, it may use the new ``Py_mod_multiple_interpreters`` module def
5+
slot. A value of ``0`` means the module does not support them. ``1`` means
6+
it does. The default is ``1``.

Modules/_testmultiphase.c

+19
Original file line numberDiff line numberDiff line change
@@ -884,3 +884,22 @@ PyInit__test_module_state_shared(void)
884884
}
885885
return module;
886886
}
887+
888+
889+
/* multiple interpreters supports */
890+
891+
static PyModuleDef_Slot non_isolated_slots[] = {
892+
{Py_mod_exec, execfunc},
893+
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
894+
{0, NULL},
895+
};
896+
897+
static PyModuleDef non_isolated_def = TEST_MODULE_DEF("_test_non_isolated",
898+
non_isolated_slots,
899+
testexport_methods);
900+
901+
PyMODINIT_FUNC
902+
PyInit__test_non_isolated(void)
903+
{
904+
return PyModuleDef_Init(&non_isolated_def);
905+
}

Objects/moduleobject.c

+30
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
245245
PyObject *(*create)(PyObject *, PyModuleDef*) = NULL;
246246
PyObject *nameobj;
247247
PyObject *m = NULL;
248+
int has_multiple_interpreters_slot = 0;
249+
void *multiple_interpreters = (void *)0;
248250
int has_execution_slots = 0;
249251
const char *name;
250252
int ret;
@@ -287,6 +289,17 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
287289
case Py_mod_exec:
288290
has_execution_slots = 1;
289291
break;
292+
case Py_mod_multiple_interpreters:
293+
if (has_multiple_interpreters_slot) {
294+
PyErr_Format(
295+
PyExc_SystemError,
296+
"module %s has more than one 'multiple interpreters' slots",
297+
name);
298+
goto error;
299+
}
300+
multiple_interpreters = cur_slot->value;
301+
has_multiple_interpreters_slot = 1;
302+
break;
290303
default:
291304
assert(cur_slot->slot < 0 || cur_slot->slot > _Py_mod_LAST_SLOT);
292305
PyErr_Format(
@@ -297,6 +310,20 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
297310
}
298311
}
299312

313+
/* By default, multi-phase init modules are expected
314+
to work under multiple interpreters. */
315+
if (!has_multiple_interpreters_slot) {
316+
multiple_interpreters = Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED;
317+
}
318+
if (multiple_interpreters == Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED) {
319+
PyInterpreterState *interp = _PyInterpreterState_GET();
320+
if (!_Py_IsMainInterpreter(interp)
321+
&& _PyImport_CheckSubinterpIncompatibleExtensionAllowed(name) < 0)
322+
{
323+
goto error;
324+
}
325+
}
326+
300327
if (create) {
301328
m = create(spec, def);
302329
if (m == NULL) {
@@ -421,6 +448,9 @@ PyModule_ExecDef(PyObject *module, PyModuleDef *def)
421448
return -1;
422449
}
423450
break;
451+
case Py_mod_multiple_interpreters:
452+
/* handled in PyModule_FromDefAndSpec2 */
453+
break;
424454
default:
425455
PyErr_Format(
426456
PyExc_SystemError,

0 commit comments

Comments
 (0)