From 53123f60d125aa4e6cbe2d6a32131c88bf6c3671 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 10 Jun 2025 14:23:00 -0600 Subject: [PATCH 1/2] Clean up cross-interpreter error handling. --- Include/internal/pycore_crossinterp.h | 36 +- Modules/_interpretersmodule.c | 90 ++-- Python/crossinterp.c | 580 +++++++++++++++++--------- Python/crossinterp_data_lookup.h | 28 ++ Python/crossinterp_exceptions.h | 7 - 5 files changed, 481 insertions(+), 260 deletions(-) diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index 713ddc66ba7382..267b6ea05dbb65 100644 --- a/Include/internal/pycore_crossinterp.h +++ b/Include/internal/pycore_crossinterp.h @@ -303,10 +303,10 @@ typedef struct _excinfo { const char *errdisplay; } _PyXI_excinfo; -PyAPI_FUNC(int) _PyXI_InitExcInfo(_PyXI_excinfo *info, PyObject *exc); +PyAPI_FUNC(_PyXI_excinfo *) _PyXI_NewExcInfo(PyObject *exc); +PyAPI_FUNC(void) _PyXI_FreeExcInfo(_PyXI_excinfo *info); PyAPI_FUNC(PyObject *) _PyXI_FormatExcInfo(_PyXI_excinfo *info); PyAPI_FUNC(PyObject *) _PyXI_ExcInfoAsObject(_PyXI_excinfo *info); -PyAPI_FUNC(void) _PyXI_ClearExcInfo(_PyXI_excinfo *info); typedef enum error_code { @@ -322,19 +322,23 @@ typedef enum error_code { _PyXI_ERR_NOT_SHAREABLE = -9, } _PyXI_errcode; +typedef struct error_override _PyXI_error_override; -typedef struct _sharedexception { - // The originating interpreter. - PyInterpreterState *interp; - // The kind of error to propagate. - _PyXI_errcode code; - // The exception information to propagate, if applicable. - // This is populated only for some error codes, - // but always for _PyXI_ERR_UNCAUGHT_EXCEPTION. - _PyXI_excinfo uncaught; -} _PyXI_error; +PyAPI_FUNC(_PyXI_error_override *) _PyXI_NewErrorOverride(void); +PyAPI_FUNC(void) _PyXI_FreeErrorOverride(_PyXI_error_override *); +PyAPI_FUNC(_PyXI_errcode) _PyXI_GetErrorOverrideCode(_PyXI_error_override *); +PyAPI_FUNC(int) _PyXI_SetErrorOverride( + _PyXI_error_override *, + _PyXI_errcode, + PyObject *); +PyAPI_FUNC(void) _PyXI_SetErrorOverrideUTF8( + _PyXI_error_override *, + _PyXI_errcode, + const char *); -PyAPI_FUNC(PyObject *) _PyXI_ApplyError(_PyXI_error *err); +PyAPI_FUNC(int) _PyXI_UnwrapNotShareableError( + PyThreadState *, + _PyXI_error_override *); // A cross-interpreter session involves entering an interpreter @@ -366,18 +370,18 @@ PyAPI_FUNC(int) _PyXI_Enter( _PyXI_session_result *); PyAPI_FUNC(int) _PyXI_Exit( _PyXI_session *, - _PyXI_errcode, + _PyXI_error_override *, _PyXI_session_result *); PyAPI_FUNC(PyObject *) _PyXI_GetMainNamespace( _PyXI_session *, - _PyXI_errcode *); + _PyXI_error_override *); PyAPI_FUNC(int) _PyXI_Preserve( _PyXI_session *, const char *, PyObject *, - _PyXI_errcode *); + _PyXI_error_override *); PyAPI_FUNC(PyObject *) _PyXI_GetPreserved(_PyXI_session_result *, const char *); diff --git a/Modules/_interpretersmodule.c b/Modules/_interpretersmodule.c index 037e9544543c4d..94d2ac02c409a8 100644 --- a/Modules/_interpretersmodule.c +++ b/Modules/_interpretersmodule.c @@ -80,21 +80,11 @@ is_notshareable_raised(PyThreadState *tstate) } static void -unwrap_not_shareable(PyThreadState *tstate) +unwrap_not_shareable(PyThreadState *tstate, _PyXI_error_override *err) { - if (!is_notshareable_raised(tstate)) { - return; - } - PyObject *exc = _PyErr_GetRaisedException(tstate); - PyObject *cause = PyException_GetCause(exc); - if (cause != NULL) { - Py_DECREF(exc); - exc = cause; + if (_PyXI_UnwrapNotShareableError(tstate, err) < 0) { + _PyErr_Clear(tstate); } - else { - assert(PyException_GetContext(exc) == NULL); - } - _PyErr_SetRaisedException(tstate, exc); } @@ -532,13 +522,30 @@ _interp_call_pack(PyThreadState *tstate, struct interp_call *call, return 0; } +static void +wrap_notshareable(PyThreadState *tstate, const char *label) +{ + if (!is_notshareable_raised(tstate)) { + return; + } + assert(label != NULL && strlen(label) > 0); + PyObject *cause = _PyErr_GetRaisedException(tstate); + _PyXIData_FormatNotShareableError(tstate, "%s not shareable", label); + PyObject *exc = _PyErr_GetRaisedException(tstate); + PyException_SetCause(exc, cause); + _PyErr_SetRaisedException(tstate, exc); +} + static int _interp_call_unpack(struct interp_call *call, PyObject **p_func, PyObject **p_args, PyObject **p_kwargs) { + PyThreadState *tstate = PyThreadState_Get(); + // Unpack the func. PyObject *func = _PyXIData_NewObject(call->func); if (func == NULL) { + wrap_notshareable(tstate, "func"); return -1; } // Unpack the args. @@ -553,6 +560,7 @@ _interp_call_unpack(struct interp_call *call, else { args = _PyXIData_NewObject(call->args); if (args == NULL) { + wrap_notshareable(tstate, "args"); Py_DECREF(func); return -1; } @@ -563,6 +571,7 @@ _interp_call_unpack(struct interp_call *call, if (call->kwargs != NULL) { kwargs = _PyXIData_NewObject(call->kwargs); if (kwargs == NULL) { + wrap_notshareable(tstate, "kwargs"); Py_DECREF(func); Py_DECREF(args); return -1; @@ -577,7 +586,7 @@ _interp_call_unpack(struct interp_call *call, static int _make_call(struct interp_call *call, - PyObject **p_result, _PyXI_errcode *p_errcode) + PyObject **p_result, _PyXI_error_override *err) { assert(call != NULL && call->func != NULL); PyThreadState *tstate = _PyThreadState_GET(); @@ -588,12 +597,10 @@ _make_call(struct interp_call *call, assert(func == NULL); assert(args == NULL); assert(kwargs == NULL); - *p_errcode = is_notshareable_raised(tstate) - ? _PyXI_ERR_NOT_SHAREABLE - : _PyXI_ERR_OTHER; + _PyXI_SetErrorOverride(err, _PyXI_ERR_OTHER, NULL); + unwrap_not_shareable(tstate, err); return -1; } - *p_errcode = _PyXI_ERR_NO_ERROR; // Make the call. PyObject *resobj = PyObject_Call(func, args, kwargs); @@ -608,17 +615,17 @@ _make_call(struct interp_call *call, } static int -_run_script(_PyXIData_t *script, PyObject *ns, _PyXI_errcode *p_errcode) +_run_script(_PyXIData_t *script, PyObject *ns, _PyXI_error_override *err) { PyObject *code = _PyXIData_NewObject(script); if (code == NULL) { - *p_errcode = _PyXI_ERR_NOT_SHAREABLE; + _PyXI_SetErrorOverride(err, _PyXI_ERR_NOT_SHAREABLE, NULL); return -1; } PyObject *result = PyEval_EvalCode(code, ns, ns); Py_DECREF(code); if (result == NULL) { - *p_errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION; + _PyXI_SetErrorOverride(err, _PyXI_ERR_UNCAUGHT_EXCEPTION, NULL); return -1; } assert(result == Py_None); @@ -644,8 +651,14 @@ _run_in_interpreter(PyThreadState *tstate, PyInterpreterState *interp, PyObject *shareables, struct run_result *runres) { assert(!_PyErr_Occurred(tstate)); + int res = -1; + _PyXI_error_override *override = _PyXI_NewErrorOverride(); + if (override == NULL) { + return -1; + } _PyXI_session *session = _PyXI_NewSession(); if (session == NULL) { + _PyXI_FreeErrorOverride(override); return -1; } _PyXI_session_result result = {0}; @@ -655,43 +668,44 @@ _run_in_interpreter(PyThreadState *tstate, PyInterpreterState *interp, // If an error occured at this step, it means that interp // was not prepared and switched. _PyXI_FreeSession(session); + _PyXI_FreeErrorOverride(override); assert(result.excinfo == NULL); return -1; } // Run in the interpreter. - int res = -1; - _PyXI_errcode errcode = _PyXI_ERR_NO_ERROR; if (script != NULL) { assert(call == NULL); - PyObject *mainns = _PyXI_GetMainNamespace(session, &errcode); + PyObject *mainns = _PyXI_GetMainNamespace(session, override); if (mainns == NULL) { goto finally; } - res = _run_script(script, mainns, &errcode); + res = _run_script(script, mainns, override); } else { assert(call != NULL); PyObject *resobj; - res = _make_call(call, &resobj, &errcode); + res = _make_call(call, &resobj, override); if (res == 0) { - res = _PyXI_Preserve(session, "resobj", resobj, &errcode); + res = _PyXI_Preserve(session, "resobj", resobj, override); Py_DECREF(resobj); if (res < 0) { goto finally; } } } - int exitres; finally: // Clean up and switch back. - exitres = _PyXI_Exit(session, errcode, &result); + (void)res; + int exitres = _PyXI_Exit(session, override, &result); assert(res == 0 || exitres != 0); _PyXI_FreeSession(session); + _PyXI_FreeErrorOverride(override); res = exitres; if (_PyErr_Occurred(tstate)) { + // It's a directly propagated exception. assert(res < 0); } else if (res < 0) { @@ -1064,7 +1078,7 @@ interp_set___main___attrs(PyObject *self, PyObject *args, PyObject *kwargs) // Clean up and switch back. assert(!PyErr_Occurred()); - int res = _PyXI_Exit(session, _PyXI_ERR_NO_ERROR, NULL); + int res = _PyXI_Exit(session, NULL, NULL); _PyXI_FreeSession(session); assert(res == 0); if (res < 0) { @@ -1124,7 +1138,7 @@ interp_exec(PyObject *self, PyObject *args, PyObject *kwds) // global variables. They will be resolved against __main__. _PyXIData_t xidata = {0}; if (_PyCode_GetScriptXIData(tstate, code, &xidata) < 0) { - unwrap_not_shareable(tstate); + unwrap_not_shareable(tstate, NULL); return NULL; } @@ -1188,7 +1202,7 @@ interp_run_string(PyObject *self, PyObject *args, PyObject *kwds) _PyXIData_t xidata = {0}; if (_PyCode_GetScriptXIData(tstate, script, &xidata) < 0) { - unwrap_not_shareable(tstate); + unwrap_not_shareable(tstate, NULL); return NULL; } @@ -1251,7 +1265,7 @@ interp_run_func(PyObject *self, PyObject *args, PyObject *kwds) _PyXIData_t xidata = {0}; if (_PyCode_GetScriptXIData(tstate, code, &xidata) < 0) { - unwrap_not_shareable(tstate); + unwrap_not_shareable(tstate, NULL); return NULL; } @@ -1542,16 +1556,16 @@ capture_exception(PyObject *self, PyObject *args, PyObject *kwds) } PyObject *captured = NULL; - _PyXI_excinfo info = {0}; - if (_PyXI_InitExcInfo(&info, exc) < 0) { + _PyXI_excinfo *info = _PyXI_NewExcInfo(exc); + if (info == NULL) { goto finally; } - captured = _PyXI_ExcInfoAsObject(&info); + captured = _PyXI_ExcInfoAsObject(info); if (captured == NULL) { goto finally; } - PyObject *formatted = _PyXI_FormatExcInfo(&info); + PyObject *formatted = _PyXI_FormatExcInfo(info); if (formatted == NULL) { Py_CLEAR(captured); goto finally; @@ -1564,7 +1578,7 @@ capture_exception(PyObject *self, PyObject *args, PyObject *kwds) } finally: - _PyXI_ClearExcInfo(&info); + _PyXI_FreeExcInfo(info); if (exc != exc_arg) { if (PyErr_Occurred()) { PyErr_SetRaisedException(exc); diff --git a/Python/crossinterp.c b/Python/crossinterp.c index 5e73ab28f2b663..65acb5fc4e60f6 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -1324,6 +1324,12 @@ _excinfo_normalize_type(struct _excinfo_type *info, *p_module = module; } +static int +excinfo_is_set(_PyXI_excinfo *info) +{ + return info->type.name != NULL || info->msg != NULL; +} + static void _PyXI_excinfo_clear(_PyXI_excinfo *info) { @@ -1485,6 +1491,10 @@ _PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype) if (tbexc == NULL) { PyErr_Clear(); } + else { + PyErr_SetObject(exctype, tbexc); + return; + } } PyObject *formatted = _PyXI_excinfo_format(info); @@ -1630,13 +1640,17 @@ _PyXI_excinfo_AsObject(_PyXI_excinfo *info) } -int -_PyXI_InitExcInfo(_PyXI_excinfo *info, PyObject *exc) +_PyXI_excinfo * +_PyXI_NewExcInfo(PyObject *exc) { assert(!PyErr_Occurred()); if (exc == NULL || exc == Py_None) { PyErr_SetString(PyExc_ValueError, "missing exc"); - return -1; + return NULL; + } + _PyXI_excinfo *info = PyMem_RawCalloc(1, sizeof(_PyXI_excinfo)); + if (info == NULL) { + return NULL; } const char *failure; if (PyExceptionInstance_Check(exc) || PyExceptionClass_Check(exc)) { @@ -1646,10 +1660,18 @@ _PyXI_InitExcInfo(_PyXI_excinfo *info, PyObject *exc) failure = _PyXI_excinfo_InitFromObject(info, exc); } if (failure != NULL) { + PyMem_RawFree(info); PyErr_SetString(PyExc_Exception, failure); - return -1; + return NULL; } - return 0; + return info; +} + +void +_PyXI_FreeExcInfo(_PyXI_excinfo *info) +{ + _PyXI_excinfo_clear(info); + PyMem_RawFree(info); } PyObject * @@ -1664,12 +1686,6 @@ _PyXI_ExcInfoAsObject(_PyXI_excinfo *info) return _PyXI_excinfo_AsObject(info); } -void -_PyXI_ClearExcInfo(_PyXI_excinfo *info) -{ - _PyXI_excinfo_clear(info); -} - /***************************/ /* short-term data sharing */ @@ -1727,70 +1743,263 @@ _PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp) return -1; } +/* exception overrides */ + +struct error_override { + // The kind of error to propagate. + _PyXI_errcode code; + // The propagated message. + const char *msg; + int msg_owned; +}; // _PyXI_error_override + +#define ERROR_OVERRIDE_INIT (_PyXI_error_override){ .code = _PyXI_ERR_NO_ERROR } + +void +clear_error_override(_PyXI_error_override *override) +{ + if (override->msg != NULL && override->msg_owned) { + PyMem_RawFree((void*)override->msg); + } + *override = ERROR_OVERRIDE_INIT; +} + +_PyXI_error_override * +_PyXI_NewErrorOverride(void) +{ + _PyXI_error_override *override = + PyMem_RawMalloc(sizeof(_PyXI_error_override)); + if (override == NULL) { + PyErr_NoMemory(); + return NULL; + } + *override = ERROR_OVERRIDE_INIT; + return override; +} + +void +_PyXI_FreeErrorOverride(_PyXI_error_override *override) +{ + clear_error_override(override); + PyMem_RawFree(override); +} + +_PyXI_errcode +_PyXI_GetErrorOverrideCode(_PyXI_error_override *override) +{ + if (override == NULL) { + return _PyXI_ERR_NO_ERROR; + } + return override->code; +} + +void +_PyXI_SetErrorOverrideUTF8(_PyXI_error_override *override, + _PyXI_errcode code, const char *msg) +{ + *override = (_PyXI_error_override){ + .code = code, + .msg = msg, + .msg_owned = 0, + }; +} + +int +_PyXI_SetErrorOverride(_PyXI_error_override *override, + _PyXI_errcode code, PyObject *obj) +{ + PyObject *msgobj = PyObject_Str(obj); + if (msgobj == NULL) { + return -1; + } + // This will leak if not paired with clear_error_override(). + // That happens automatically in _capture_current_exception(). + const char *msg = _copy_string_obj_raw(msgobj, NULL); + Py_DECREF(msgobj); + if (PyErr_Occurred()) { + return -1; + } + *override = (_PyXI_error_override){ + .code = code, + .msg = msg, + .msg_owned = 1, + }; + return 0; +} + /* shared exceptions */ +typedef struct _sharedexception { + // The originating interpreter. + PyInterpreterState *interp; + // The error to propagate, if different from the uncaught exception. + _PyXI_error_override *override; + _PyXI_error_override _override; + // The exception information to propagate, if applicable. + // This is populated only for some error codes, + // but always for _PyXI_ERR_UNCAUGHT_EXCEPTION. + _PyXI_excinfo uncaught; +} _PyXI_error; + +static void +xi_error_clear(_PyXI_error *err) +{ + if (err->override != NULL) { + clear_error_override(err->override); + } + _PyXI_excinfo_clear(&err->uncaught); +} + +static int +xi_error_is_set(_PyXI_error *error) +{ + if (error->override != NULL) { + assert(error->override->code != _PyXI_ERR_NO_ERROR); + return 1; + } + return excinfo_is_set(&error->uncaught); +} + +static int +xi_error_has_override(_PyXI_error *err) +{ + if (err->override == NULL) { + return 0; + } + return (err->override->code != _PyXI_ERR_NO_ERROR + && err->override->code != _PyXI_ERR_UNCAUGHT_EXCEPTION); +} + +static void +xi_error_set_override(_PyXI_error *err, _PyXI_error_override *override) +{ + assert(err->override == NULL || err->override->msg == NULL); + if (override != NULL) { + err->override = &err->_override; + err->_override = *override; + // The caller still owns override->msg. + err->override->msg_owned = 0; + } + else if (err->override != NULL) { + clear_error_override(err->override); + err->override = NULL; + } +} + +static void +xi_error_set_override_code(_PyXI_error *err, _PyXI_errcode code) +{ + _PyXI_error_override override = ERROR_OVERRIDE_INIT; + override.code = code; + xi_error_set_override(err, &override); +} + static const char * -_PyXI_InitError(_PyXI_error *error, PyObject *excobj, _PyXI_errcode code) +_PyXI_InitError(_PyXI_error *error, + PyObject *excobj, _PyXI_error_override *override) { + PyThreadState *tstate = PyThreadState_Get(); + assert(!_PyErr_Occurred(tstate)); + assert(override != NULL || excobj != NULL); if (error->interp == NULL) { - error->interp = PyInterpreterState_Get(); + error->interp = tstate->interp; + } + + _PyXI_error_override err = { + .code = _PyXI_ERR_UNCAUGHT_EXCEPTION, + }; + if (override != NULL) { + err = *override; + *override = (_PyXI_error_override){0}; } const char *failure = NULL; - if (code == _PyXI_ERR_UNCAUGHT_EXCEPTION) { - // There is an unhandled exception we need to propagate. + if (err.code == _PyXI_ERR_UNCAUGHT_EXCEPTION) { + // There is an unhandled exception we need to preserve. + assert(err.msg == NULL); failure = _PyXI_excinfo_InitFromException(&error->uncaught, excobj); if (failure != NULL) { // We failed to initialize error->uncaught. // XXX Print the excobj/traceback? Emit a warning? // XXX Print the current exception/traceback? if (PyErr_ExceptionMatches(PyExc_MemoryError)) { - error->code = _PyXI_ERR_NO_MEMORY; + err.code = _PyXI_ERR_NO_MEMORY; } else { - error->code = _PyXI_ERR_OTHER; + err.code = _PyXI_ERR_OTHER; } PyErr_Clear(); } - else { - error->code = code; - } - assert(error->code != _PyXI_ERR_NO_ERROR); } else { // There is an error code we need to propagate. assert(excobj == NULL); - assert(code != _PyXI_ERR_NO_ERROR); - error->code = code; + assert(err.code != _PyXI_ERR_NO_ERROR); _PyXI_excinfo_clear(&error->uncaught); + if (override != NULL) { + error->_override = *override; + *override = (_PyXI_error_override){0}; + } + else { + // XXX + } + error->_override = *override; + error->override = &error->_override; + } + + if (err.code == _PyXI_ERR_UNCAUGHT_EXCEPTION) { + error->override = NULL; + } + else { + assert(err.code != _PyXI_ERR_NO_ERROR); + error->_override = err; + error->override = &error->_override; } return failure; } -PyObject * +static PyObject * _PyXI_ApplyError(_PyXI_error *error) { PyThreadState *tstate = PyThreadState_Get(); - if (error->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) { + _PyXI_errcode code = _PyXI_ERR_UNCAUGHT_EXCEPTION; + if (error->override != NULL) { + code = error->override->code; + assert(code != _PyXI_ERR_NO_ERROR); + } + + if (code == _PyXI_ERR_UNCAUGHT_EXCEPTION) { // We will raise an exception that proxies the propagated exception. return _PyXI_excinfo_AsObject(&error->uncaught); } - else if (error->code == _PyXI_ERR_NOT_SHAREABLE) { + else if (code == _PyXI_ERR_NOT_SHAREABLE) { // Propagate the exception directly. assert(!_PyErr_Occurred(tstate)); - _set_xid_lookup_failure(tstate, NULL, error->uncaught.msg, NULL); + PyObject *cause = NULL; + if (excinfo_is_set(&error->uncaught)) { + // Maybe instead set a PyExc_ExceptionSnapshot as __cause__? + // That type doesn't exist currently + // but would look like interpreters.ExecutionFailed. + _PyXI_excinfo_Apply(&error->uncaught, PyExc_Exception); + cause = _PyErr_GetRaisedException(tstate); + } + const char *msg = error->override != NULL + ? error->override->msg + : error->uncaught.msg; + _set_xid_lookup_failure(tstate, NULL, msg, cause); } else { // Raise an exception corresponding to the code. - assert(error->code != _PyXI_ERR_NO_ERROR); - (void)_PyXI_ApplyErrorCode(error->code, error->interp); - if (error->uncaught.type.name != NULL || error->uncaught.msg != NULL) { + (void)_PyXI_ApplyErrorCode(code, error->interp); + assert(error->override == NULL || error->override->msg == NULL); + if (excinfo_is_set(&error->uncaught)) { // __context__ will be set to a proxy of the propagated exception. - PyObject *exc = PyErr_GetRaisedException(); + // (or use PyExc_ExceptionSnapshot like _PyXI_ERR_NOT_SHAREABLE?) + PyObject *exc = _PyErr_GetRaisedException(tstate); _PyXI_excinfo_Apply(&error->uncaught, PyExc_InterpreterError); - PyObject *exc2 = PyErr_GetRaisedException(); + PyObject *exc2 = _PyErr_GetRaisedException(tstate); PyException_SetContext(exc, exc2); - PyErr_SetRaisedException(exc); + _PyErr_SetRaisedException(tstate, exc); } } assert(PyErr_Occurred()); @@ -2164,11 +2373,11 @@ _create_sharedns(PyObject *names) return NULL; } -static void _propagate_not_shareable_error(_PyXI_errcode *); +static void _propagate_not_shareable_error(_PyXI_error_override *); static int _fill_sharedns(_PyXI_namespace *ns, PyObject *nsobj, - xidata_fallback_t fallback, _PyXI_errcode *p_errcode) + xidata_fallback_t fallback, _PyXI_error_override *p_err) { // All items are expected to be shareable. assert(_sharedns_check_counts(ns)); @@ -2176,8 +2385,8 @@ _fill_sharedns(_PyXI_namespace *ns, PyObject *nsobj, assert(ns->numvalues == 0); for (Py_ssize_t i=0; i < ns->maxitems; i++) { if (_sharednsitem_copy_from_ns(&ns->items[i], nsobj, fallback) < 0) { - if (p_errcode != NULL) { - _propagate_not_shareable_error(p_errcode); + if (p_err != NULL) { + _propagate_not_shareable_error(p_err); } // Clear out the ones we set so far. for (Py_ssize_t j=0; j < i; j++) { @@ -2244,18 +2453,6 @@ _apply_sharedns(_PyXI_namespace *ns, PyObject *nsobj, PyObject *dflt) /* switched-interpreter sessions */ /*********************************/ -struct xi_session_error { - // This is set if the interpreter is entered and raised an exception - // that needs to be handled in some special way during exit. - _PyXI_errcode *override; - // This is set if exit captured an exception to propagate. - _PyXI_error *info; - - // -- pre-allocated memory -- - _PyXI_error _info; - _PyXI_errcode _override; -}; - struct xi_session { #define SESSION_UNUSED 0 #define SESSION_ACTIVE 1 @@ -2288,8 +2485,6 @@ struct xi_session { // once the session exits. Do not access this directly; use // _PyXI_Preserve() and _PyXI_GetPreserved() instead; PyObject *_preserved; - - struct xi_session_error error; }; _PyXI_session * @@ -2317,25 +2512,9 @@ _session_is_active(_PyXI_session *session) return session->status == SESSION_ACTIVE; } -static int -_session_pop_error(_PyXI_session *session, struct xi_session_error *err) -{ - if (session->error.info == NULL) { - assert(session->error.override == NULL); - *err = (struct xi_session_error){0}; - return 0; - } - *err = session->error; - err->info = &err->_info; - if (err->override != NULL) { - err->override = &err->_override; - } - session->error = (struct xi_session_error){0}; - return 1; -} - -static int _ensure_main_ns(_PyXI_session *, _PyXI_errcode *); -static inline void _session_set_error(_PyXI_session *, _PyXI_errcode); +static int _ensure_main_ns(_PyXI_session *, _PyXI_error_override *); +static void _capture_current_exception(PyThreadState *, _PyXI_error_override *, + _PyXI_error *); /* enter/exit a cross-interpreter session */ @@ -2351,10 +2530,6 @@ _enter_session(_PyXI_session *session, PyInterpreterState *interp) // Set elsewhere and cleared in _exit_session(). assert(!session->running); assert(session->main_ns == NULL); - // Set elsewhere and cleared in _capture_current_exception(). - assert(session->error.override == NULL); - // Set elsewhere and cleared in _PyXI_Exit(). - assert(session->error.info == NULL); // Switch to interpreter. PyThreadState *tstate = PyThreadState_Get(); @@ -2409,16 +2584,13 @@ _exit_session(_PyXI_session *session) assert(!session->own_init_tstate); } - assert(session->error.info == NULL); - assert(session->error.override == _PyXI_ERR_NO_ERROR); - *session = (_PyXI_session){0}; } static void -_propagate_not_shareable_error(_PyXI_errcode *p_errcode) +_propagate_not_shareable_error(_PyXI_error_override *override) { - assert(p_errcode != NULL); + assert(override != NULL); PyThreadState *tstate = PyThreadState_Get(); PyObject *exctype = get_notshareableerror_type(tstate); if (exctype == NULL) { @@ -2428,7 +2600,9 @@ _propagate_not_shareable_error(_PyXI_errcode *p_errcode) } if (PyErr_ExceptionMatches(exctype)) { // We want to propagate the exception directly. - *p_errcode = _PyXI_ERR_NOT_SHAREABLE; + *override = (_PyXI_error_override){ + .code = _PyXI_ERR_NOT_SHAREABLE, + }; } } @@ -2437,6 +2611,8 @@ _PyXI_Enter(_PyXI_session *session, PyInterpreterState *interp, PyObject *nsupdates, _PyXI_session_result *result) { + PyThreadState *tstate = _PyThreadState_GET(); + // Convert the attrs for cross-interpreter use. _PyXI_namespace *sharedns = NULL; if (nsupdates != NULL) { @@ -2457,16 +2633,16 @@ _PyXI_Enter(_PyXI_session *session, } // For now we limit it to shareable objects. xidata_fallback_t fallback = _PyXIDATA_XIDATA_ONLY; - _PyXI_errcode errcode = _PyXI_ERR_NO_ERROR; - if (_fill_sharedns(sharedns, nsupdates, fallback, &errcode) < 0) { - assert(PyErr_Occurred()); - assert(session->error.info == NULL); - if (errcode == _PyXI_ERR_NO_ERROR) { - errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION; + _PyXI_error_override _err = ERROR_OVERRIDE_INIT; + if (_fill_sharedns(sharedns, nsupdates, fallback, &_err) < 0) { + assert(_PyErr_Occurred(tstate)); + if (_err.code == _PyXI_ERR_NO_ERROR) { + _err.code = _PyXI_ERR_UNCAUGHT_EXCEPTION; } _destroy_sharedns(sharedns); if (result != NULL) { - result->errcode = errcode; + assert(_err.msg == NULL); + result->errcode = _err.code; } return -1; } @@ -2475,52 +2651,54 @@ _PyXI_Enter(_PyXI_session *session, // Switch to the requested interpreter (if necessary). _enter_session(session, interp); - _PyXI_errcode errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION; + _PyXI_error_override override = ERROR_OVERRIDE_INIT; + override.code = _PyXI_ERR_UNCAUGHT_EXCEPTION; + tstate = _PyThreadState_GET(); // Ensure this thread owns __main__. if (_PyInterpreterState_SetRunningMain(interp) < 0) { // In the case where we didn't switch interpreters, it would // be more efficient to leave the exception in place and return // immediately. However, life is simpler if we don't. - errcode = _PyXI_ERR_ALREADY_RUNNING; + override.code = _PyXI_ERR_ALREADY_RUNNING; goto error; } session->running = 1; // Apply the cross-interpreter data. if (sharedns != NULL) { - if (_ensure_main_ns(session, &errcode) < 0) { + if (_ensure_main_ns(session, &override) < 0) { goto error; } if (_apply_sharedns(sharedns, session->main_ns, NULL) < 0) { - errcode = _PyXI_ERR_APPLY_NS_FAILURE; + override.code = _PyXI_ERR_APPLY_NS_FAILURE; goto error; } _destroy_sharedns(sharedns); } - errcode = _PyXI_ERR_NO_ERROR; - assert(!PyErr_Occurred()); + override.code = _PyXI_ERR_NO_ERROR; + assert(!_PyErr_Occurred(tstate)); return 0; error: // We want to propagate all exceptions here directly (best effort). - assert(errcode != _PyXI_ERR_NO_ERROR); - _session_set_error(session, errcode); - assert(!PyErr_Occurred()); + assert(override.code != _PyXI_ERR_NO_ERROR); + _PyXI_error err = {0}; + _capture_current_exception(tstate, &override, &err); + assert(!_PyErr_Occurred(tstate)); // Exit the session. - struct xi_session_error err; - (void)_session_pop_error(session, &err); _exit_session(session); + tstate = _PyThreadState_GET(); if (sharedns != NULL) { _destroy_sharedns(sharedns); } // Apply the error from the other interpreter. - PyObject *excinfo = _PyXI_ApplyError(err.info); - _PyXI_excinfo_clear(&err.info->uncaught); + PyObject *excinfo = _PyXI_ApplyError(&err); + xi_error_clear(&err); if (excinfo != NULL) { if (result != NULL) { result->excinfo = excinfo; @@ -2529,84 +2707,95 @@ _PyXI_Enter(_PyXI_session *session, #ifdef Py_DEBUG fprintf(stderr, "_PyXI_Enter(): uncaught exception discarded"); #endif + Py_DECREF(excinfo); } } - assert(PyErr_Occurred()); + assert(_PyErr_Occurred(tstate)); return -1; } static int _pop_preserved(_PyXI_session *, _PyXI_namespace **, PyObject **, - _PyXI_errcode *); + _PyXI_error_override *); static int _finish_preserved(_PyXI_namespace *, PyObject **); int -_PyXI_Exit(_PyXI_session *session, _PyXI_errcode errcode, +_PyXI_Exit(_PyXI_session *session, _PyXI_error_override *override, _PyXI_session_result *result) { + PyThreadState *tstate = _PyThreadState_GET(); int res = 0; // Capture the raised exception, if any. - assert(session->error.info == NULL); - if (PyErr_Occurred()) { - _session_set_error(session, errcode); - assert(!PyErr_Occurred()); + _PyXI_error err = {0}; + if (override != NULL && override->code == _PyXI_ERR_NO_ERROR) { + assert(override->msg == NULL); + override = NULL; + } + if (_PyErr_Occurred(tstate)) { + _capture_current_exception(tstate, override, &err); + assert(!_PyErr_Occurred(tstate)); } else { - assert(errcode == _PyXI_ERR_NO_ERROR); - assert(session->error.override == NULL); + assert(override == NULL); } // Capture the preserved namespace. _PyXI_namespace *preserved = NULL; PyObject *preservedobj = NULL; if (result != NULL) { - errcode = _PyXI_ERR_NO_ERROR; - if (_pop_preserved(session, &preserved, &preservedobj, &errcode) < 0) { - if (session->error.info != NULL) { + _PyXI_error_override _override = ERROR_OVERRIDE_INIT; + if (_pop_preserved( + session, &preserved, &preservedobj, &_override) < 0) + { + if (xi_error_is_set(&err)) { // XXX Chain the exception (i.e. set __context__)? PyErr_FormatUnraisable( "Exception ignored while capturing preserved objects"); + clear_error_override(&_override); } else { - _session_set_error(session, errcode); + if (_override.code == _PyXI_ERR_NO_ERROR) { + _override.code = _PyXI_ERR_UNCAUGHT_EXCEPTION; + } + _capture_current_exception(tstate, &_override, &err); } } } // Exit the session. - struct xi_session_error err; - (void)_session_pop_error(session, &err); _exit_session(session); + tstate = _PyThreadState_GET(); // Restore the preserved namespace. assert(preserved == NULL || preservedobj == NULL); if (_finish_preserved(preserved, &preservedobj) < 0) { assert(preservedobj == NULL); - if (err.info != NULL) { + if (xi_error_is_set(&err)) { // XXX Chain the exception (i.e. set __context__)? PyErr_FormatUnraisable( "Exception ignored while capturing preserved objects"); } else { - errcode = _PyXI_ERR_PRESERVE_FAILURE; - _propagate_not_shareable_error(&errcode); + xi_error_set_override_code(&err, _PyXI_ERR_PRESERVE_FAILURE); + _propagate_not_shareable_error(err.override); } } if (result != NULL) { result->preserved = preservedobj; - result->errcode = errcode; + result->errcode = err.override != NULL + ? err.override->code + : _PyXI_ERR_NO_ERROR; } // Apply the error from the other interpreter, if any. - if (err.info != NULL) { + if (xi_error_is_set(&err)) { res = -1; - assert(!PyErr_Occurred()); - PyObject *excinfo = _PyXI_ApplyError(err.info); - _PyXI_excinfo_clear(&err.info->uncaught); + assert(!_PyErr_Occurred(tstate)); + PyObject *excinfo = _PyXI_ApplyError(&err); if (excinfo == NULL) { - assert(PyErr_Occurred()); - if (result != NULL) { + assert(_PyErr_Occurred(tstate)); + if (result != NULL && !xi_error_has_override(&err)) { _PyXI_ClearResult(result); *result = (_PyXI_session_result){ .errcode = _PyXI_ERR_EXC_PROPAGATION_FAILURE, @@ -2620,7 +2809,9 @@ _PyXI_Exit(_PyXI_session *session, _PyXI_errcode errcode, #ifdef Py_DEBUG fprintf(stderr, "_PyXI_Exit(): uncaught exception discarded"); #endif + Py_DECREF(excinfo); } + xi_error_clear(&err); } return res; } @@ -2629,20 +2820,25 @@ _PyXI_Exit(_PyXI_session *session, _PyXI_errcode errcode, /* in an active cross-interpreter session */ static void -_capture_current_exception(_PyXI_session *session) +_capture_current_exception(PyThreadState *tstate, + _PyXI_error_override *override, + _PyXI_error *error) { - assert(session->error.info == NULL); + assert(!xi_error_is_set(error)); if (!PyErr_Occurred()) { - assert(session->error.override == NULL); + assert(error->override == NULL); + xi_error_set_override(error, override); return; } // Handle the exception override. - _PyXI_errcode *override = session->error.override; - session->error.override = NULL; - _PyXI_errcode errcode = override != NULL - ? *override - : _PyXI_ERR_UNCAUGHT_EXCEPTION; + _PyXI_error_override _override = { + .code = _PyXI_ERR_UNCAUGHT_EXCEPTION, + }; + if (override == NULL) { + override = &_override; + } + _PyXI_errcode errcode = override->code; // Pop the exception object. PyObject *excval = NULL; @@ -2662,20 +2858,13 @@ _capture_current_exception(_PyXI_session *session) } // Capture the exception. - _PyXI_error *err = &session->error._info; - *err = (_PyXI_error){ - .interp = session->init_tstate->interp, + *error = (_PyXI_error){ + .interp = tstate->interp, }; - const char *failure; - if (excval == NULL) { - failure = _PyXI_InitError(err, NULL, errcode); - } - else { - failure = _PyXI_InitError(err, excval, _PyXI_ERR_UNCAUGHT_EXCEPTION); - Py_DECREF(excval); - if (failure == NULL && override != NULL) { - err->code = errcode; - } + const char *failure = _PyXI_InitError(error, excval, &_override); + Py_XDECREF(excval); + if (failure == NULL) { + xi_error_set_override(error, override); } // Handle capture failure. @@ -2684,32 +2873,15 @@ _capture_current_exception(_PyXI_session *session) fprintf(stderr, "RunFailedError: script raised an uncaught exception (%s)", failure); - err = NULL; + xi_error_clear(error); } // Finished! assert(!PyErr_Occurred()); - session->error.info = err; -} - -static inline void -_session_set_error(_PyXI_session *session, _PyXI_errcode errcode) -{ - assert(_session_is_active(session)); - assert(PyErr_Occurred()); - if (errcode == _PyXI_ERR_NO_ERROR) { - // We're a bit forgiving here. - errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION; - } - if (errcode != _PyXI_ERR_UNCAUGHT_EXCEPTION) { - session->error._override = errcode; - session->error.override = &session->error._override; - } - _capture_current_exception(session); } static int -_ensure_main_ns(_PyXI_session *session, _PyXI_errcode *p_errcode) +_ensure_main_ns(_PyXI_session *session, _PyXI_error_override *err) { assert(_session_is_active(session)); if (session->main_ns != NULL) { @@ -2718,16 +2890,20 @@ _ensure_main_ns(_PyXI_session *session, _PyXI_errcode *p_errcode) // Cache __main__.__dict__. PyObject *main_mod = _Py_GetMainModule(session->init_tstate); if (_Py_CheckMainModule(main_mod) < 0) { - if (p_errcode != NULL) { - *p_errcode = _PyXI_ERR_MAIN_NS_FAILURE; + if (err != NULL) { + *err = (_PyXI_error_override){ + .code = _PyXI_ERR_MAIN_NS_FAILURE, + }; } return -1; } PyObject *ns = PyModule_GetDict(main_mod); // borrowed Py_DECREF(main_mod); if (ns == NULL) { - if (p_errcode != NULL) { - *p_errcode = _PyXI_ERR_MAIN_NS_FAILURE; + if (err != NULL) { + *err = (_PyXI_error_override){ + .code = _PyXI_ERR_MAIN_NS_FAILURE, + }; } return -1; } @@ -2736,13 +2912,13 @@ _ensure_main_ns(_PyXI_session *session, _PyXI_errcode *p_errcode) } PyObject * -_PyXI_GetMainNamespace(_PyXI_session *session, _PyXI_errcode *p_errcode) +_PyXI_GetMainNamespace(_PyXI_session *session, _PyXI_error_override *err) { if (!_session_is_active(session)) { PyErr_SetString(PyExc_RuntimeError, "session not active"); return NULL; } - if (_ensure_main_ns(session, p_errcode) < 0) { + if (_ensure_main_ns(session, err) < 0) { return NULL; } return session->main_ns; @@ -2752,9 +2928,12 @@ _PyXI_GetMainNamespace(_PyXI_session *session, _PyXI_errcode *p_errcode) static int _pop_preserved(_PyXI_session *session, _PyXI_namespace **p_xidata, PyObject **p_obj, - _PyXI_errcode *p_errcode) + _PyXI_error_override *p_err) { + _PyXI_error_override err = ERROR_OVERRIDE_INIT; + _PyXI_namespace *xidata = NULL; assert(_PyThreadState_GET() == session->init_tstate); // active session + if (session->_preserved == NULL) { *p_xidata = NULL; *p_obj = NULL; @@ -2772,10 +2951,8 @@ _pop_preserved(_PyXI_session *session, // We did switch interpreters. Py_ssize_t len = PyDict_Size(session->_preserved); if (len < 0) { - if (p_errcode != NULL) { - *p_errcode = _PyXI_ERR_PRESERVE_FAILURE; - } - return -1; + err.code = _PyXI_ERR_PRESERVE_FAILURE; + goto error; } else if (len == 0) { *p_xidata = NULL; @@ -2783,29 +2960,31 @@ _pop_preserved(_PyXI_session *session, else { _PyXI_namespace *xidata = _create_sharedns(session->_preserved); if (xidata == NULL) { - if (p_errcode != NULL) { - *p_errcode = _PyXI_ERR_PRESERVE_FAILURE; - } - return -1; + err.code = _PyXI_ERR_PRESERVE_FAILURE; + goto error; } - _PyXI_errcode errcode = _PyXI_ERR_NO_ERROR; if (_fill_sharedns(xidata, session->_preserved, - _PyXIDATA_FULL_FALLBACK, &errcode) < 0) + _PyXIDATA_FULL_FALLBACK, &err) < 0) { - assert(session->error.info == NULL); - if (errcode != _PyXI_ERR_NOT_SHAREABLE) { - errcode = _PyXI_ERR_PRESERVE_FAILURE; - } - if (p_errcode != NULL) { - *p_errcode = errcode; + if (err.code != _PyXI_ERR_NOT_SHAREABLE) { + assert(err.msg != NULL); + err.code = _PyXI_ERR_PRESERVE_FAILURE; } - _destroy_sharedns(xidata); - return -1; + goto error; } *p_xidata = xidata; } Py_CLEAR(session->_preserved); return 0; + +error: + if (p_err != NULL) { + *p_err = err; + } + if (xidata != NULL) { + _destroy_sharedns(xidata); + } + return -1; } static int @@ -2835,8 +3014,9 @@ _finish_preserved(_PyXI_namespace *xidata, PyObject **p_preserved) int _PyXI_Preserve(_PyXI_session *session, const char *name, PyObject *value, - _PyXI_errcode *p_errcode) + _PyXI_error_override *p_err) { + _PyXI_error_override err = ERROR_OVERRIDE_INIT; if (!_session_is_active(session)) { PyErr_SetString(PyExc_RuntimeError, "session not active"); return -1; @@ -2846,20 +3026,22 @@ _PyXI_Preserve(_PyXI_session *session, const char *name, PyObject *value, if (session->_preserved == NULL) { set_exc_with_cause(PyExc_RuntimeError, "failed to initialize preserved objects"); - if (p_errcode != NULL) { - *p_errcode = _PyXI_ERR_PRESERVE_FAILURE; - } - return -1; + err.code = _PyXI_ERR_PRESERVE_FAILURE; + goto error; } } if (PyDict_SetItemString(session->_preserved, name, value) < 0) { set_exc_with_cause(PyExc_RuntimeError, "failed to preserve object"); - if (p_errcode != NULL) { - *p_errcode = _PyXI_ERR_PRESERVE_FAILURE; - } - return -1; + err.code = _PyXI_ERR_PRESERVE_FAILURE; + goto error; } return 0; + +error: + if (p_err != NULL) { + *p_err = err; + } + return -1; } PyObject * diff --git a/Python/crossinterp_data_lookup.h b/Python/crossinterp_data_lookup.h index b16f38b847fc66..0a495f90176d5e 100644 --- a/Python/crossinterp_data_lookup.h +++ b/Python/crossinterp_data_lookup.h @@ -88,6 +88,34 @@ _PyXIData_FormatNotShareableError(PyThreadState *tstate, va_end(vargs); } +int +_PyXI_UnwrapNotShareableError(PyThreadState * tstate, + _PyXI_error_override *override) +{ + PyObject *exctype = get_notshareableerror_type(tstate); + assert(exctype != NULL); + if (!_PyErr_ExceptionMatches(tstate, exctype)) { + return -1; + } + PyObject *exc = _PyErr_GetRaisedException(tstate); + if (override != NULL) { + _PyXI_errcode code = _PyXI_ERR_NOT_SHAREABLE; + if (_PyXI_SetErrorOverride(override, code, exc) < 0) { + return -1; + } + } + PyObject *cause = PyException_GetCause(exc); + if (cause != NULL) { + Py_DECREF(exc); + exc = cause; + } + else { + assert(PyException_GetContext(exc) == NULL); + } + _PyErr_SetRaisedException(tstate, exc); + return 0; +} + _PyXIData_getdata_t _PyXIData_Lookup(PyThreadState *tstate, PyObject *obj) diff --git a/Python/crossinterp_exceptions.h b/Python/crossinterp_exceptions.h index ca4ca1cf123e49..06034940c1a221 100644 --- a/Python/crossinterp_exceptions.h +++ b/Python/crossinterp_exceptions.h @@ -7,13 +7,6 @@ _ensure_current_cause(PyThreadState *tstate, PyObject *cause) } PyObject *exc = _PyErr_GetRaisedException(tstate); assert(exc != NULL); - PyObject *ctx = PyException_GetContext(exc); - if (ctx == NULL) { - PyException_SetContext(exc, Py_NewRef(cause)); - } - else { - Py_DECREF(ctx); - } assert(PyException_GetCause(exc) == NULL); PyException_SetCause(exc, Py_NewRef(cause)); _PyErr_SetRaisedException(tstate, exc); From 738fc6cf21c7d88142512b5ba0869919e46de94e Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 11 Jun 2025 16:48:35 -0600 Subject: [PATCH 2/2] fixes --- Include/internal/pycore_crossinterp.h | 31 +- Modules/_interpretersmodule.c | 36 +-- Python/crossinterp.c | 399 +++++++++++++------------- Python/crossinterp_data_lookup.h | 7 +- 4 files changed, 232 insertions(+), 241 deletions(-) diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index 267b6ea05dbb65..81faffac194171 100644 --- a/Include/internal/pycore_crossinterp.h +++ b/Include/internal/pycore_crossinterp.h @@ -322,23 +322,20 @@ typedef enum error_code { _PyXI_ERR_NOT_SHAREABLE = -9, } _PyXI_errcode; -typedef struct error_override _PyXI_error_override; - -PyAPI_FUNC(_PyXI_error_override *) _PyXI_NewErrorOverride(void); -PyAPI_FUNC(void) _PyXI_FreeErrorOverride(_PyXI_error_override *); -PyAPI_FUNC(_PyXI_errcode) _PyXI_GetErrorOverrideCode(_PyXI_error_override *); -PyAPI_FUNC(int) _PyXI_SetErrorOverride( - _PyXI_error_override *, - _PyXI_errcode, - PyObject *); -PyAPI_FUNC(void) _PyXI_SetErrorOverrideUTF8( - _PyXI_error_override *, +typedef struct xi_failure _PyXI_failure; + +PyAPI_FUNC(_PyXI_failure *) _PyXI_NewFailure(void); +PyAPI_FUNC(void) _PyXI_FreeFailure(_PyXI_failure *); +PyAPI_FUNC(_PyXI_errcode) _PyXI_GetFailureCode(_PyXI_failure *); +PyAPI_FUNC(int) _PyXI_InitFailure(_PyXI_failure *, _PyXI_errcode, PyObject *); +PyAPI_FUNC(void) _PyXI_InitFailureUTF8( + _PyXI_failure *, _PyXI_errcode, const char *); PyAPI_FUNC(int) _PyXI_UnwrapNotShareableError( PyThreadState *, - _PyXI_error_override *); + _PyXI_failure *); // A cross-interpreter session involves entering an interpreter @@ -370,19 +367,21 @@ PyAPI_FUNC(int) _PyXI_Enter( _PyXI_session_result *); PyAPI_FUNC(int) _PyXI_Exit( _PyXI_session *, - _PyXI_error_override *, + _PyXI_failure *, _PyXI_session_result *); PyAPI_FUNC(PyObject *) _PyXI_GetMainNamespace( _PyXI_session *, - _PyXI_error_override *); + _PyXI_failure *); PyAPI_FUNC(int) _PyXI_Preserve( _PyXI_session *, const char *, PyObject *, - _PyXI_error_override *); -PyAPI_FUNC(PyObject *) _PyXI_GetPreserved(_PyXI_session_result *, const char *); + _PyXI_failure *); +PyAPI_FUNC(PyObject *) _PyXI_GetPreserved( + _PyXI_session_result *, + const char *); /*************/ diff --git a/Modules/_interpretersmodule.c b/Modules/_interpretersmodule.c index 94d2ac02c409a8..fdfb3e6dd3482d 100644 --- a/Modules/_interpretersmodule.c +++ b/Modules/_interpretersmodule.c @@ -80,9 +80,9 @@ is_notshareable_raised(PyThreadState *tstate) } static void -unwrap_not_shareable(PyThreadState *tstate, _PyXI_error_override *err) +unwrap_not_shareable(PyThreadState *tstate, _PyXI_failure *failure) { - if (_PyXI_UnwrapNotShareableError(tstate, err) < 0) { + if (_PyXI_UnwrapNotShareableError(tstate, failure) < 0) { _PyErr_Clear(tstate); } } @@ -586,7 +586,7 @@ _interp_call_unpack(struct interp_call *call, static int _make_call(struct interp_call *call, - PyObject **p_result, _PyXI_error_override *err) + PyObject **p_result, _PyXI_failure *failure) { assert(call != NULL && call->func != NULL); PyThreadState *tstate = _PyThreadState_GET(); @@ -597,8 +597,8 @@ _make_call(struct interp_call *call, assert(func == NULL); assert(args == NULL); assert(kwargs == NULL); - _PyXI_SetErrorOverride(err, _PyXI_ERR_OTHER, NULL); - unwrap_not_shareable(tstate, err); + _PyXI_InitFailure(failure, _PyXI_ERR_OTHER, NULL); + unwrap_not_shareable(tstate, failure); return -1; } @@ -615,17 +615,17 @@ _make_call(struct interp_call *call, } static int -_run_script(_PyXIData_t *script, PyObject *ns, _PyXI_error_override *err) +_run_script(_PyXIData_t *script, PyObject *ns, _PyXI_failure *failure) { PyObject *code = _PyXIData_NewObject(script); if (code == NULL) { - _PyXI_SetErrorOverride(err, _PyXI_ERR_NOT_SHAREABLE, NULL); + _PyXI_InitFailure(failure, _PyXI_ERR_NOT_SHAREABLE, NULL); return -1; } PyObject *result = PyEval_EvalCode(code, ns, ns); Py_DECREF(code); if (result == NULL) { - _PyXI_SetErrorOverride(err, _PyXI_ERR_UNCAUGHT_EXCEPTION, NULL); + _PyXI_InitFailure(failure, _PyXI_ERR_UNCAUGHT_EXCEPTION, NULL); return -1; } assert(result == Py_None); @@ -652,13 +652,13 @@ _run_in_interpreter(PyThreadState *tstate, PyInterpreterState *interp, { assert(!_PyErr_Occurred(tstate)); int res = -1; - _PyXI_error_override *override = _PyXI_NewErrorOverride(); - if (override == NULL) { + _PyXI_failure *failure = _PyXI_NewFailure(); + if (failure == NULL) { return -1; } _PyXI_session *session = _PyXI_NewSession(); if (session == NULL) { - _PyXI_FreeErrorOverride(override); + _PyXI_FreeFailure(failure); return -1; } _PyXI_session_result result = {0}; @@ -668,7 +668,7 @@ _run_in_interpreter(PyThreadState *tstate, PyInterpreterState *interp, // If an error occured at this step, it means that interp // was not prepared and switched. _PyXI_FreeSession(session); - _PyXI_FreeErrorOverride(override); + _PyXI_FreeFailure(failure); assert(result.excinfo == NULL); return -1; } @@ -676,18 +676,18 @@ _run_in_interpreter(PyThreadState *tstate, PyInterpreterState *interp, // Run in the interpreter. if (script != NULL) { assert(call == NULL); - PyObject *mainns = _PyXI_GetMainNamespace(session, override); + PyObject *mainns = _PyXI_GetMainNamespace(session, failure); if (mainns == NULL) { goto finally; } - res = _run_script(script, mainns, override); + res = _run_script(script, mainns, failure); } else { assert(call != NULL); PyObject *resobj; - res = _make_call(call, &resobj, override); + res = _make_call(call, &resobj, failure); if (res == 0) { - res = _PyXI_Preserve(session, "resobj", resobj, override); + res = _PyXI_Preserve(session, "resobj", resobj, failure); Py_DECREF(resobj); if (res < 0) { goto finally; @@ -698,10 +698,10 @@ _run_in_interpreter(PyThreadState *tstate, PyInterpreterState *interp, finally: // Clean up and switch back. (void)res; - int exitres = _PyXI_Exit(session, override, &result); + int exitres = _PyXI_Exit(session, failure, &result); assert(res == 0 || exitres != 0); _PyXI_FreeSession(session); - _PyXI_FreeErrorOverride(override); + _PyXI_FreeFailure(failure); res = exitres; if (_PyErr_Occurred(tstate)) { diff --git a/Python/crossinterp.c b/Python/crossinterp.c index 65acb5fc4e60f6..39c7ea698904bd 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -1493,6 +1493,7 @@ _PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype) } else { PyErr_SetObject(exctype, tbexc); + Py_DECREF(tbexc); return; } } @@ -1661,7 +1662,7 @@ _PyXI_NewExcInfo(PyObject *exc) } if (failure != NULL) { PyMem_RawFree(info); - PyErr_SetString(PyExc_Exception, failure); + set_exc_with_cause(PyExc_Exception, failure); return NULL; } return info; @@ -1743,61 +1744,67 @@ _PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp) return -1; } -/* exception overrides */ +/* basic failure info */ -struct error_override { +struct xi_failure { // The kind of error to propagate. _PyXI_errcode code; // The propagated message. const char *msg; int msg_owned; -}; // _PyXI_error_override +}; // _PyXI_failure -#define ERROR_OVERRIDE_INIT (_PyXI_error_override){ .code = _PyXI_ERR_NO_ERROR } +#define XI_FAILURE_INIT (_PyXI_failure){ .code = _PyXI_ERR_NO_ERROR } -void -clear_error_override(_PyXI_error_override *override) +static void +clear_xi_failure(_PyXI_failure *failure) { - if (override->msg != NULL && override->msg_owned) { - PyMem_RawFree((void*)override->msg); + if (failure->msg != NULL && failure->msg_owned) { + PyMem_RawFree((void*)failure->msg); } - *override = ERROR_OVERRIDE_INIT; + *failure = XI_FAILURE_INIT; +} + +static void +copy_xi_failure(_PyXI_failure *dest, _PyXI_failure *src) +{ + *dest = *src; + dest->msg_owned = 0; } -_PyXI_error_override * -_PyXI_NewErrorOverride(void) +_PyXI_failure * +_PyXI_NewFailure(void) { - _PyXI_error_override *override = - PyMem_RawMalloc(sizeof(_PyXI_error_override)); - if (override == NULL) { + _PyXI_failure *failure = PyMem_RawMalloc(sizeof(_PyXI_failure)); + if (failure == NULL) { PyErr_NoMemory(); return NULL; } - *override = ERROR_OVERRIDE_INIT; - return override; + *failure = XI_FAILURE_INIT; + return failure; } void -_PyXI_FreeErrorOverride(_PyXI_error_override *override) +_PyXI_FreeFailure(_PyXI_failure *failure) { - clear_error_override(override); - PyMem_RawFree(override); + clear_xi_failure(failure); + PyMem_RawFree(failure); } _PyXI_errcode -_PyXI_GetErrorOverrideCode(_PyXI_error_override *override) +_PyXI_GetFailureCode(_PyXI_failure *failure) { - if (override == NULL) { + if (failure == NULL) { return _PyXI_ERR_NO_ERROR; } - return override->code; + return failure->code; } void -_PyXI_SetErrorOverrideUTF8(_PyXI_error_override *override, - _PyXI_errcode code, const char *msg) +_PyXI_InitFailureUTF8(_PyXI_failure *failure, + _PyXI_errcode code, const char *msg) { - *override = (_PyXI_error_override){ + *failure = (_PyXI_failure){ .code = code, .msg = msg, .msg_owned = 0, @@ -1805,21 +1812,20 @@ _PyXI_SetErrorOverrideUTF8(_PyXI_error_override *override, } int -_PyXI_SetErrorOverride(_PyXI_error_override *override, - _PyXI_errcode code, PyObject *obj) +_PyXI_InitFailure(_PyXI_failure *failure, _PyXI_errcode code, PyObject *obj) { PyObject *msgobj = PyObject_Str(obj); if (msgobj == NULL) { return -1; } - // This will leak if not paired with clear_error_override(). + // This will leak if not paired with clear_xi_failure(). // That happens automatically in _capture_current_exception(). const char *msg = _copy_string_obj_raw(msgobj, NULL); Py_DECREF(msgobj); if (PyErr_Occurred()) { return -1; } - *override = (_PyXI_error_override){ + *failure = (_PyXI_failure){ .code = code, .msg = msg, .msg_owned = 1, @@ -1829,12 +1835,12 @@ _PyXI_SetErrorOverride(_PyXI_error_override *override, /* shared exceptions */ -typedef struct _sharedexception { +typedef struct { // The originating interpreter. PyInterpreterState *interp; // The error to propagate, if different from the uncaught exception. - _PyXI_error_override *override; - _PyXI_error_override _override; + _PyXI_failure *override; + _PyXI_failure _override; // The exception information to propagate, if applicable. // This is populated only for some error codes, // but always for _PyXI_ERR_UNCAUGHT_EXCEPTION. @@ -1844,8 +1850,9 @@ typedef struct _sharedexception { static void xi_error_clear(_PyXI_error *err) { + err->interp = NULL; if (err->override != NULL) { - clear_error_override(err->override); + clear_xi_failure(err->override); } _PyXI_excinfo_clear(&err->uncaught); } @@ -1855,6 +1862,8 @@ xi_error_is_set(_PyXI_error *error) { if (error->override != NULL) { assert(error->override->code != _PyXI_ERR_NO_ERROR); + assert(error->override->code != _PyXI_ERR_UNCAUGHT_EXCEPTION + || excinfo_is_set(&error->uncaught)); return 1; } return excinfo_is_set(&error->uncaught); @@ -1870,98 +1879,93 @@ xi_error_has_override(_PyXI_error *err) && err->override->code != _PyXI_ERR_UNCAUGHT_EXCEPTION); } -static void -xi_error_set_override(_PyXI_error *err, _PyXI_error_override *override) +static PyObject * +xi_error_resolve_current_exc(PyThreadState *tstate, + _PyXI_failure *override) { - assert(err->override == NULL || err->override->msg == NULL); - if (override != NULL) { - err->override = &err->_override; - err->_override = *override; - // The caller still owns override->msg. - err->override->msg_owned = 0; + assert(override == NULL || override->code != _PyXI_ERR_NO_ERROR); + + PyObject *exc = _PyErr_GetRaisedException(tstate); + if (exc == NULL) { + assert(override == NULL + || override->code != _PyXI_ERR_UNCAUGHT_EXCEPTION); + } + else if (override == NULL) { + // This is equivalent to _PyXI_ERR_UNCAUGHT_EXCEPTION. } - else if (err->override != NULL) { - clear_error_override(err->override); - err->override = NULL; + else if (override->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) { + // We want to actually capture the current exception. + } + else if (exc != NULL) { + // It might make sense to do similarly for other codes. + if (override->code == _PyXI_ERR_ALREADY_RUNNING) { + // We don't need the exception info. + Py_CLEAR(exc); + } + // ...else we want to actually capture the current exception. } + return exc; +} + +static void +xi_error_set_override(PyThreadState *tstate, _PyXI_error *err, + _PyXI_failure *override) +{ + assert(err->override == NULL); + assert(override != NULL); + assert(override->code != _PyXI_ERR_NO_ERROR); + // Use xi_error_set_exc() instead of setting _PyXI_ERR_UNCAUGHT_EXCEPTION.. + assert(override->code != _PyXI_ERR_UNCAUGHT_EXCEPTION); + err->override = &err->_override; + // The caller still owns override->msg. + copy_xi_failure(&err->_override, override); + err->interp = tstate->interp; } static void -xi_error_set_override_code(_PyXI_error *err, _PyXI_errcode code) +xi_error_set_override_code(PyThreadState *tstate, _PyXI_error *err, + _PyXI_errcode code) { - _PyXI_error_override override = ERROR_OVERRIDE_INIT; + _PyXI_failure override = XI_FAILURE_INIT; override.code = code; - xi_error_set_override(err, &override); + xi_error_set_override(tstate, err, &override); } static const char * -_PyXI_InitError(_PyXI_error *error, - PyObject *excobj, _PyXI_error_override *override) +xi_error_set_exc(PyThreadState *tstate, _PyXI_error *err, PyObject *exc) { - PyThreadState *tstate = PyThreadState_Get(); assert(!_PyErr_Occurred(tstate)); - assert(override != NULL || excobj != NULL); - if (error->interp == NULL) { - error->interp = tstate->interp; - } - - _PyXI_error_override err = { - .code = _PyXI_ERR_UNCAUGHT_EXCEPTION, - }; - if (override != NULL) { - err = *override; - *override = (_PyXI_error_override){0}; - } - - const char *failure = NULL; - if (err.code == _PyXI_ERR_UNCAUGHT_EXCEPTION) { - // There is an unhandled exception we need to preserve. - assert(err.msg == NULL); - failure = _PyXI_excinfo_InitFromException(&error->uncaught, excobj); - if (failure != NULL) { - // We failed to initialize error->uncaught. - // XXX Print the excobj/traceback? Emit a warning? - // XXX Print the current exception/traceback? - if (PyErr_ExceptionMatches(PyExc_MemoryError)) { - err.code = _PyXI_ERR_NO_MEMORY; - } - else { - err.code = _PyXI_ERR_OTHER; - } - PyErr_Clear(); - } - } - else { - // There is an error code we need to propagate. - assert(excobj == NULL); - assert(err.code != _PyXI_ERR_NO_ERROR); - _PyXI_excinfo_clear(&error->uncaught); - if (override != NULL) { - error->_override = *override; - *override = (_PyXI_error_override){0}; + assert(!xi_error_is_set(err)); + assert(err->override == NULL); + assert(err->interp == NULL); + assert(exc != NULL); + const char *failure = + _PyXI_excinfo_InitFromException(&err->uncaught, exc); + if (failure != NULL) { + // We failed to initialize err->uncaught. + // XXX Print the excobj/traceback? Emit a warning? + // XXX Print the current exception/traceback? + if (_PyErr_ExceptionMatches(tstate, PyExc_MemoryError)) { + xi_error_set_override_code(tstate, err, _PyXI_ERR_NO_MEMORY); } else { - // XXX + xi_error_set_override_code(tstate, err, _PyXI_ERR_OTHER); } - error->_override = *override; - error->override = &error->_override; - } - - if (err.code == _PyXI_ERR_UNCAUGHT_EXCEPTION) { - error->override = NULL; - } - else { - assert(err.code != _PyXI_ERR_NO_ERROR); - error->_override = err; - error->override = &error->_override; + PyErr_Clear(); } return failure; } static PyObject * -_PyXI_ApplyError(_PyXI_error *error) +_PyXI_ApplyError(_PyXI_error *error, const char *failure) { PyThreadState *tstate = PyThreadState_Get(); + + if (failure != NULL) { + xi_error_clear(error); + return NULL; + } + _PyXI_errcode code = _PyXI_ERR_UNCAUGHT_EXCEPTION; if (error->override != NULL) { code = error->override->code; @@ -1987,6 +1991,7 @@ _PyXI_ApplyError(_PyXI_error *error) ? error->override->msg : error->uncaught.msg; _set_xid_lookup_failure(tstate, NULL, msg, cause); + Py_XDECREF(cause); } else { // Raise an exception corresponding to the code. @@ -2373,20 +2378,22 @@ _create_sharedns(PyObject *names) return NULL; } -static void _propagate_not_shareable_error(_PyXI_error_override *); +static void _propagate_not_shareable_error(PyThreadState *, + _PyXI_failure *); static int _fill_sharedns(_PyXI_namespace *ns, PyObject *nsobj, - xidata_fallback_t fallback, _PyXI_error_override *p_err) + xidata_fallback_t fallback, _PyXI_failure *p_err) { // All items are expected to be shareable. assert(_sharedns_check_counts(ns)); assert(ns->numnames == ns->maxitems); assert(ns->numvalues == 0); + PyThreadState *tstate = PyThreadState_Get(); for (Py_ssize_t i=0; i < ns->maxitems; i++) { if (_sharednsitem_copy_from_ns(&ns->items[i], nsobj, fallback) < 0) { if (p_err != NULL) { - _propagate_not_shareable_error(p_err); + _propagate_not_shareable_error(tstate, p_err); } // Clear out the ones we set so far. for (Py_ssize_t j=0; j < i; j++) { @@ -2512,10 +2519,6 @@ _session_is_active(_PyXI_session *session) return session->status == SESSION_ACTIVE; } -static int _ensure_main_ns(_PyXI_session *, _PyXI_error_override *); -static void _capture_current_exception(PyThreadState *, _PyXI_error_override *, - _PyXI_error *); - /* enter/exit a cross-interpreter session */ @@ -2588,10 +2591,10 @@ _exit_session(_PyXI_session *session) } static void -_propagate_not_shareable_error(_PyXI_error_override *override) +_propagate_not_shareable_error(PyThreadState *tstate, + _PyXI_failure *override) { assert(override != NULL); - PyThreadState *tstate = PyThreadState_Get(); PyObject *exctype = get_notshareableerror_type(tstate); if (exctype == NULL) { PyErr_FormatUnraisable( @@ -2600,12 +2603,17 @@ _propagate_not_shareable_error(_PyXI_error_override *override) } if (PyErr_ExceptionMatches(exctype)) { // We want to propagate the exception directly. - *override = (_PyXI_error_override){ + *override = (_PyXI_failure){ .code = _PyXI_ERR_NOT_SHAREABLE, }; } } + +static int _ensure_main_ns(_PyXI_session *, _PyXI_failure *); +static const char * capture_session_error(_PyXI_session *, _PyXI_error *, + _PyXI_failure *); + int _PyXI_Enter(_PyXI_session *session, PyInterpreterState *interp, PyObject *nsupdates, @@ -2633,7 +2641,7 @@ _PyXI_Enter(_PyXI_session *session, } // For now we limit it to shareable objects. xidata_fallback_t fallback = _PyXIDATA_XIDATA_ONLY; - _PyXI_error_override _err = ERROR_OVERRIDE_INIT; + _PyXI_failure _err = XI_FAILURE_INIT; if (_fill_sharedns(sharedns, nsupdates, fallback, &_err) < 0) { assert(_PyErr_Occurred(tstate)); if (_err.code == _PyXI_ERR_NO_ERROR) { @@ -2651,7 +2659,7 @@ _PyXI_Enter(_PyXI_session *session, // Switch to the requested interpreter (if necessary). _enter_session(session, interp); - _PyXI_error_override override = ERROR_OVERRIDE_INIT; + _PyXI_failure override = XI_FAILURE_INIT; override.code = _PyXI_ERR_UNCAUGHT_EXCEPTION; tstate = _PyThreadState_GET(); @@ -2685,8 +2693,7 @@ _PyXI_Enter(_PyXI_session *session, // We want to propagate all exceptions here directly (best effort). assert(override.code != _PyXI_ERR_NO_ERROR); _PyXI_error err = {0}; - _capture_current_exception(tstate, &override, &err); - assert(!_PyErr_Occurred(tstate)); + const char *failure = capture_session_error(session, &err, &override); // Exit the session. _exit_session(session); @@ -2697,7 +2704,7 @@ _PyXI_Enter(_PyXI_session *session, } // Apply the error from the other interpreter. - PyObject *excinfo = _PyXI_ApplyError(&err); + PyObject *excinfo = _PyXI_ApplyError(&err, failure); xi_error_clear(&err); if (excinfo != NULL) { if (result != NULL) { @@ -2716,11 +2723,11 @@ _PyXI_Enter(_PyXI_session *session, } static int _pop_preserved(_PyXI_session *, _PyXI_namespace **, PyObject **, - _PyXI_error_override *); + _PyXI_failure *); static int _finish_preserved(_PyXI_namespace *, PyObject **); int -_PyXI_Exit(_PyXI_session *session, _PyXI_error_override *override, +_PyXI_Exit(_PyXI_session *session, _PyXI_failure *override, _PyXI_session_result *result) { PyThreadState *tstate = _PyThreadState_GET(); @@ -2728,13 +2735,13 @@ _PyXI_Exit(_PyXI_session *session, _PyXI_error_override *override, // Capture the raised exception, if any. _PyXI_error err = {0}; + const char *failure = NULL; if (override != NULL && override->code == _PyXI_ERR_NO_ERROR) { assert(override->msg == NULL); override = NULL; } if (_PyErr_Occurred(tstate)) { - _capture_current_exception(tstate, override, &err); - assert(!_PyErr_Occurred(tstate)); + failure = capture_session_error(session, &err, override); } else { assert(override == NULL); @@ -2744,26 +2751,30 @@ _PyXI_Exit(_PyXI_session *session, _PyXI_error_override *override, _PyXI_namespace *preserved = NULL; PyObject *preservedobj = NULL; if (result != NULL) { - _PyXI_error_override _override = ERROR_OVERRIDE_INIT; + assert(!_PyErr_Occurred(tstate)); + _PyXI_failure _override = XI_FAILURE_INIT; if (_pop_preserved( session, &preserved, &preservedobj, &_override) < 0) { + assert(preserved == NULL); + assert(preservedobj == NULL); if (xi_error_is_set(&err)) { // XXX Chain the exception (i.e. set __context__)? PyErr_FormatUnraisable( "Exception ignored while capturing preserved objects"); - clear_error_override(&_override); + clear_xi_failure(&_override); } else { if (_override.code == _PyXI_ERR_NO_ERROR) { _override.code = _PyXI_ERR_UNCAUGHT_EXCEPTION; } - _capture_current_exception(tstate, &_override, &err); + failure = capture_session_error(session, &err, &_override); } } } // Exit the session. + assert(!_PyErr_Occurred(tstate)); _exit_session(session); tstate = _PyThreadState_GET(); @@ -2777,8 +2788,9 @@ _PyXI_Exit(_PyXI_session *session, _PyXI_error_override *override, "Exception ignored while capturing preserved objects"); } else { - xi_error_set_override_code(&err, _PyXI_ERR_PRESERVE_FAILURE); - _propagate_not_shareable_error(err.override); + xi_error_set_override_code( + tstate, &err, _PyXI_ERR_PRESERVE_FAILURE); + _propagate_not_shareable_error(tstate, err.override); } } if (result != NULL) { @@ -2792,7 +2804,7 @@ _PyXI_Exit(_PyXI_session *session, _PyXI_error_override *override, if (xi_error_is_set(&err)) { res = -1; assert(!_PyErr_Occurred(tstate)); - PyObject *excinfo = _PyXI_ApplyError(&err); + PyObject *excinfo = _PyXI_ApplyError(&err, failure); if (excinfo == NULL) { assert(_PyErr_Occurred(tstate)); if (result != NULL && !xi_error_has_override(&err)) { @@ -2819,79 +2831,60 @@ _PyXI_Exit(_PyXI_session *session, _PyXI_error_override *override, /* in an active cross-interpreter session */ -static void -_capture_current_exception(PyThreadState *tstate, - _PyXI_error_override *override, - _PyXI_error *error) -{ - assert(!xi_error_is_set(error)); - if (!PyErr_Occurred()) { - assert(error->override == NULL); - xi_error_set_override(error, override); - return; - } - - // Handle the exception override. - _PyXI_error_override _override = { - .code = _PyXI_ERR_UNCAUGHT_EXCEPTION, - }; - if (override == NULL) { - override = &_override; - } - _PyXI_errcode errcode = override->code; +static const char * +capture_session_error(_PyXI_session *session, _PyXI_error *err, + _PyXI_failure *override) +{ + assert(_session_is_active(session)); + assert(!xi_error_is_set(err)); + PyThreadState *tstate = session->init_tstate; - // Pop the exception object. - PyObject *excval = NULL; - if (errcode == _PyXI_ERR_UNCAUGHT_EXCEPTION) { - // We want to actually capture the current exception. - excval = PyErr_GetRaisedException(); - } - else if (errcode == _PyXI_ERR_ALREADY_RUNNING) { - // We don't need the exception info. - PyErr_Clear(); - } - else { - // We could do a variety of things here, depending on errcode. - // However, for now we simply capture the exception and save - // the errcode. - excval = PyErr_GetRaisedException(); + // Normalize the exception override. + if (override != NULL) { + if (override->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) { + assert(override->msg == NULL); + override = NULL; + } + else { + assert(override->code != _PyXI_ERR_NO_ERROR); + } } - // Capture the exception. - *error = (_PyXI_error){ - .interp = tstate->interp, - }; - const char *failure = _PyXI_InitError(error, excval, &_override); - Py_XDECREF(excval); - if (failure == NULL) { - xi_error_set_override(error, override); + // Handle the exception, if any. + const char *failure = NULL; + PyObject *exc = xi_error_resolve_current_exc(tstate, override); + if (exc != NULL) { + // There is an unhandled exception we need to preserve. + failure = xi_error_set_exc(tstate, err, exc); + Py_DECREF(exc); + if (_PyErr_Occurred(tstate)) { + PyErr_FormatUnraisable(failure); + } } - // Handle capture failure. - if (failure != NULL) { - // XXX Make this error message more generic. - fprintf(stderr, - "RunFailedError: script raised an uncaught exception (%s)", - failure); - xi_error_clear(error); + // Handle the override. + if (override != NULL && failure == NULL) { + xi_error_set_override(tstate, err, override); } // Finished! - assert(!PyErr_Occurred()); + assert(!_PyErr_Occurred(tstate)); + return failure; } static int -_ensure_main_ns(_PyXI_session *session, _PyXI_error_override *err) +_ensure_main_ns(_PyXI_session *session, _PyXI_failure *failure) { assert(_session_is_active(session)); + PyThreadState *tstate = session->init_tstate; if (session->main_ns != NULL) { return 0; } // Cache __main__.__dict__. - PyObject *main_mod = _Py_GetMainModule(session->init_tstate); + PyObject *main_mod = _Py_GetMainModule(tstate); if (_Py_CheckMainModule(main_mod) < 0) { - if (err != NULL) { - *err = (_PyXI_error_override){ + if (failure != NULL) { + *failure = (_PyXI_failure){ .code = _PyXI_ERR_MAIN_NS_FAILURE, }; } @@ -2900,8 +2893,8 @@ _ensure_main_ns(_PyXI_session *session, _PyXI_error_override *err) PyObject *ns = PyModule_GetDict(main_mod); // borrowed Py_DECREF(main_mod); if (ns == NULL) { - if (err != NULL) { - *err = (_PyXI_error_override){ + if (failure != NULL) { + *failure = (_PyXI_failure){ .code = _PyXI_ERR_MAIN_NS_FAILURE, }; } @@ -2912,13 +2905,13 @@ _ensure_main_ns(_PyXI_session *session, _PyXI_error_override *err) } PyObject * -_PyXI_GetMainNamespace(_PyXI_session *session, _PyXI_error_override *err) +_PyXI_GetMainNamespace(_PyXI_session *session, _PyXI_failure *failure) { if (!_session_is_active(session)) { PyErr_SetString(PyExc_RuntimeError, "session not active"); return NULL; } - if (_ensure_main_ns(session, err) < 0) { + if (_ensure_main_ns(session, failure) < 0) { return NULL; } return session->main_ns; @@ -2928,9 +2921,9 @@ _PyXI_GetMainNamespace(_PyXI_session *session, _PyXI_error_override *err) static int _pop_preserved(_PyXI_session *session, _PyXI_namespace **p_xidata, PyObject **p_obj, - _PyXI_error_override *p_err) + _PyXI_failure *p_failure) { - _PyXI_error_override err = ERROR_OVERRIDE_INIT; + _PyXI_failure failure = XI_FAILURE_INIT; _PyXI_namespace *xidata = NULL; assert(_PyThreadState_GET() == session->init_tstate); // active session @@ -2951,7 +2944,7 @@ _pop_preserved(_PyXI_session *session, // We did switch interpreters. Py_ssize_t len = PyDict_Size(session->_preserved); if (len < 0) { - err.code = _PyXI_ERR_PRESERVE_FAILURE; + failure.code = _PyXI_ERR_PRESERVE_FAILURE; goto error; } else if (len == 0) { @@ -2960,15 +2953,15 @@ _pop_preserved(_PyXI_session *session, else { _PyXI_namespace *xidata = _create_sharedns(session->_preserved); if (xidata == NULL) { - err.code = _PyXI_ERR_PRESERVE_FAILURE; + failure.code = _PyXI_ERR_PRESERVE_FAILURE; goto error; } if (_fill_sharedns(xidata, session->_preserved, - _PyXIDATA_FULL_FALLBACK, &err) < 0) + _PyXIDATA_FULL_FALLBACK, &failure) < 0) { - if (err.code != _PyXI_ERR_NOT_SHAREABLE) { - assert(err.msg != NULL); - err.code = _PyXI_ERR_PRESERVE_FAILURE; + if (failure.code != _PyXI_ERR_NOT_SHAREABLE) { + assert(failure.msg != NULL); + failure.code = _PyXI_ERR_PRESERVE_FAILURE; } goto error; } @@ -2978,8 +2971,8 @@ _pop_preserved(_PyXI_session *session, return 0; error: - if (p_err != NULL) { - *p_err = err; + if (p_failure != NULL) { + *p_failure = failure; } if (xidata != NULL) { _destroy_sharedns(xidata); @@ -3014,9 +3007,9 @@ _finish_preserved(_PyXI_namespace *xidata, PyObject **p_preserved) int _PyXI_Preserve(_PyXI_session *session, const char *name, PyObject *value, - _PyXI_error_override *p_err) + _PyXI_failure *p_failure) { - _PyXI_error_override err = ERROR_OVERRIDE_INIT; + _PyXI_failure failure = XI_FAILURE_INIT; if (!_session_is_active(session)) { PyErr_SetString(PyExc_RuntimeError, "session not active"); return -1; @@ -3026,20 +3019,20 @@ _PyXI_Preserve(_PyXI_session *session, const char *name, PyObject *value, if (session->_preserved == NULL) { set_exc_with_cause(PyExc_RuntimeError, "failed to initialize preserved objects"); - err.code = _PyXI_ERR_PRESERVE_FAILURE; + failure.code = _PyXI_ERR_PRESERVE_FAILURE; goto error; } } if (PyDict_SetItemString(session->_preserved, name, value) < 0) { set_exc_with_cause(PyExc_RuntimeError, "failed to preserve object"); - err.code = _PyXI_ERR_PRESERVE_FAILURE; + failure.code = _PyXI_ERR_PRESERVE_FAILURE; goto error; } return 0; error: - if (p_err != NULL) { - *p_err = err; + if (p_failure != NULL) { + *p_failure = failure; } return -1; } diff --git a/Python/crossinterp_data_lookup.h b/Python/crossinterp_data_lookup.h index 0a495f90176d5e..6d0b93eb82ac1e 100644 --- a/Python/crossinterp_data_lookup.h +++ b/Python/crossinterp_data_lookup.h @@ -89,8 +89,7 @@ _PyXIData_FormatNotShareableError(PyThreadState *tstate, } int -_PyXI_UnwrapNotShareableError(PyThreadState * tstate, - _PyXI_error_override *override) +_PyXI_UnwrapNotShareableError(PyThreadState * tstate, _PyXI_failure *failure) { PyObject *exctype = get_notshareableerror_type(tstate); assert(exctype != NULL); @@ -98,9 +97,9 @@ _PyXI_UnwrapNotShareableError(PyThreadState * tstate, return -1; } PyObject *exc = _PyErr_GetRaisedException(tstate); - if (override != NULL) { + if (failure != NULL) { _PyXI_errcode code = _PyXI_ERR_NOT_SHAREABLE; - if (_PyXI_SetErrorOverride(override, code, exc) < 0) { + if (_PyXI_InitFailure(failure, code, exc) < 0) { return -1; } }