Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
67a88c2
Factor out _Py_excinfo.
ericsnowcurrently Oct 24, 2023
a9ea9ac
Extract _PyXI_errcode.
ericsnowcurrently Oct 25, 2023
33d91af
Extract _PyXI_exception_info.
ericsnowcurrently Oct 25, 2023
2424e33
Extract _PyXI_namespace.
ericsnowcurrently Oct 25, 2023
23d6959
Factor out _enter_interpreter(), _exit_interpreter(), etc.
ericsnowcurrently Oct 23, 2023
b08249f
Move enter/exit to crossinterp.c.
ericsnowcurrently Oct 27, 2023
773f5ab
Factor out _sharednsitem_set_value().
ericsnowcurrently Oct 23, 2023
cf7354e
Add _PyXI_NamespaceFromNames().
ericsnowcurrently Oct 31, 2023
6b43620
Add a default arg to _PyXI_ApplyNamespace().
ericsnowcurrently Oct 31, 2023
caef717
Allocate xid dynamically when in target interpreter.
ericsnowcurrently Oct 31, 2023
0bd42e0
Add _PyXI_FillNamespaceFromDict().
ericsnowcurrently Oct 31, 2023
a230f77
Add xid_state structs and lifecycle funcs.
ericsnowcurrently Oct 31, 2023
5675f86
Add PyExc_NotShareableError.
ericsnowcurrently Oct 31, 2023
45488f2
Propagate the ValueError when a value is not shareable.
ericsnowcurrently Oct 31, 2023
6f07364
Propagate errors in _PyXI_Enter() directly.
ericsnowcurrently Oct 31, 2023
0201b7f
Factor out _init_not_shareable_error_type() and _fini_not_shareable_e…
ericsnowcurrently Nov 1, 2023
d32a918
Drop some duplicate lines.
ericsnowcurrently Nov 1, 2023
1d4fc87
Fix a comment.
ericsnowcurrently Nov 1, 2023
88c9d54
Call _PyXI_Fini() *before* the interpreter is cleared.
ericsnowcurrently Nov 1, 2023
2edcb49
Fix init/fini.
ericsnowcurrently Nov 1, 2023
8e53752
Add _get_not_shareable_error_type().
ericsnowcurrently Nov 1, 2023
53764c1
Export fewer symbols.
ericsnowcurrently Nov 1, 2023
cacf969
Merge branch 'main' into crossinterp-utils-additions
ericsnowcurrently Nov 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Move enter/exit to crossinterp.c.
  • Loading branch information
ericsnowcurrently committed Oct 30, 2023
commit b08249fc38fc1f5b66409df4fdd96e4e05f77bd2
55 changes: 55 additions & 0 deletions Include/internal/pycore_crossinterp.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,61 @@ PyAPI_FUNC(_PyXI_namespace *) _PyXI_NamespaceFromDict(PyObject *nsobj);
PyAPI_FUNC(int) _PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj);


// A cross-interpreter session involves entering an interpreter
// (_PyXI_Enter()), doing some work with it, and finally exiting
// that interpreter (_PyXI_Exit()).
//
// At the boundaries of the session, both entering and exiting,
// data may be exchanged between the previous interpreter and the
// target one in a thread-safe way that does not violate the
// isolation between interpreters. This includes setting objects
// in the target's __main__ module on the way in, and capturing
// uncaught exceptions on the way out.
typedef struct xi_session {
// Once a session has been entered, this is the tstate that was
// current before the session. If it is different from cur_tstate
// then we must have switched interpreters. Either way, this will
// be the current tstate once we exit the session.
PyThreadState *prev_tstate;
// Once a session has been entered, this is the current tstate.
// It must be current when the session exits.
PyThreadState *init_tstate;
// This is true if init_tstate needs cleanup during exit.
int own_init_tstate;

// This is true if, while entering the session, init_thread took
// "ownership" of the interpreter's __main__ module. This means
// it is the only thread that is allowed to run code there.
// (Caveat: for now, users may still run exec() against the
// __main__ module's dict, though that isn't advisable.)
int running;
// This is a cached reference to the __dict__ of the entered
// interpreter's __main__ module. It is looked up when at the
// beginning of the session as a convenience.
PyObject *main_ns;

// 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 *exc_override;
// This is set if exit captured an exception to propagate.
_PyXI_exception_info *exc;

// -- pre-allocated memory --
_PyXI_exception_info _exc;
} _PyXI_session;

PyAPI_FUNC(int) _PyXI_Enter(
PyInterpreterState *interp,
PyObject *nsupdates,
_PyXI_session *session);
PyAPI_FUNC(void) _PyXI_Exit(_PyXI_session *session);

PyAPI_FUNC(void) _PyXI_ApplyCapturedException(
_PyXI_session *session,
PyObject *excwrapper);
PyAPI_FUNC(int) _PyXI_HasCapturedException(_PyXI_session *session);


#ifdef __cplusplus
}
#endif
Expand Down
170 changes: 24 additions & 146 deletions Modules/_xxsubinterpretersmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -201,95 +201,6 @@ exceptions_init(PyObject *mod)
return 0;
}

static PyThreadState *
_enter_interpreter(PyInterpreterState *interp)
{
// Switch to interpreter.
PyThreadState *save_tstate = NULL;
PyThreadState *tstate = NULL;
if (interp != PyInterpreterState_Get()) {
tstate = PyThreadState_New(interp);
tstate->_whence = _PyThreadState_WHENCE_EXEC;
// XXX Possible GILState issues?
save_tstate = PyThreadState_Swap(tstate);
}
return save_tstate;
}

