Skip to content

Commit 74191dd

Browse files
Return an ExceptionSnapshot from _interpreters.exec().
1 parent 6a5fb7b commit 74191dd

File tree

6 files changed

+81
-73
lines changed

6 files changed

+81
-73
lines changed

Lib/test/support/interpreters.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,18 @@ def run(self, src_str, /, *, init=None):
116116
that time, the previous interpreter is allowed to run
117117
in other threads.
118118
"""
119-
_interpreters.exec(self._id, src_str, init)
119+
err = _interpreters.exec(self._id, src_str, init)
120+
if err is not None:
121+
if err.name is not None:
122+
if err.msg is not None:
123+
msg = f'{err.name}: {err.msg}'
124+
else:
125+
msg = err.name
126+
elif err.msg is not None:
127+
msg = err.msg
128+
else:
129+
msg = None
130+
raise RunFailedError(msg)
120131

121132

122133
def create_channel():

Lib/test/test__xxinterpchannels.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,16 +1017,16 @@ def test_close_multiple_users(self):
10171017
_channels.recv({cid})
10181018
"""))
10191019
channels.close(cid)
1020-
with self.assertRaises(interpreters.RunFailedError) as cm:
1021-
interpreters.run_string(id1, dedent(f"""
1020+
1021+
excsnap = interpreters.run_string(id1, dedent(f"""
10221022
_channels.send({cid}, b'spam')
10231023
"""))
1024-
self.assertIn('ChannelClosedError', str(cm.exception))
1025-
with self.assertRaises(interpreters.RunFailedError) as cm:
1026-
interpreters.run_string(id2, dedent(f"""
1024+
self.assertIn('ChannelClosedError', excsnap.type)
1025+
1026+
excsnap = interpreters.run_string(id2, dedent(f"""
10271027
_channels.send({cid}, b'spam')
10281028
"""))
1029-
self.assertIn('ChannelClosedError', str(cm.exception))
1029+
self.assertIn('ChannelClosedError', excsnap.type)
10301030

10311031
def test_close_multiple_times(self):
10321032
cid = channels.create()

Lib/test/test__xxsubinterpreters.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -740,30 +740,33 @@ def assert_run_failed(self, exctype, msg=None):
740740
"{}: {}".format(exctype.__name__, msg))
741741

742742
def test_invalid_syntax(self):
743-
with self.assert_run_failed(SyntaxError):
744-
# missing close paren
745-
interpreters.run_string(self.id, 'print("spam"')
743+
# missing close paren
744+
exc = interpreters.run_string(self.id, 'print("spam"')
745+
self.assertEqual(exc.type, 'SyntaxError')
746746

747747
def test_failure(self):
748-
with self.assert_run_failed(Exception, 'spam'):
749-
interpreters.run_string(self.id, 'raise Exception("spam")')
748+
exc = interpreters.run_string(self.id, 'raise Exception("spam")')
749+
self.assertEqual(exc.type, 'Exception')
750+
self.assertEqual(exc.msg, 'spam')
750751

751752
def test_SystemExit(self):
752-
with self.assert_run_failed(SystemExit, '42'):
753-
interpreters.run_string(self.id, 'raise SystemExit(42)')
753+
exc = interpreters.run_string(self.id, 'raise SystemExit(42)')
754+
self.assertEqual(exc.type, 'SystemExit')
755+
self.assertEqual(exc.msg, '42')
754756

755757
def test_sys_exit(self):
756-
with self.assert_run_failed(SystemExit):
757-
interpreters.run_string(self.id, dedent("""
758-
import sys
759-
sys.exit()
760-
"""))
758+
exc = interpreters.run_string(self.id, dedent("""
759+
import sys
760+
sys.exit()
761+
"""))
762+
self.assertEqual(exc.type, 'SystemExit')
761763

762-
with self.assert_run_failed(SystemExit, '42'):
763-
interpreters.run_string(self.id, dedent("""
764-
import sys
765-
sys.exit(42)
766-
"""))
764+
exc = interpreters.run_string(self.id, dedent("""
765+
import sys
766+
sys.exit(42)
767+
"""))
768+
self.assertEqual(exc.type, 'SystemExit')
769+
self.assertEqual(exc.msg, '42')
767770

768771
def test_with_shared(self):
769772
r, w = os.pipe()

Lib/test/test_import/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1968,10 +1968,12 @@ def test_disallowed_reimport(self):
19681968
print(_testsinglephase)
19691969
''')
19701970
interpid = _interpreters.create()
1971-
with self.assertRaises(_interpreters.RunFailedError):
1972-
_interpreters.run_string(interpid, script)
1973-
with self.assertRaises(_interpreters.RunFailedError):
1974-
_interpreters.run_string(interpid, script)
1971+
1972+
excsnap = _interpreters.run_string(interpid, script)
1973+
self.assertIsNot(excsnap, None)
1974+
1975+
excsnap = _interpreters.run_string(interpid, script)
1976+
self.assertIsNot(excsnap, None)
19751977

19761978

19771979
class TestSinglePhaseSnapshot(ModuleSnapshot):

Lib/test/test_importlib/test_util.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -655,25 +655,19 @@ def test_magic_number(self):
655655
@unittest.skipIf(_interpreters is None, 'subinterpreters required')
656656
class IncompatibleExtensionModuleRestrictionsTests(unittest.TestCase):
657657

658-
ERROR = re.compile("^ImportError: module (.*) does not support loading in subinterpreters")
659-
660658
def run_with_own_gil(self, script):
661659
interpid = _interpreters.create(isolated=True)
662-
try:
663-
_interpreters.run_string(interpid, script)
664-
except _interpreters.RunFailedError as exc:
665-
if m := self.ERROR.match(str(exc)):
666-
modname, = m.groups()
667-
raise ImportError(modname)
660+
excsnap = _interpreters.run_string(interpid, script)
661+
if excsnap is not None:
662+
if excsnap.type == 'ImportError':
663+
raise ImportError(excsnap.msg)
668664

669665
def run_with_shared_gil(self, script):
670666
interpid = _interpreters.create(isolated=False)
671-
try:
672-
_interpreters.run_string(interpid, script)
673-
except _interpreters.RunFailedError as exc:
674-
if m := self.ERROR.match(str(exc)):
675-
modname, = m.groups()
676-
raise ImportError(modname)
667+
excsnap = _interpreters.run_string(interpid, script)
668+
if excsnap is not None:
669+
if excsnap.type == 'ImportError':
670+
raise ImportError(excsnap.msg)
677671

678672
@unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module")
679673
def test_single_phase_init_module(self):

Modules/_xxsubinterpretersmodule.c

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -237,18 +237,17 @@ _run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags)
237237
static int
238238
_run_in_interpreter(PyInterpreterState *interp,
239239
const char *codestr, Py_ssize_t codestrlen,
240-
PyObject *shareables, int flags,
241-
PyObject *excwrapper)
240+
PyObject *shareables, int flags, PyObject **excsnap)
242241
{
243242
assert(!PyErr_Occurred());
244243
_PyXI_session session = {0};
245244

246245
// Prep and switch interpreters.
247246
if (_PyXI_Enter(&session, interp, shareables) < 0) {
248247
assert(!PyErr_Occurred());
249-
_PyXI_ApplyExceptionInfo(session.exc, excwrapper);
250-
assert(PyErr_Occurred());
251-
return -1;
248+
*excsnap = _PyXI_ResolveCapturedException(&session, NULL);
249+
assert((PyErr_Occurred() == NULL) != (*excsnap == NULL));
250+
return PyErr_Occurred() ? -1 : 0;
252251
}
253252

254253
// Run the script.
@@ -258,9 +257,12 @@ _run_in_interpreter(PyInterpreterState *interp,
258257
_PyXI_Exit(&session);
259258

260259
// Propagate any exception out to the caller.
261-
assert(!PyErr_Occurred());
262260
if (res < 0) {
263-
_PyXI_ApplyCapturedException(&session, excwrapper);
261+
*excsnap = _PyXI_ResolveCapturedException(&session, NULL);
262+
assert((PyErr_Occurred() == NULL) != (*excsnap == NULL));
263+
if (!PyErr_Occurred()) {
264+
res = 0;
265+
}
264266
}
265267
else {
266268
assert(!_PyXI_HasCapturedException(&session));
@@ -528,14 +530,15 @@ convert_code_arg(PyObject *arg, const char *fname, const char *displayname,
528530
return code;
529531
}
530532

531-
static int
533+
static PyObject *
532534
_interp_exec(PyObject *self,
533535
PyObject *id_arg, PyObject *code_arg, PyObject *shared_arg)
534536
{
535537
// Look up the interpreter.
536538
PyInterpreterState *interp = PyInterpreterID_LookUp(id_arg);
537539
if (interp == NULL) {
538-
return -1;
540+
assert(PyErr_Occurred());
541+
return NULL;
539542
}
540543

541544
// Extract code.
@@ -545,20 +548,24 @@ _interp_exec(PyObject *self,
545548
const char *codestr = get_code_str(code_arg,
546549
&codestrlen, &bytes_obj, &flags);
547550
if (codestr == NULL) {
548-
return -1;
551+
assert(PyErr_Occurred());
552+
return NULL;
549553
}
550554

551555
// Run the code in the interpreter.
552-
module_state *state = get_module_state(self);
553-
assert(state != NULL);
556+
PyObject *excsnap = NULL;
554557
int res = _run_in_interpreter(interp, codestr, codestrlen,
555-
shared_arg, flags, state->RunFailedError);
558+
shared_arg, flags, &excsnap);
556559
Py_XDECREF(bytes_obj);
557560
if (res < 0) {
558-
return -1;
561+
assert(PyErr_Occurred());
562+
assert(excsnap == NULL);
563+
return NULL;
559564
}
560-
561-
return 0;
565+
else if (excsnap != NULL) {
566+
return excsnap;
567+
}
568+
Py_RETURN_NONE;
562569
}
563570

564571
static PyObject *
@@ -586,12 +593,9 @@ interp_exec(PyObject *self, PyObject *args, PyObject *kwds)
586593
return NULL;
587594
}
588595

589-
int res = _interp_exec(self, id, code, shared);
596+
PyObject *res = _interp_exec(self, id, code, shared);
590597
Py_DECREF(code);
591-
if (res < 0) {
592-
return NULL;
593-
}
594-
Py_RETURN_NONE;
598+
return res;
595599
}
596600

597601
PyDoc_STRVAR(exec_doc,
@@ -629,12 +633,9 @@ interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)
629633
return NULL;
630634
}
631635

632-
int res = _interp_exec(self, id, script, shared);
636+
PyObject *res = _interp_exec(self, id, script, shared);
633637
Py_DECREF(script);
634-
if (res < 0) {
635-
return NULL;
636-
}
637-
Py_RETURN_NONE;
638+
return res;
638639
}
639640

640641
PyDoc_STRVAR(run_string_doc,
@@ -663,12 +664,9 @@ interp_run_func(PyObject *self, PyObject *args, PyObject *kwds)
663664
return NULL;
664665
}
665666

666-
int res = _interp_exec(self, id, (PyObject *)code, shared);
667+
PyObject *res = _interp_exec(self, id, (PyObject *)code, shared);
667668
Py_DECREF(code);
668-
if (res < 0) {
669-
return NULL;
670-
}
671-
Py_RETURN_NONE;
669+
return res;
672670
}
673671

674672
PyDoc_STRVAR(run_func_doc,

0 commit comments

Comments
 (0)