From 6f63e57aa8cd2a0a515aaa536a965a70d785c4ce Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 8 Feb 2026 12:30:13 -0500 Subject: [PATCH 1/4] Don't use exception object across interpreters. --- Python/import.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Python/import.c b/Python/import.c index 466c5868ab7ee8..e7f803d84f1367 100644 --- a/Python/import.c +++ b/Python/import.c @@ -2176,13 +2176,29 @@ import_run_extension(PyThreadState *tstate, PyModInitFunction p0, } main_finally: + if (rc < 0) { + _Py_ext_module_loader_result_apply_error(&res, name_buf); + } + /* Switch back to the subinterpreter. */ if (switched) { + // gh-144601: The exception object can't be transferred across + // interpreters. Instead, we print out an unraisable exception, and + // then raise a different exception for the calling interpreter. + if (rc < 0) { + assert(PyErr_Occurred()); + PyErr_FormatUnraisable("Exception while importing from subinterpreter"); + } assert(main_tstate != tstate); switch_back_from_main_interpreter(tstate, main_tstate, mod); /* Any module we got from the init function will have to be * reloaded in the subinterpreter. */ mod = NULL; + if (rc < 0) { + PyErr_SetString(PyExc_ImportError, + "failed to import from subinterpreter due to exception"); + goto error; + } } /*****************************************************************/ @@ -2191,7 +2207,6 @@ import_run_extension(PyThreadState *tstate, PyModInitFunction p0, /* Finally we handle the error return from _PyImport_RunModInitFunc(). */ if (rc < 0) { - _Py_ext_module_loader_result_apply_error(&res, name_buf); goto error; } From 48920c6d1b883845ad411043ae5e9a457975b078 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 8 Feb 2026 12:45:46 -0500 Subject: [PATCH 2/4] Add a test case. --- Lib/test/test_import/__init__.py | 25 +++++++++++++++++++++++++ Modules/_testsinglephase.c | 8 ++++++++ 2 files changed, 33 insertions(+) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 59c6dc4587c93d..61fbd0e8b3e8d6 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -45,6 +45,7 @@ Py_GIL_DISABLED, no_rerun, force_not_colorized_test_class, + catch_unraisable_exception ) from test.support.import_helper import ( forget, make_legacy_pyc, unlink, unload, ready_to_import, @@ -2517,6 +2518,30 @@ def test_disallowed_reimport(self): excsnap = _interpreters.run_string(interpid, script) self.assertIsNot(excsnap, None) + @requires_subinterpreters + def test_pyinit_function_raises_exception(self): + import _testsinglephase + filename = _testsinglephase.__file__ + script = f"""if True: + from test.test_import import import_extension_from_file + + import_extension_from_file('_testsinglephase_raise_exception', {filename!r})""" + + interp = _interpreters.create() + try: + with catch_unraisable_exception() as cm: + exception = _interpreters.run_string(interp, script) + unraisable = cm.unraisable + finally: + _interpreters.destroy(interp) + + self.assertIsNotNone(exception) + self.assertIsNotNone(exception.type.__name__, "ImportError") + self.assertIsNotNone(exception.msg, "failed to import from subinterpreter due to exception") + self.assertIsNotNone(unraisable) + self.assertIs(unraisable.exc_type, RuntimeError) + self.assertEqual(str(unraisable.exc_value), "evil") + class TestSinglePhaseSnapshot(ModuleSnapshot): """A representation of a single-phase init module for testing. diff --git a/Modules/_testsinglephase.c b/Modules/_testsinglephase.c index ee38d61b43a82a..7ea77c6312c59e 100644 --- a/Modules/_testsinglephase.c +++ b/Modules/_testsinglephase.c @@ -801,3 +801,11 @@ PyInit__testsinglephase_circular(void) } return Py_NewRef(static_module_circular); } + + +PyMODINIT_FUNC +PyInit__testsinglephase_raise_exception(void) +{ + PyErr_SetString(PyExc_RuntimeError, "evil"); + return NULL; +} From 8a4f224cdb02344949e8986bff3aa1b9a212f1c3 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 8 Feb 2026 12:46:25 -0500 Subject: [PATCH 3/4] Add a comment. --- Lib/test/test_import/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 61fbd0e8b3e8d6..f66e2987d34850 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -2520,6 +2520,8 @@ def test_disallowed_reimport(self): @requires_subinterpreters def test_pyinit_function_raises_exception(self): + # gh-144601: PyInit functions that raised exceptions would cause a + # crash when imported from a subinterpreter. import _testsinglephase filename = _testsinglephase.__file__ script = f"""if True: From 0fb07075370eda924dfb5ab49bf12a251f395f9f Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 8 Feb 2026 12:47:29 -0500 Subject: [PATCH 4/4] Add blurb. --- .../2026-02-08-12-47-27.gh-issue-144601.E4Yi9J.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-02-08-12-47-27.gh-issue-144601.E4Yi9J.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-08-12-47-27.gh-issue-144601.E4Yi9J.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-08-12-47-27.gh-issue-144601.E4Yi9J.rst new file mode 100644 index 00000000000000..1c7772e2f3ca26 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-08-12-47-27.gh-issue-144601.E4Yi9J.rst @@ -0,0 +1,2 @@ +Fix crash when importing a module whose ``PyInit`` function raises an +exception from a subinterpreter.