[Python-checkins] gh-104108: Add the Py_mod_multiple_interpreters Module Def Slot (gh-104148)

ericsnowcurrently webhook-mailer at python.org
Fri May 5 16:05:02 EDT 2023


https://github.com/python/cpython/commit/1c420e138fd828895b6bd3c44ef99156e8796095
commit: 1c420e138fd828895b6bd3c44ef99156e8796095
branch: main
author: Eric Snow <ericsnowcurrently at gmail.com>
committer: ericsnowcurrently <ericsnowcurrently at gmail.com>
date: 2023-05-05T14:04:55-06:00
summary:

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.

files:
A Misc/NEWS.d/next/Core and Builtins/2023-05-03-17-46-47.gh-issue-104108.GOxAYt.rst
M Include/moduleobject.h
M Lib/test/test_import/__init__.py
M Modules/_testmultiphase.c
M Objects/moduleobject.c

diff --git a/Include/moduleobject.h b/Include/moduleobject.h
index 555564ec73b4..7ac6f6e8a4a2 100644
--- a/Include/moduleobject.h
+++ b/Include/moduleobject.h
@@ -78,11 +78,16 @@ struct PyModuleDef_Slot {
 
 #define Py_mod_create 1
 #define Py_mod_exec 2
+#define Py_mod_multiple_interpreters 3
 
 #ifndef Py_LIMITED_API
-#define _Py_mod_LAST_SLOT 2
+#define _Py_mod_LAST_SLOT 3
 #endif
 
+/* for Py_mod_multiple_interpreters: */
+#define Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED ((void *)0)
+#define Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED ((void *)1)
+
 #endif /* New in 3.5 */
 
 struct PyModuleDef {
diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
index 41dfdaabe246..9211639b016e 100644
--- a/Lib/test/test_import/__init__.py
+++ b/Lib/test/test_import/__init__.py
@@ -1652,26 +1652,44 @@ def pipe(self):
             os.set_blocking(r, False)
         return (r, w)
 
-    def import_script(self, name, fd, check_override=None):
+    def import_script(self, name, fd, filename=None, check_override=None):
         override_text = ''
         if check_override is not None:
             override_text = f'''
-            import _imp
-            _imp._override_multi_interp_extensions_check({check_override})
-            '''
-        return textwrap.dedent(f'''
-            import os, sys
-            {override_text}
-            try:
-                import {name}
-            except ImportError as exc:
-                text = 'ImportError: ' + str(exc)
-            else:
-                text = 'okay'
-            os.write({fd}, text.encode('utf-8'))
-            ''')
+                import _imp
+                _imp._override_multi_interp_extensions_check({check_override})
+                '''
+        if filename:
+            return textwrap.dedent(f'''
+                from importlib.util import spec_from_loader, module_from_spec
+                from importlib.machinery import ExtensionFileLoader
+                import os, sys
+                {override_text}
+                loader = ExtensionFileLoader({name!r}, {filename!r})
+                spec = spec_from_loader({name!r}, loader)
+                try:
+                    module = module_from_spec(spec)
+                    loader.exec_module(module)
+                except ImportError as exc:
+                    text = 'ImportError: ' + str(exc)
+                else:
+                    text = 'okay'
+                os.write({fd}, text.encode('utf-8'))
+                ''')
+        else:
+            return textwrap.dedent(f'''
+                import os, sys
+                {override_text}
+                try:
+                    import {name}
+                except ImportError as exc:
+                    text = 'ImportError: ' + str(exc)
+                else:
+                    text = 'okay'
+                os.write({fd}, text.encode('utf-8'))
+                ''')
 
-    def run_here(self, name, *,
+    def run_here(self, name, filename=None, *,
                  check_singlephase_setting=False,
                  check_singlephase_override=None,
                  isolated=False,
@@ -1700,26 +1718,30 @@ def run_here(self, name, *,
         )
 
         r, w = self.pipe()
-        script = self.import_script(name, w, check_singlephase_override)
+        script = self.import_script(name, w, filename,
+                                    check_singlephase_override)
 
         ret = run_in_subinterp_with_config(script, **kwargs)
         self.assertEqual(ret, 0)
         return os.read(r, 100)
 
-    def check_compatible_here(self, name, *, strict=False, isolated=False):
+    def check_compatible_here(self, name, filename=None, *,
+                              strict=False,
+                              isolated=False,
+                              ):
         # Verify that the named module may be imported in a subinterpreter.
         # (See run_here() for more info.)
-        out = self.run_here(name,
+        out = self.run_here(name, filename,
                             check_singlephase_setting=strict,
                             isolated=isolated,
                             )
         self.assertEqual(out, b'okay')
 
-    def check_incompatible_here(self, name, *, isolated=False):
+    def check_incompatible_here(self, name, filename=None, *, isolated=False):
         # Differences from check_compatible_here():
         #  * verify that import fails
         #  * "strict" is always True
-        out = self.run_here(name,
+        out = self.run_here(name, filename,
                             check_singlephase_setting=True,
                             isolated=isolated,
                             )
@@ -1820,6 +1842,24 @@ def test_multi_init_extension_compat(self):
         with self.subTest(f'{module}: strict, fresh'):
             self.check_compatible_fresh(module, strict=True)
 
+    @unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
+    def test_multi_init_extension_non_isolated_compat(self):
+        modname = '_test_non_isolated'
+        filename = _testmultiphase.__file__
+        loader = ExtensionFileLoader(modname, filename)
+        spec = importlib.util.spec_from_loader(modname, loader)
+        module = importlib.util.module_from_spec(spec)
+        loader.exec_module(module)
+        sys.modules[modname] = module
+
+        require_extension(module)
+        with self.subTest(f'{modname}: isolated'):
+            self.check_incompatible_here(modname, filename, isolated=True)
+        with self.subTest(f'{modname}: not isolated'):
+            self.check_incompatible_here(modname, filename, isolated=False)
+        with self.subTest(f'{modname}: not strict'):
+            self.check_compatible_here(modname, filename, strict=False)
+
     def test_python_compat(self):
         module = 'threading'
         require_pure_python(module)
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-05-03-17-46-47.gh-issue-104108.GOxAYt.rst b/Misc/NEWS.d/next/Core and Builtins/2023-05-03-17-46-47.gh-issue-104108.GOxAYt.rst
new file mode 100644
index 000000000000..dad843636493
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-05-03-17-46-47.gh-issue-104108.GOxAYt.rst	
@@ -0,0 +1,6 @@
+Multi-phase init extension modules may now indicate whether or not they
+actually support multiple interpreters.  By default such modules are
+expected to support use in multiple interpreters.  In the uncommon case that
+one does not, it may use the new ``Py_mod_multiple_interpreters`` module def
+slot.  A value of ``0`` means the module does not support them. ``1`` means
+it does.  The default is ``1``.
diff --git a/Modules/_testmultiphase.c b/Modules/_testmultiphase.c
index cf8990a2df0a..bc7d8b64a943 100644
--- a/Modules/_testmultiphase.c
+++ b/Modules/_testmultiphase.c
@@ -884,3 +884,22 @@ PyInit__test_module_state_shared(void)
     }
     return module;
 }
+
+
+/* multiple interpreters supports */
+
+static PyModuleDef_Slot non_isolated_slots[] = {
+    {Py_mod_exec, execfunc},
+    {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
+    {0, NULL},
+};
+
+static PyModuleDef non_isolated_def = TEST_MODULE_DEF("_test_non_isolated",
+                                                      non_isolated_slots,
+                                                      testexport_methods);
+
+PyMODINIT_FUNC
+PyInit__test_non_isolated(void)
+{
+    return PyModuleDef_Init(&non_isolated_def);
+}
diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c
index ffcd90ed122e..63f4226dd9e9 100644
--- a/Objects/moduleobject.c
+++ b/Objects/moduleobject.c
@@ -245,6 +245,8 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
     PyObject *(*create)(PyObject *, PyModuleDef*) = NULL;
     PyObject *nameobj;
     PyObject *m = NULL;
+    int has_multiple_interpreters_slot = 0;
+    void *multiple_interpreters = (void *)0;
     int has_execution_slots = 0;
     const char *name;
     int ret;
@@ -287,6 +289,17 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
             case Py_mod_exec:
                 has_execution_slots = 1;
                 break;
+            case Py_mod_multiple_interpreters:
+                if (has_multiple_interpreters_slot) {
+                    PyErr_Format(
+                        PyExc_SystemError,
+                        "module %s has more than one 'multiple interpreters' slots",
+                        name);
+                    goto error;
+                }
+                multiple_interpreters = cur_slot->value;
+                has_multiple_interpreters_slot = 1;
+                break;
             default:
                 assert(cur_slot->slot < 0 || cur_slot->slot > _Py_mod_LAST_SLOT);
                 PyErr_Format(
@@ -297,6 +310,20 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
         }
     }
 
+    /* By default, multi-phase init modules are expected
+       to work under multiple interpreters. */
+    if (!has_multiple_interpreters_slot) {
+        multiple_interpreters = Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED;
+    }
+    if (multiple_interpreters == Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED) {
+        PyInterpreterState *interp = _PyInterpreterState_GET();
+        if (!_Py_IsMainInterpreter(interp)
+            && _PyImport_CheckSubinterpIncompatibleExtensionAllowed(name) < 0)
+        {
+            goto error;
+        }
+    }
+
     if (create) {
         m = create(spec, def);
         if (m == NULL) {
@@ -421,6 +448,9 @@ PyModule_ExecDef(PyObject *module, PyModuleDef *def)
                     return -1;
                 }
                 break;
+            case Py_mod_multiple_interpreters:
+                /* handled in PyModule_FromDefAndSpec2 */
+                break;
             default:
                 PyErr_Format(
                     PyExc_SystemError,



More information about the Python-checkins mailing list