Skip to content

gh-132775: Expand the Capability of Interpreter.call() #133484

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 30, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix exc propagation and an excinfo memory leak.
  • Loading branch information
ericsnowcurrently committed May 29, 2025
commit 17beeda38ad32d39017a4fe5e97220736a1e7a4f
20 changes: 16 additions & 4 deletions Include/internal/pycore_crossinterp.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,9 @@ typedef enum error_code {
_PyXI_ERR_ALREADY_RUNNING = -4,
_PyXI_ERR_MAIN_NS_FAILURE = -5,
_PyXI_ERR_APPLY_NS_FAILURE = -6,
_PyXI_ERR_NOT_SHAREABLE = -7,
_PyXI_ERR_PRESERVE_FAILURE = -7,
_PyXI_ERR_EXC_PROPAGATION_FAILURE = -8,
_PyXI_ERR_NOT_SHAREABLE = -9,
} _PyXI_errcode;


Expand Down Expand Up @@ -353,6 +355,7 @@ PyAPI_FUNC(void) _PyXI_FreeSession(_PyXI_session *);
typedef struct {
PyObject *preserved;
PyObject *excinfo;
_PyXI_errcode errcode;
} _PyXI_session_result;
PyAPI_FUNC(void) _PyXI_ClearResult(_PyXI_session_result *);

Expand All @@ -361,11 +364,20 @@ PyAPI_FUNC(int) _PyXI_Enter(
PyInterpreterState *interp,
PyObject *nsupdates,
_PyXI_session_result *);
PyAPI_FUNC(int) _PyXI_Exit(_PyXI_session *, _PyXI_session_result *);
PyAPI_FUNC(int) _PyXI_Exit(
_PyXI_session *,
_PyXI_errcode,
_PyXI_session_result *);

PyAPI_FUNC(PyObject *) _PyXI_GetMainNamespace(_PyXI_session *);
PyAPI_FUNC(PyObject *) _PyXI_GetMainNamespace(
_PyXI_session *,
_PyXI_errcode *);

PyAPI_FUNC(int) _PyXI_Preserve(_PyXI_session *, const char *, PyObject *);
PyAPI_FUNC(int) _PyXI_Preserve(
_PyXI_session *,
const char *,
PyObject *,
_PyXI_errcode *);
PyAPI_FUNC(PyObject *) _PyXI_GetPreserved(_PyXI_session_result *, const char *);


Expand Down
68 changes: 45 additions & 23 deletions Lib/test/test_interpreters/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,36 @@ def defined_in___main__(name, script, *, remove=False):
mainns.pop(name, None)


def build_excinfo(exctype, msg=None, formatted=None, errdisplay=None):
if isinstance(exctype, type):
assert issubclass(exctype, BaseException), exctype
exctype = types.SimpleNamespace(
__name__=exctype.__name__,
__qualname__=exctype.__qualname__,
__module__=exctype.__module__,
)
elif isinstance(exctype, str):
module, _, name = exctype.rpartition(exctype)
if not module and name in __builtins__:
module = 'builtins'
exctype = types.SimpleNamespace(
__name__=name,
__qualname__=exctype,
__module__=module or None,
)
else:
assert isinstance(exctype, types.SimpleNamespace)
assert msg is None or isinstance(msg, str), msg
assert formatted is None or isinstance(formatted, str), formatted
assert errdisplay is None or isinstance(errdisplay, str), errdisplay
return types.SimpleNamespace(
type=exctype,
msg=msg,
formatted=formatted,
errdisplay=errdisplay,
)


class ModuleTests(TestBase):

def test_queue_aliases(self):
Expand Down Expand Up @@ -1121,7 +1151,7 @@ def test_stateless_funcs(self):

func = call_func_return_unpickleable
with self.subTest('no args, returns unpickleable'):
with self.assert_fails_not_shareable():
with self.assertRaises(interpreters.NotShareableError):
interp.call(func)

def test_stateless_func_returns_arg(self):
Expand Down Expand Up @@ -1318,7 +1348,7 @@ def {funcname}():

with self.subTest('pickleable, added dynamically'):
with defined_in___main__(funcname, script) as arg:
with self.assert_fails_not_shareable():
with self.assertRaises(interpreters.NotShareableError):
interp.call(defs.spam_returns_arg, arg)

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

func = get_call_func_closure
with self.subTest(func):
with self.assert_fails_not_shareable():
with self.assertRaises(interpreters.NotShareableError):
interp.call(func, 42)

func = get_call_func_closure(42)
Expand All @@ -1376,12 +1406,12 @@ def test_call_invalid(self):
func = call_func_complex
op = 'closure'
with self.subTest(f'{func} ({op})'):
with self.assert_fails_not_shareable():
with self.assertRaises(interpreters.NotShareableError):
interp.call(func, op, value='~~~')

op = 'custom-inner'
with self.subTest(f'{func} ({op})'):
with self.assert_fails_not_shareable():
with self.assertRaises(interpreters.NotShareableError):
interp.call(func, op, 'eggs!')

def test_call_in_thread(self):
Expand Down Expand Up @@ -1924,18 +1954,14 @@ def test_exec(self):
with results:
exc = _interpreters.exec(interpid, script)
out = results.stdout()
self.assertEqual(out, '')
self.assert_ns_equal(exc, types.SimpleNamespace(
type=types.SimpleNamespace(
__name__='Exception',
__qualname__='Exception',
__module__='builtins',
),
msg='uh-oh!',
expected = build_excinfo(
Exception, 'uh-oh!',
# We check these in other tests.
formatted=exc.formatted,
errdisplay=exc.errdisplay,
))
)
self.assertEqual(out, '')
self.assert_ns_equal(exc, expected)

with self.subTest('from C-API'):
with self.interpreter_from_capi() as interpid:
Expand Down Expand Up @@ -1983,18 +2009,14 @@ def test_call(self):
with self.subTest('uncaught exception'):
func = defs.spam_raises
res, exc = _interpreters.call(interpid, func)
self.assertIsNone(res)
self.assertEqual(exc, types.SimpleNamespace(
type=types.SimpleNamespace(
__name__='Exception',
__qualname__='Exception',
__module__='builtins',
),
msg='spam!',
expected = build_excinfo(
Exception, 'spam!',
# We check these in other tests.
formatted=exc.formatted,
errdisplay=exc.errdisplay,
))
)
self.assertIsNone(res)
self.assertEqual(exc, expected)

@requires_test_modules
def test_set___main___attrs(self):
Expand Down
Loading
Loading