Skip to content

Commit 17beeda

Browse files
Fix exc propagation and an excinfo memory leak.
1 parent 3ed3423 commit 17beeda

File tree

4 files changed

+340
-215
lines changed

4 files changed

+340
-215
lines changed

Include/internal/pycore_crossinterp.h

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,9 @@ typedef enum error_code {
317317
_PyXI_ERR_ALREADY_RUNNING = -4,
318318
_PyXI_ERR_MAIN_NS_FAILURE = -5,
319319
_PyXI_ERR_APPLY_NS_FAILURE = -6,
320-
_PyXI_ERR_NOT_SHAREABLE = -7,
320+
_PyXI_ERR_PRESERVE_FAILURE = -7,
321+
_PyXI_ERR_EXC_PROPAGATION_FAILURE = -8,
322+
_PyXI_ERR_NOT_SHAREABLE = -9,
321323
} _PyXI_errcode;
322324

323325

@@ -353,6 +355,7 @@ PyAPI_FUNC(void) _PyXI_FreeSession(_PyXI_session *);
353355
typedef struct {
354356
PyObject *preserved;
355357
PyObject *excinfo;
358+
_PyXI_errcode errcode;
356359
} _PyXI_session_result;
357360
PyAPI_FUNC(void) _PyXI_ClearResult(_PyXI_session_result *);
358361

@@ -361,11 +364,20 @@ PyAPI_FUNC(int) _PyXI_Enter(
361364
PyInterpreterState *interp,
362365
PyObject *nsupdates,
363366
_PyXI_session_result *);
364-
PyAPI_FUNC(int) _PyXI_Exit(_PyXI_session *, _PyXI_session_result *);
367+
PyAPI_FUNC(int) _PyXI_Exit(
368+
_PyXI_session *,
369+
_PyXI_errcode,
370+
_PyXI_session_result *);
365371

366-
PyAPI_FUNC(PyObject *) _PyXI_GetMainNamespace(_PyXI_session *);
372+
PyAPI_FUNC(PyObject *) _PyXI_GetMainNamespace(
373+
_PyXI_session *,
374+
_PyXI_errcode *);
367375

368-
PyAPI_FUNC(int) _PyXI_Preserve(_PyXI_session *, const char *, PyObject *);
376+
PyAPI_FUNC(int) _PyXI_Preserve(
377+
_PyXI_session *,
378+
const char *,
379+
PyObject *,
380+
_PyXI_errcode *);
369381
PyAPI_FUNC(PyObject *) _PyXI_GetPreserved(_PyXI_session_result *, const char *);
370382

371383

Lib/test/test_interpreters/test_api.py

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,36 @@ def defined_in___main__(name, script, *, remove=False):
5757
mainns.pop(name, None)
5858

5959

60+
def build_excinfo(exctype, msg=None, formatted=None, errdisplay=None):
61+
if isinstance(exctype, type):
62+
assert issubclass(exctype, BaseException), exctype
63+
exctype = types.SimpleNamespace(
64+
__name__=exctype.__name__,
65+
__qualname__=exctype.__qualname__,
66+
__module__=exctype.__module__,
67+
)
68+
elif isinstance(exctype, str):
69+
module, _, name = exctype.rpartition(exctype)
70+
if not module and name in __builtins__:
71+
module = 'builtins'
72+
exctype = types.SimpleNamespace(
73+
__name__=name,
74+
__qualname__=exctype,
75+
__module__=module or None,
76+
)
77+
else:
78+
assert isinstance(exctype, types.SimpleNamespace)
79+
assert msg is None or isinstance(msg, str), msg
80+
assert formatted is None or isinstance(formatted, str), formatted
81+
assert errdisplay is None or isinstance(errdisplay, str), errdisplay
82+
return types.SimpleNamespace(
83+
type=exctype,
84+
msg=msg,
85+
formatted=formatted,
86+
errdisplay=errdisplay,
87+
)
88+
89+
6090
class ModuleTests(TestBase):
6191

