Skip to content

Commit d347e29

Browse files
ericsnowcurrentlymiss-islington
authored andcommitted
pythongh-132775: Clean Up Cross-Interpreter Error Handling (pythongh-135369)
In this refactor we: * move some code around * make a couple of typedefs opaque * decouple errors from session state * improve tracebacks for propagated exceptions This change helps simplify several upcoming changes. (cherry picked from commit c7f4a80) Co-authored-by: Eric Snow <ericsnowcurrently@gmail.com>
1 parent d851f8e commit d347e29

File tree

5 files changed

+523
-311
lines changed

5 files changed

+523
-311
lines changed

Include/internal/pycore_crossinterp.h

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -303,10 +303,10 @@ typedef struct _excinfo {
303303
const char *errdisplay;
304304
} _PyXI_excinfo;
305305

306-
PyAPI_FUNC(int) _PyXI_InitExcInfo(_PyXI_excinfo *info, PyObject *exc);
306+
PyAPI_FUNC(_PyXI_excinfo *) _PyXI_NewExcInfo(PyObject *exc);
307+
PyAPI_FUNC(void) _PyXI_FreeExcInfo(_PyXI_excinfo *info);
307308
PyAPI_FUNC(PyObject *) _PyXI_FormatExcInfo(_PyXI_excinfo *info);
308309
PyAPI_FUNC(PyObject *) _PyXI_ExcInfoAsObject(_PyXI_excinfo *info);
309-
PyAPI_FUNC(void) _PyXI_ClearExcInfo(_PyXI_excinfo *info);
310310

311311

312312
typedef enum error_code {
@@ -322,19 +322,20 @@ typedef enum error_code {
322322
_PyXI_ERR_NOT_SHAREABLE = -9,
323323
} _PyXI_errcode;
324324

325+
typedef struct xi_failure _PyXI_failure;
325326

326-
typedef struct _sharedexception {
327-
// The originating interpreter.
328-
PyInterpreterState *interp;
329-
// The kind of error to propagate.
330-
_PyXI_errcode code;
331-
// The exception information to propagate, if applicable.
332-
// This is populated only for some error codes,
333-
// but always for _PyXI_ERR_UNCAUGHT_EXCEPTION.
334-
_PyXI_excinfo uncaught;
335-
} _PyXI_error;
327+
PyAPI_FUNC(_PyXI_failure *) _PyXI_NewFailure(void);
328+
PyAPI_FUNC(void) _PyXI_FreeFailure(_PyXI_failure *);
329+
PyAPI_FUNC(_PyXI_errcode) _PyXI_GetFailureCode(_PyXI_failure *);
330+
PyAPI_FUNC(int) _PyXI_InitFailure(_PyXI_failure *, _PyXI_errcode, PyObject *);
331+
PyAPI_FUNC(void) _PyXI_InitFailureUTF8(
332+
_PyXI_failure *,
333+
_PyXI_errcode,
334+
const char *);
336335

337-
PyAPI_FUNC(PyObject *) _PyXI_ApplyError(_PyXI_error *err);
336+
PyAPI_FUNC(int) _PyXI_UnwrapNotShareableError(
337+
PyThreadState *,
338+
_PyXI_failure *);
338339

339340

340341
// A cross-interpreter session involves entering an interpreter
@@ -366,19 +367,21 @@ PyAPI_FUNC(int) _PyXI_Enter(
366367
_PyXI_session_result *);
367368
PyAPI_FUNC(int) _PyXI_Exit(
368369
_PyXI_session *,
369-
_PyXI_errcode,
370+
_PyXI_failure *,
370371
_PyXI_session_result *);
371372

372373
PyAPI_FUNC(PyObject *) _PyXI_GetMainNamespace(
373374
_PyXI_session *,
374-
_PyXI_errcode *);
375+
_PyXI_failure *);
375376

376377
PyAPI_FUNC(int) _PyXI_Preserve(
377378
_PyXI_session *,
378379
const char *,
379380
PyObject *,
380-
_PyXI_errcode *);
381-
PyAPI_FUNC(PyObject *) _PyXI_GetPreserved(_PyXI_session_result *, const char *);
381+
_PyXI_failure *);
382+
PyAPI_FUNC(PyObject *) _PyXI_GetPreserved(
383+
_PyXI_session_result *,
384+
const char *);
382385

383386

384387
/*************/

Modules/_interpretersmodule.c

Lines changed: 52 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -80,21 +80,11 @@ is_notshareable_raised(PyThreadState *tstate)
8080
}
8181

