diff --git a/Doc/data/python3.14.abi b/Doc/data/python3.14.abi
index c5480aaf0e80f0..c692d1f4914ad9 100644
--- a/Doc/data/python3.14.abi
+++ b/Doc/data/python3.14.abi
@@ -892,6 +892,7 @@
+
@@ -1510,21 +1511,26 @@
-
-
+
+
+
-
+
+
+
+
+
@@ -3213,8 +3219,10 @@
-
-
+
+
+
+
@@ -4282,12 +4290,12 @@
-
+
-
+
@@ -5702,7 +5710,7 @@
-
+
@@ -6681,39 +6689,39 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -6722,15 +6730,15 @@
-
+
-
+
-
+
@@ -8221,7 +8229,7 @@
-
+
@@ -8558,7 +8566,7 @@
-
+
@@ -8691,13 +8699,13 @@
-
+
-
+
@@ -8773,214 +8781,214 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
+
-
-
-
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
@@ -9408,7 +9416,7 @@
-
+
@@ -10664,6 +10672,12 @@
+
+
+
+
+
+
@@ -11168,43 +11182,43 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -11775,51 +11789,51 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
@@ -12742,12 +12756,12 @@
-
+
-
+
@@ -13010,7 +13024,7 @@
-
+
@@ -13040,7 +13054,7 @@
-
+
@@ -13293,13 +13307,13 @@
-
+
-
+
@@ -13927,138 +13941,138 @@
-
+
+
+
+
+
+
+
-
+
-
+
-
-
-
-
-
-
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -14067,86 +14081,86 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -14155,25 +14169,25 @@
-
+
-
+
-
+
-
+
@@ -14187,7 +14201,7 @@
-
+
@@ -14201,40 +14215,40 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -14245,22 +14259,22 @@
-
+
-
+
-
+
-
+
@@ -14270,39 +14284,39 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -14313,27 +14327,27 @@
-
+
-
+
-
+
-
+
-
+
@@ -14345,12 +14359,12 @@
-
+
-
+
@@ -14390,7 +14404,7 @@
-
+
@@ -14403,7 +14417,7 @@
-
+
@@ -22601,13 +22615,13 @@
-
+
-
+
@@ -23656,7 +23670,7 @@
-
+
@@ -23691,7 +23705,7 @@
-
+
@@ -23897,19 +23911,19 @@
-
+
-
+
-
+
-
+
@@ -23917,7 +23931,7 @@
-
+
@@ -23928,12 +23942,12 @@
-
+
-
+
@@ -24274,11 +24288,11 @@
-
+
-
+
@@ -24364,11 +24378,11 @@
-
+
-
+
@@ -24578,13 +24592,13 @@
-
+
-
+
@@ -26944,92 +26958,74 @@
-
+
+
+
-
+
-
+
-
+
-
-
-
+
+
-
+
-
+
-
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
@@ -27241,79 +27237,97 @@
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
@@ -27331,67 +27345,72 @@
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
@@ -27529,15 +27548,15 @@
-
+
-
+
-
+
@@ -27545,11 +27564,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -27593,7 +27612,7 @@
-
+
@@ -27650,21 +27669,21 @@
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
+
@@ -27738,9 +27757,9 @@
-
+
-
+
@@ -27780,17 +27799,17 @@
-
+
-
+
-
-
-
-
+
+
+
+
@@ -27811,7 +27830,7 @@
-
+
@@ -27825,7 +27844,7 @@
-
+
@@ -27833,15 +27852,15 @@
-
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
@@ -27901,7 +27920,7 @@
-
+
@@ -27936,15 +27955,15 @@
-
+
-
+
-
+
@@ -28034,8 +28053,8 @@
-
-
+
+
@@ -28047,8 +28066,8 @@
-
-
+
+
@@ -28069,7 +28088,7 @@
-
+
@@ -28082,7 +28101,7 @@
-
+
@@ -28091,22 +28110,22 @@
-
+
-
-
+
+
-
-
+
+
@@ -28123,7 +28142,7 @@
-
+
@@ -28132,7 +28151,7 @@
-
+
@@ -28140,24 +28159,24 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -28167,9 +28186,9 @@
-
-
-
+
+
+
@@ -28177,48 +28196,48 @@
-
+
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
@@ -28249,7 +28268,7 @@
-
+
@@ -28386,7 +28405,7 @@
-
+
@@ -28398,11 +28417,11 @@
-
+
-
-
+
+
@@ -28422,8 +28441,8 @@
-
-
+
+
@@ -28443,27 +28462,27 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
-
-
+
+
-
+
@@ -28531,13 +28550,13 @@
-
+
-
-
+
+
@@ -28562,21 +28581,21 @@
-
+
-
+
-
+
-
+
@@ -28585,7 +28604,7 @@
-
+
@@ -28599,7 +28618,7 @@
-
+
@@ -28608,7 +28627,7 @@
-
+
@@ -28676,12 +28695,12 @@
-
+
-
+
@@ -28698,19 +28717,19 @@
-
+
-
+
-
+
@@ -28745,12 +28764,12 @@
-
+
-
+
@@ -28763,44 +28782,44 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -28809,32 +28828,32 @@
-
+
-
+
-
+
-
+
-
+
@@ -28859,7 +28878,7 @@
-
+
@@ -28867,12 +28886,12 @@
-
-
+
+
-
+
@@ -28882,33 +28901,33 @@
-
+
-
+
-
+
-
+
-
+
@@ -28916,85 +28935,85 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -29003,62 +29022,62 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
-
+
+
-
+
-
-
-
-
-
+
+
+
+
+
@@ -29070,8 +29089,8 @@
-
-
+
+
@@ -29117,12 +29136,12 @@
-
+
-
+
@@ -29130,8 +29149,8 @@
-
-
+
+
@@ -29140,53 +29159,53 @@
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
-
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
@@ -29202,7 +29221,7 @@
-
+
@@ -29211,21 +29230,21 @@
-
+
-
+
-
+
-
+
@@ -29241,65 +29260,65 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -29307,18 +29326,18 @@
-
+
-
+
-
+
-
+
@@ -29339,10 +29358,10 @@
-
+
-
+
@@ -29354,16 +29373,16 @@
-
+
-
+
-
-
-
-
+
+
+
+
@@ -29465,21 +29484,21 @@
-
+
-
-
-
-
+
+
+
+
-
+
-
+
@@ -29488,30 +29507,30 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -29574,16 +29593,16 @@
-
-
+
+
-
+
-
+
@@ -29597,7 +29616,7 @@
-
+
@@ -29609,23 +29628,23 @@
-
-
+
+
-
+
-
+
-
+
@@ -29643,11 +29662,11 @@
-
+
-
+
@@ -29657,36 +29676,36 @@
-
+
-
+
-
+
-
+
-
-
-
-
+
+
+
+
-
+
-
+
-
+
-
+
@@ -29698,25 +29717,25 @@
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -29724,7 +29743,7 @@
-
+
@@ -29732,38 +29751,38 @@
-
+
-
+
-
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
+
-
+
@@ -29774,17 +29793,17 @@
-
-
-
+
+
+
-
-
-
-
+
+
+
+
+
+
-
-
@@ -29954,13 +29973,13 @@
-
+
-
-
+
+
@@ -29992,7 +30011,7 @@
-
+
@@ -30340,7 +30359,7 @@
-
+
@@ -30349,8 +30368,8 @@
+
-
@@ -30415,19 +30434,19 @@
-
+
-
-
+
+
-
-
-
-
+
+
+
+
@@ -30447,7 +30466,7 @@
-
+
@@ -30509,7 +30528,7 @@
-
+
@@ -30520,7 +30539,7 @@
-
+
@@ -30533,24 +30552,24 @@
-
+
-
+
-
+
-
+
-
+
@@ -30858,7 +30877,7 @@
-
+
@@ -30872,7 +30891,7 @@
-
+
@@ -30909,22 +30928,22 @@
-
-
-
-
+
+
+
+
-
+
-
-
+
+
-
-
+
+
@@ -31055,7 +31074,7 @@
-
+
@@ -31064,8 +31083,8 @@
-
-
+
+
@@ -31089,7 +31108,7 @@
-
+
@@ -31184,10 +31203,10 @@
-
-
-
-
+
+
+
+
@@ -31217,7 +31236,7 @@
-
+
@@ -31238,8 +31257,8 @@
+
-
@@ -31261,26 +31280,26 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
@@ -31313,7 +31332,7 @@
-
+
@@ -31334,7 +31353,7 @@
-
+
@@ -31364,7 +31383,7 @@
-
+
@@ -31373,17 +31392,17 @@
-
+
-
+
-
-
+
+
@@ -31392,11 +31411,11 @@
-
+
-
+
@@ -31431,13 +31450,13 @@
-
+
-
+
@@ -31451,8 +31470,8 @@
-
-
+
+
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 713ddc66ba7382..81faffac194171 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,20 @@ typedef enum error_code {
_PyXI_ERR_NOT_SHAREABLE = -9,
} _PyXI_errcode;
+typedef struct xi_failure _PyXI_failure;
-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_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(PyObject *) _PyXI_ApplyError(_PyXI_error *err);
+PyAPI_FUNC(int) _PyXI_UnwrapNotShareableError(
+ PyThreadState *,
+ _PyXI_failure *);
// A cross-interpreter session involves entering an interpreter
@@ -366,19 +367,21 @@ PyAPI_FUNC(int) _PyXI_Enter(
_PyXI_session_result *);
PyAPI_FUNC(int) _PyXI_Exit(
_PyXI_session *,
- _PyXI_errcode,
+ _PyXI_failure *,
_PyXI_session_result *);
PyAPI_FUNC(PyObject *) _PyXI_GetMainNamespace(
_PyXI_session *,
- _PyXI_errcode *);
+ _PyXI_failure *);
PyAPI_FUNC(int) _PyXI_Preserve(
_PyXI_session *,
const char *,
PyObject *,
- _PyXI_errcode *);
-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 037e9544543c4d..fdfb3e6dd3482d 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_failure *failure)
{
- 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, failure) < 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_failure *failure)
{
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_InitFailure(failure, _PyXI_ERR_OTHER, NULL);
+ unwrap_not_shareable(tstate, failure);
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_failure *failure)
{
PyObject *code = _PyXIData_NewObject(script);
if (code == NULL) {
- *p_errcode = _PyXI_ERR_NOT_SHAREABLE;
+ _PyXI_InitFailure(failure, _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_InitFailure(failure, _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_failure *failure = _PyXI_NewFailure();
+ if (failure == NULL) {
+ return -1;
+ }
_PyXI_session *session = _PyXI_NewSession();
if (session == NULL) {
+ _PyXI_FreeFailure(failure);
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_FreeFailure(failure);
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, failure);
if (mainns == NULL) {
goto finally;
}
- res = _run_script(script, mainns, &errcode);
+ res = _run_script(script, mainns, failure);
}
else {
assert(call != NULL);
PyObject *resobj;
- res = _make_call(call, &resobj, &errcode);
+ res = _make_call(call, &resobj, failure);
if (res == 0) {
- res = _PyXI_Preserve(session, "resobj", resobj, &errcode);
+ res = _PyXI_Preserve(session, "resobj", resobj, failure);
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, failure, &result);
assert(res == 0 || exitres != 0);
_PyXI_FreeSession(session);
+ _PyXI_FreeFailure(failure);
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..39c7ea698904bd 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,11 @@ _PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype)
if (tbexc == NULL) {
PyErr_Clear();
}
+ else {
+ PyErr_SetObject(exctype, tbexc);
+ Py_DECREF(tbexc);
+ return;
+ }
}
PyObject *formatted = _PyXI_excinfo_format(info);
@@ -1630,13 +1641,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 +1661,18 @@ _PyXI_InitExcInfo(_PyXI_excinfo *info, PyObject *exc)
failure = _PyXI_excinfo_InitFromObject(info, exc);
}
if (failure != NULL) {
- PyErr_SetString(PyExc_Exception, failure);
- return -1;
+ PyMem_RawFree(info);
+ set_exc_with_cause(PyExc_Exception, failure);
+ return NULL;
}
- return 0;
+ return info;
+}
+
+void
+_PyXI_FreeExcInfo(_PyXI_excinfo *info)
+{
+ _PyXI_excinfo_clear(info);
+ PyMem_RawFree(info);
}
PyObject *
@@ -1664,12 +1687,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 +1744,267 @@ _PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
return -1;
}
+/* basic failure info */
+
+struct xi_failure {
+ // The kind of error to propagate.
+ _PyXI_errcode code;
+ // The propagated message.
+ const char *msg;
+ int msg_owned;
+}; // _PyXI_failure
+
+#define XI_FAILURE_INIT (_PyXI_failure){ .code = _PyXI_ERR_NO_ERROR }
+
+static void
+clear_xi_failure(_PyXI_failure *failure)
+{
+ if (failure->msg != NULL && failure->msg_owned) {
+ PyMem_RawFree((void*)failure->msg);
+ }
+ *failure = XI_FAILURE_INIT;
+}
+
+static void
+copy_xi_failure(_PyXI_failure *dest, _PyXI_failure *src)
+{
+ *dest = *src;
+ dest->msg_owned = 0;
+}
+
+_PyXI_failure *
+_PyXI_NewFailure(void)
+{
+ _PyXI_failure *failure = PyMem_RawMalloc(sizeof(_PyXI_failure));
+ if (failure == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ *failure = XI_FAILURE_INIT;
+ return failure;
+}
+
+void
+_PyXI_FreeFailure(_PyXI_failure *failure)
+{
+ clear_xi_failure(failure);
+ PyMem_RawFree(failure);
+}
+
+_PyXI_errcode
+_PyXI_GetFailureCode(_PyXI_failure *failure)
+{
+ if (failure == NULL) {
+ return _PyXI_ERR_NO_ERROR;
+ }
+ return failure->code;
+}
+
+void
+_PyXI_InitFailureUTF8(_PyXI_failure *failure,
+ _PyXI_errcode code, const char *msg)
+{
+ *failure = (_PyXI_failure){
+ .code = code,
+ .msg = msg,
+ .msg_owned = 0,
+ };
+}
+
+int
+_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_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;
+ }
+ *failure = (_PyXI_failure){
+ .code = code,
+ .msg = msg,
+ .msg_owned = 1,
+ };
+ return 0;
+}
+
/* shared exceptions */
-static const char *
-_PyXI_InitError(_PyXI_error *error, PyObject *excobj, _PyXI_errcode code)
+typedef struct {
+ // The originating interpreter.
+ PyInterpreterState *interp;
+ // The error to propagate, if different from the uncaught exception.
+ _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.
+ _PyXI_excinfo uncaught;
+} _PyXI_error;
+
+static void
+xi_error_clear(_PyXI_error *err)
{
- if (error->interp == NULL) {
- error->interp = PyInterpreterState_Get();
+ err->interp = NULL;
+ if (err->override != NULL) {
+ clear_xi_failure(err->override);
}
+ _PyXI_excinfo_clear(&err->uncaught);
+}
- const char *failure = NULL;
- if (code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
- // There is an unhandled exception we need to propagate.
- 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;
- }
- else {
- error->code = _PyXI_ERR_OTHER;
- }
- PyErr_Clear();
+static int
+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);
+}
+
+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 PyObject *
+xi_error_resolve_current_exc(PyThreadState *tstate,
+ _PyXI_failure *override)
+{
+ 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 (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(PyThreadState *tstate, _PyXI_error *err,
+ _PyXI_errcode code)
+{
+ _PyXI_failure override = XI_FAILURE_INIT;
+ override.code = code;
+ xi_error_set_override(tstate, err, &override);
+}
+
+static const char *
+xi_error_set_exc(PyThreadState *tstate, _PyXI_error *err, PyObject *exc)
+{
+ assert(!_PyErr_Occurred(tstate));
+ 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 {
- error->code = code;
+ xi_error_set_override_code(tstate, err, _PyXI_ERR_OTHER);
}
- 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;
- _PyXI_excinfo_clear(&error->uncaught);
+ PyErr_Clear();
}
return failure;
}
-PyObject *
-_PyXI_ApplyError(_PyXI_error *error)
+static PyObject *
+_PyXI_ApplyError(_PyXI_error *error, const char *failure)
{
PyThreadState *tstate = PyThreadState_Get();
- if (error->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
+
+ if (failure != NULL) {
+ xi_error_clear(error);
+ return NULL;
+ }
+
+ _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);
+ Py_XDECREF(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,20 +2378,22 @@ _create_sharedns(PyObject *names)
return NULL;
}
-static void _propagate_not_shareable_error(_PyXI_errcode *);
+static void _propagate_not_shareable_error(PyThreadState *,
+ _PyXI_failure *);
static int
_fill_sharedns(_PyXI_namespace *ns, PyObject *nsobj,
- xidata_fallback_t fallback, _PyXI_errcode *p_errcode)
+ 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_errcode != NULL) {
- _propagate_not_shareable_error(p_errcode);
+ if (p_err != NULL) {
+ _propagate_not_shareable_error(tstate, p_err);
}
// Clear out the ones we set so far.
for (Py_ssize_t j=0; j < i; j++) {
@@ -2244,18 +2460,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 +2492,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,26 +2519,6 @@ _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);
-
/* enter/exit a cross-interpreter session */
@@ -2351,10 +2533,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,17 +2587,14 @@ _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(PyThreadState *tstate,
+ _PyXI_failure *override)
{
- assert(p_errcode != NULL);
- PyThreadState *tstate = PyThreadState_Get();
+ assert(override != NULL);
PyObject *exctype = get_notshareableerror_type(tstate);
if (exctype == NULL) {
PyErr_FormatUnraisable(
@@ -2428,15 +2603,24 @@ _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_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,
_PyXI_session_result *result)
{
+ PyThreadState *tstate = _PyThreadState_GET();
+
// Convert the attrs for cross-interpreter use.
_PyXI_namespace *sharedns = NULL;
if (nsupdates != NULL) {
@@ -2457,16 +2641,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_failure _err = XI_FAILURE_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 +2659,53 @@ _PyXI_Enter(_PyXI_session *session,
// Switch to the requested interpreter (if necessary).
_enter_session(session, interp);
- _PyXI_errcode errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
+ _PyXI_failure override = XI_FAILURE_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};
+ const char *failure = capture_session_error(session, &err, &override);
// 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, failure);
+ xi_error_clear(&err);
if (excinfo != NULL) {
if (result != NULL) {
result->excinfo = excinfo;
@@ -2529,84 +2714,100 @@ _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_failure *);
static int _finish_preserved(_PyXI_namespace *, PyObject **);
int
-_PyXI_Exit(_PyXI_session *session, _PyXI_errcode errcode,
+_PyXI_Exit(_PyXI_session *session, _PyXI_failure *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};
+ const char *failure = NULL;
+ if (override != NULL && override->code == _PyXI_ERR_NO_ERROR) {
+ assert(override->msg == NULL);
+ override = NULL;
+ }
+ if (_PyErr_Occurred(tstate)) {
+ failure = capture_session_error(session, &err, override);
}
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) {
+ 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_xi_failure(&_override);
}
else {
- _session_set_error(session, errcode);
+ if (_override.code == _PyXI_ERR_NO_ERROR) {
+ _override.code = _PyXI_ERR_UNCAUGHT_EXCEPTION;
+ }
+ failure = capture_session_error(session, &err, &_override);
}
}
}
// Exit the session.
- struct xi_session_error err;
- (void)_session_pop_error(session, &err);
+ assert(!_PyErr_Occurred(tstate));
_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(
+ tstate, &err, _PyXI_ERR_PRESERVE_FAILURE);
+ _propagate_not_shareable_error(tstate, 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, failure);
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 +2821,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;
}
@@ -2628,106 +2831,72 @@ _PyXI_Exit(_PyXI_session *session, _PyXI_errcode errcode,
/* in an active cross-interpreter session */
-static void
-_capture_current_exception(_PyXI_session *session)
+static const char *
+capture_session_error(_PyXI_session *session, _PyXI_error *err,
+ _PyXI_failure *override)
{
- assert(session->error.info == NULL);
- if (!PyErr_Occurred()) {
- assert(session->error.override == NULL);
- 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;
+ 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.
- _PyXI_error *err = &session->error._info;
- *err = (_PyXI_error){
- .interp = session->init_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;
+ // 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);
- err = NULL;
+ // Handle the override.
+ if (override != NULL && failure == NULL) {
+ xi_error_set_override(tstate, err, override);
}
// 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);
+ assert(!_PyErr_Occurred(tstate));
+ return failure;
}
static int
-_ensure_main_ns(_PyXI_session *session, _PyXI_errcode *p_errcode)
+_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 (p_errcode != NULL) {
- *p_errcode = _PyXI_ERR_MAIN_NS_FAILURE;
+ if (failure != NULL) {
+ *failure = (_PyXI_failure){
+ .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 (failure != NULL) {
+ *failure = (_PyXI_failure){
+ .code = _PyXI_ERR_MAIN_NS_FAILURE,
+ };
}
return -1;
}
@@ -2736,13 +2905,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_failure *failure)
{
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, failure) < 0) {
return NULL;
}
return session->main_ns;
@@ -2752,9 +2921,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_failure *p_failure)
{
+ _PyXI_failure failure = XI_FAILURE_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 +2944,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;
+ failure.code = _PyXI_ERR_PRESERVE_FAILURE;
+ goto error;
}
else if (len == 0) {
*p_xidata = NULL;
@@ -2783,29 +2953,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;
+ failure.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, &failure) < 0)
{
- assert(session->error.info == NULL);
- if (errcode != _PyXI_ERR_NOT_SHAREABLE) {
- errcode = _PyXI_ERR_PRESERVE_FAILURE;
+ if (failure.code != _PyXI_ERR_NOT_SHAREABLE) {
+ assert(failure.msg != NULL);
+ failure.code = _PyXI_ERR_PRESERVE_FAILURE;
}
- if (p_errcode != NULL) {
- *p_errcode = errcode;
- }
- _destroy_sharedns(xidata);
- return -1;
+ goto error;
}
*p_xidata = xidata;
}
Py_CLEAR(session->_preserved);
return 0;
+
+error:
+ if (p_failure != NULL) {
+ *p_failure = failure;
+ }
+ if (xidata != NULL) {
+ _destroy_sharedns(xidata);
+ }
+ return -1;
}
static int
@@ -2835,8 +3007,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_failure *p_failure)
{
+ _PyXI_failure failure = XI_FAILURE_INIT;
if (!_session_is_active(session)) {
PyErr_SetString(PyExc_RuntimeError, "session not active");
return -1;
@@ -2846,20 +3019,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;
+ 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");
- if (p_errcode != NULL) {
- *p_errcode = _PyXI_ERR_PRESERVE_FAILURE;
- }
- return -1;
+ failure.code = _PyXI_ERR_PRESERVE_FAILURE;
+ goto error;
}
return 0;
+
+error:
+ if (p_failure != NULL) {
+ *p_failure = failure;
+ }
+ return -1;
}
PyObject *
diff --git a/Python/crossinterp_data_lookup.h b/Python/crossinterp_data_lookup.h
index b16f38b847fc66..6d0b93eb82ac1e 100644
--- a/Python/crossinterp_data_lookup.h
+++ b/Python/crossinterp_data_lookup.h
@@ -88,6 +88,33 @@ _PyXIData_FormatNotShareableError(PyThreadState *tstate,
va_end(vargs);
}
+int
+_PyXI_UnwrapNotShareableError(PyThreadState * tstate, _PyXI_failure *failure)
+{
+ PyObject *exctype = get_notshareableerror_type(tstate);
+ assert(exctype != NULL);
+ if (!_PyErr_ExceptionMatches(tstate, exctype)) {
+ return -1;
+ }
+ PyObject *exc = _PyErr_GetRaisedException(tstate);
+ if (failure != NULL) {
+ _PyXI_errcode code = _PyXI_ERR_NOT_SHAREABLE;
+ if (_PyXI_InitFailure(failure, 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 12cd61db1b6762..98411adc5eb3f6 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);