6292
def test_queue_aliases(self):
@@ -1121,7 +1151,7 @@ def test_stateless_funcs(self):
11211151

11221152
func = call_func_return_unpickleable
11231153
with self.subTest('no args, returns unpickleable'):
1124-
with self.assert_fails_not_shareable():
1154+
with self.assertRaises(interpreters.NotShareableError):
11251155
interp.call(func)
11261156

11271157
def test_stateless_func_returns_arg(self):
@@ -1318,7 +1348,7 @@ def {funcname}():
13181348

13191349
with self.subTest('pickleable, added dynamically'):
13201350
with defined_in___main__(funcname, script) as arg:
1321-
with self.assert_fails_not_shareable():
1351+
with self.assertRaises(interpreters.NotShareableError):
13221352
interp.call(defs.spam_returns_arg, arg)
13231353

13241354
with self.subTest('lying about __main__'):
@@ -1365,7 +1395,7 @@ def test_call_invalid(self):
13651395

13661396
func = get_call_func_closure
13671397
with self.subTest(func):
1368-
with self.assert_fails_not_shareable():
1398+
with self.assertRaises(interpreters.NotShareableError):
13691399
interp.call(func, 42)
13701400

13711401
func = get_call_func_closure(42)
@@ -1376,12 +1406,12 @@ def test_call_invalid(self):
13761406
func = call_func_complex
13771407
op = 'closure'
13781408
with self.subTest(f'{func} ({op})'):
1379-
with self.assert_fails_not_shareable():
1409+
with self.assertRaises(interpreters.NotShareableError):
13801410
interp.call(func, op, value='~~~')
13811411

13821412
op = 'custom-inner'
13831413
with self.subTest(f'{func} ({op})'):
1384-
with self.assert_fails_not_shareable():
1414+
with self.assertRaises(interpreters.NotShareableError):
13851415
interp.call(func, op, 'eggs!')
13861416

13871417
def test_call_in_thread(self):
@@ -1924,18 +1954,14 @@ def test_exec(self):
19241954
with results:
19251955
exc = _interpreters.exec(interpid, script)
19261956
out = results.stdout()
1927-
self.assertEqual(out, '')
1928-
self.assert_ns_equal(exc, types.SimpleNamespace(
1929-
type=types.SimpleNamespace(
1930-
__name__='Exception',
1931-
__qualname__='Exception',
1932-
__module__='builtins',
1933-
),
1934-
msg='uh-oh!',
1957+
expected = build_excinfo(
1958+
Exception, 'uh-oh!',
19351959
# We check these in other tests.
19361960
formatted=exc.formatted,
19371961
errdisplay=exc.errdisplay,
1938-
))
1962+
)
1963+
self.assertEqual(out, '')
1964+
self.assert_ns_equal(exc, expected)
19391965

19401966
with self.subTest('from C-API'):
19411967
with self.interpreter_from_capi() as interpid:
@@ -1983,18 +2009,14 @@ def test_call(self):
19832009
with self.subTest('uncaught exception'):
19842010
func = defs.spam_raises
19852011
res, exc = _interpreters.call(interpid, func)
1986-
self.assertIsNone(res)
1987-
self.assertEqual(exc, types.SimpleNamespace(
1988-
type=types.SimpleNamespace(
1989-
__name__='Exception',
1990-
__qualname__='Exception',
1991-
__module__='builtins',
1992-
),
1993-
msg='spam!',
2012+
expected = build_excinfo(
2013+
Exception, 'spam!',
19942014
# We check these in other tests.
19952015
formatted=exc.formatted,
19962016
errdisplay=exc.errdisplay,
1997-
))
2017+
)
2018+
self.assertIsNone(res)
2019+
self.assertEqual(exc, expected)
19982020

19992021
@requires_test_modules
20002022
def test_set___main___attrs(self):

0 commit comments

Comments
 (0)