8282
static void
83-
unwrap_not_shareable(PyThreadState *tstate)
83+
unwrap_not_shareable(PyThreadState *tstate, _PyXI_failure *failure)
8484
{
85-
if (!is_notshareable_raised(tstate)) {
86-
return;
87-
}
88-
PyObject *exc = _PyErr_GetRaisedException(tstate);
89-
PyObject *cause = PyException_GetCause(exc);
90-
if (cause != NULL) {
91-
Py_DECREF(exc);
92-
exc = cause;
85+
if (_PyXI_UnwrapNotShareableError(tstate, failure) < 0) {
86+
_PyErr_Clear(tstate);
9387
}
94-
else {
95-
assert(PyException_GetContext(exc) == NULL);
96-
}
97-
_PyErr_SetRaisedException(tstate, exc);
9888
}
9989

10090

@@ -532,13 +522,30 @@ _interp_call_pack(PyThreadState *tstate, struct interp_call *call,
532522
return 0;
533523
}
534524

525+
static void
526+
wrap_notshareable(PyThreadState *tstate, const char *label)
527+
{
528+
if (!is_notshareable_raised(tstate)) {
529+
return;
530+
}
531+
assert(label != NULL && strlen(label) > 0);
532+
PyObject *cause = _PyErr_GetRaisedException(tstate);
533+
_PyXIData_FormatNotShareableError(tstate, "%s not shareable", label);
534+
PyObject *exc = _PyErr_GetRaisedException(tstate);
535+
PyException_SetCause(exc, cause);
536+
_PyErr_SetRaisedException(tstate, exc);
537+
}
538+
535539
static int
536540
_interp_call_unpack(struct interp_call *call,
537541
PyObject **p_func, PyObject **p_args, PyObject **p_kwargs)
538542
{
543+
PyThreadState *tstate = PyThreadState_Get();
544+
539545
// Unpack the func.
540546
PyObject *func = _PyXIData_NewObject(call->func);
541547
if (func == NULL) {
548+
wrap_notshareable(tstate, "func");
542549
return -1;
543550
}
544551
// Unpack the args.
@@ -553,6 +560,7 @@ _interp_call_unpack(struct interp_call *call,
553560
else {
554561
args = _PyXIData_NewObject(call->args);
555562
if (args == NULL) {
563+
wrap_notshareable(tstate, "args");
556564
Py_DECREF(func);
557565
return -1;
558566
}
@@ -563,6 +571,7 @@ _interp_call_unpack(struct interp_call *call,
563571
if (call->kwargs != NULL) {
564572
kwargs = _PyXIData_NewObject(call->kwargs);
565573
if (kwargs == NULL) {
574+
wrap_notshareable(tstate, "kwargs");
566575
Py_DECREF(func);
567576
Py_DECREF(args);
568577
return -1;
@@ -577,7 +586,7 @@ _interp_call_unpack(struct interp_call *call,
577586

578587
static int
579588
_make_call(struct interp_call *call,
580-
PyObject **p_result, _PyXI_errcode *p_errcode)
589+
PyObject **p_result, _PyXI_failure *failure)
581590
{
582591
assert(call != NULL && call->func != NULL);
583592
PyThreadState *tstate = _PyThreadState_GET();
@@ -588,12 +597,10 @@ _make_call(struct interp_call *call,
588597
assert(func == NULL);
589598
assert(args == NULL);
590599
assert(kwargs == NULL);
591-
*p_errcode = is_notshareable_raised(tstate)
592-
? _PyXI_ERR_NOT_SHAREABLE
593-
: _PyXI_ERR_OTHER;
600+
_PyXI_InitFailure(failure, _PyXI_ERR_OTHER, NULL);
601+
unwrap_not_shareable(tstate, failure);
594602
return -1;
595603
}
596-
*p_errcode = _PyXI_ERR_NO_ERROR;
597604

598605
// Make the call.
599606
PyObject *resobj = PyObject_Call(func, args, kwargs);
@@ -608,17 +615,17 @@ _make_call(struct interp_call *call,
608615
}
609616

610617
static int
611-
_run_script(_PyXIData_t *script, PyObject *ns, _PyXI_errcode *p_errcode)
618+
_run_script(_PyXIData_t *script, PyObject *ns, _PyXI_failure *failure)
612619
{
613620
PyObject *code = _PyXIData_NewObject(script);
614621
if (code == NULL) {
615-
*p_errcode = _PyXI_ERR_NOT_SHAREABLE;
622+
_PyXI_InitFailure(failure, _PyXI_ERR_NOT_SHAREABLE, NULL);
616623
return -1;
617624
}
618625
PyObject *result = PyEval_EvalCode(code, ns, ns);
619626
Py_DECREF(code);
620627
if (result == NULL) {
621-
*p_errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
628+
_PyXI_InitFailure(failure, _PyXI_ERR_UNCAUGHT_EXCEPTION, NULL);
622629
return -1;
623630
}
624631
assert(result == Py_None);
@@ -644,8 +651,14 @@ _run_in_interpreter(PyThreadState *tstate, PyInterpreterState *interp,
644651
PyObject *shareables, struct run_result *runres)
645652
{
646653
assert(!_PyErr_Occurred(tstate));
654+
int res = -1;
655+
_PyXI_failure *failure = _PyXI_NewFailure();
656+
if (failure == NULL) {
657+
return -1;
658+
}
647659
_PyXI_session *session = _PyXI_NewSession();
648660
if (session == NULL) {
661+
_PyXI_FreeFailure(failure);
649662
return -1;
650663
}
651664
_PyXI_session_result result = {0};
@@ -655,43 +668,44 @@ _run_in_interpreter(PyThreadState *tstate, PyInterpreterState *interp,
655668
// If an error occured at this step, it means that interp
656669
// was not prepared and switched.
657670
_PyXI_FreeSession(session);
671+
_PyXI_FreeFailure(failure);
658672
assert(result.excinfo == NULL);
659673
return -1;
660674
}
661675

662676
// Run in the interpreter.
663-
int res = -1;
664-
_PyXI_errcode errcode = _PyXI_ERR_NO_ERROR;
665677
if (script != NULL) {
666678
assert(call == NULL);
667-
PyObject *mainns = _PyXI_GetMainNamespace(session, &errcode);
679+
PyObject *mainns = _PyXI_GetMainNamespace(session, failure);
668680
if (mainns == NULL) {
669681
goto finally;
670682
}
671-
res = _run_script(script, mainns, &errcode);
683+
res = _run_script(script, mainns, failure);
672684
}
673685
else {
674686
assert(call != NULL);
675687
PyObject *resobj;
676-
res = _make_call(call, &resobj, &errcode);
688+
res = _make_call(call, &resobj, failure);
677689
if (res == 0) {
678-
res = _PyXI_Preserve(session, "resobj", resobj, &errcode);
690+
res = _PyXI_Preserve(session, "resobj", resobj, failure);
679691
Py_DECREF(resobj);
680692
if (res < 0) {
681693
goto finally;
682694
}
683695
}
684696
}
685-
int exitres;
686697

687698
finally:
688699
// Clean up and switch back.
689-
exitres = _PyXI_Exit(session, errcode, &result);
700+
(void)res;
701+
int exitres = _PyXI_Exit(session, failure, &result);
690702
assert(res == 0 || exitres != 0);
691703
_PyXI_FreeSession(session);
704+
_PyXI_FreeFailure(failure);
692705

693706
res = exitres;
694707
if (_PyErr_Occurred(tstate)) {
708+
// It's a directly propagated exception.
695709
assert(res < 0);
696710
}
697711
else if (res < 0) {
@@ -1064,7 +1078,7 @@ interp_set___main___attrs(PyObject *self, PyObject *args, PyObject *kwargs)
10641078

10651079
// Clean up and switch back.
10661080
assert(!PyErr_Occurred());
1067-
int res = _PyXI_Exit(session, _PyXI_ERR_NO_ERROR, NULL);
1081+
int res = _PyXI_Exit(session, NULL, NULL);
10681082
_PyXI_FreeSession(session);
10691083
assert(res == 0);
10701084
if (res < 0) {
@@ -1124,7 +1138,7 @@ interp_exec(PyObject *self, PyObject *args, PyObject *kwds)
11241138
// global variables. They will be resolved against __main__.
11251139
_PyXIData_t xidata = {0};
11261140
if (_PyCode_GetScriptXIData(tstate, code, &xidata) < 0) {
1127-
unwrap_not_shareable(tstate);
1141+
unwrap_not_shareable(tstate, NULL);
11281142
return NULL;
11291143
}
11301144

@@ -1188,7 +1202,7 @@ interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)
11881202

11891203
_PyXIData_t xidata = {0};
11901204
if (_PyCode_GetScriptXIData(tstate, script, &xidata) < 0) {
1191-
unwrap_not_shareable(tstate);
1205+
unwrap_not_shareable(tstate, NULL);
11921206
return NULL;
11931207
}
11941208

@@ -1251,7 +1265,7 @@ interp_run_func(PyObject *self, PyObject *args, PyObject *kwds)
12511265

12521266
_PyXIData_t xidata = {0};
12531267
if (_PyCode_GetScriptXIData(tstate, code, &xidata) < 0) {
1254-
unwrap_not_shareable(tstate);
1268+
unwrap_not_shareable(tstate, NULL);
12551269
return NULL;
12561270
}
12571271

@@ -1542,16 +1556,16 @@ capture_exception(PyObject *self, PyObject *args, PyObject *kwds)
15421556
}
15431557
PyObject *captured = NULL;
15441558

1545-
_PyXI_excinfo info = {0};
1546-
if (_PyXI_InitExcInfo(&info, exc) < 0) {
1559+
_PyXI_excinfo *info = _PyXI_NewExcInfo(exc);
1560+
if (info == NULL) {
15471561
goto finally;
15481562
}
1549-
captured = _PyXI_ExcInfoAsObject(&info);
1563+
captured = _PyXI_ExcInfoAsObject(info);
15501564
if (captured == NULL) {
15511565
goto finally;
15521566
}
15531567

1554-
PyObject *formatted = _PyXI_FormatExcInfo(&info);
1568+
PyObject *formatted = _PyXI_FormatExcInfo(info);
15551569
if (formatted == NULL) {
15561570
Py_CLEAR(captured);
15571571
goto finally;
@@ -1564,7 +1578,7 @@ capture_exception(PyObject *self, PyObject *args, PyObject *kwds)
15641578
}
15651579

15661580
finally:
1567-
_PyXI_ClearExcInfo(&info);
1581+
_PyXI_FreeExcInfo(info);
15681582
if (exc != exc_arg) {
15691583
if (PyErr_Occurred()) {
15701584
PyErr_SetRaisedException(exc);

0 commit comments

Comments
 (0)