static int
_exit_interpreter(PyInterpreterState *interp, PyThreadState *save_tstate,
int errcode, _PyXI_exception_info *exc)
{
int res = 0;
if (errcode != _PyXI_ERR_NO_ERROR) {
assert(exc != NULL);
PyObject *excval = PyErr_GetRaisedException();
*exc = (_PyXI_exception_info){ .interp = interp };
const char *failure = _PyXI_InitExceptionInfo(exc, excval, errcode);
if (failure != NULL) {
fprintf(stderr,
"RunFailedError: script raised an uncaught exception (%s)",
failure);
}
else {
res = -1;
}
if (excval != NULL) {
// XXX Instead, store the rendered traceback on sharedexc,
// attach it to the exception when applied,
// and teach PyErr_Display() to print it.
PyErr_Display(NULL, excval, NULL);
Py_DECREF(excval);
}
assert(!PyErr_Occurred());
}

// Switch back.
if (save_tstate != NULL) {
PyThreadState *tstate = PyThreadState_Get();
PyThreadState_Clear(tstate);
PyThreadState_Swap(save_tstate);
PyThreadState_Delete(tstate);
}

return res;
}

static int
_enter_interpreter_main(PyInterpreterState *interp, PyObject **p_ns)
{
assert(PyInterpreterState_Get() == interp);

if (_PyInterpreterState_SetRunningMain(interp) < 0) {
assert(PyErr_Occurred());
// 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.
PyErr_Clear();
return _PyXI_ERR_ALREADY_RUNNING;
}

PyObject *main_mod = PyUnstable_InterpreterState_GetMainModule(interp);
if (main_mod == NULL) {
return _PyXI_ERR_UNCAUGHT_EXCEPTION;
}
PyObject *ns = PyModule_GetDict(main_mod); // borrowed
Py_DECREF(main_mod);
if (ns == NULL) {
return _PyXI_ERR_UNCAUGHT_EXCEPTION;
}

*p_ns = Py_NewRef(ns);
return _PyXI_ERR_NO_ERROR;
}

static void
_exit_interpreter_main(PyInterpreterState *interp, PyObject *ns)
{
Py_XDECREF(ns);
_PyInterpreterState_SetNotRunningMain(interp);
}

static int
_run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags)
{
Expand All @@ -315,73 +226,38 @@ _run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags)
}

static int
_run_in_interpreter(PyObject *mod, PyInterpreterState *interp,
_run_in_interpreter(PyInterpreterState *interp,
const char *codestr, Py_ssize_t codestrlen,
PyObject *shareables, int flags)
PyObject *shareables, int flags,
PyObject *excwrapper)
{
// Convert the attrs for cross-interpreter use.
_PyXI_namespace *shared = _PyXI_NamespaceFromDict(shareables);
if (shared == NULL && PyErr_Occurred()) {
return -1;
}
_PyXI_exception_info exc;

// Switch to interpreter.
PyThreadState *save_tstate = _enter_interpreter(interp);
PyObject *ns = NULL;
_PyXI_errcode errcode = _enter_interpreter_main(interp, &ns);
if (errcode != _PyXI_ERR_NO_ERROR) {
goto error;
}
errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
assert(!PyErr_Occurred());
_PyXI_session session = {0};

// Apply the cross-interpreter data.
if (shared != NULL) {
if (_PyXI_ApplyNamespace(shared, ns) < 0) {
goto error;
}
// Prep and switch interpreters.
if (_PyXI_Enter(interp, shareables, &session) < 0) {
assert(!PyErr_Occurred());
_PyXI_ApplyExceptionInfo(session.exc, excwrapper);
assert(PyErr_Occurred());
return -1;
}

// Run the script.
errcode = _run_script(ns, codestr, codestrlen, flags);
if (errcode != _PyXI_ERR_NO_ERROR) {
goto error;
}
int res = _run_script(session.main_ns, codestr, codestrlen, flags);

// Switch back.
assert(!PyErr_Occurred());
assert(errcode == _PyXI_ERR_NO_ERROR);
_exit_interpreter_main(interp, ns);
(void)_exit_interpreter(interp, save_tstate, 0, NULL);

goto finally;

error:
// Switch back.
assert(errcode != _PyXI_ERR_NO_ERROR);
if (errcode != _PyXI_ERR_ALREADY_RUNNING) {
_exit_interpreter_main(interp, ns);
}
else {
assert(ns == NULL);
}
int res = _exit_interpreter(interp, save_tstate, errcode, &exc);
assert(res < 0);
// Clean up and switch back.
_PyXI_Exit(&session);

// Propagate any exception out to the caller.
assert(!PyErr_Occurred());
module_state *state = get_module_state(mod);
assert(state != NULL);
_PyXI_ApplyExceptionInfo(&exc, state->RunFailedError);
assert(PyErr_Occurred());

finally:
// Clear the cross-interpreter attrs.
if (shared != NULL) {
_PyXI_FreeNamespace(shared);
if (res < 0) {
_PyXI_ApplyCapturedException(&session, excwrapper);
}
else {
assert(!_PyXI_HasCapturedException(&session));
}

return (int)errcode;
return res;
}


Expand Down Expand Up @@ -664,8 +540,10 @@ _interp_exec(PyObject *self,
}

// Run the code in the interpreter.
int res = _run_in_interpreter(self, interp, codestr, codestrlen,
shared_arg, flags);
module_state *state = get_module_state(self);
assert(state != NULL);
int res = _run_in_interpreter(interp, codestr, codestrlen,
shared_arg, flags, state->RunFailedError);
Py_XDECREF(bytes_obj);
if (res < 0) {
return -1;
Expand Down
Loading