]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-76785: Crossinterp utils additions (gh-111530)
authorEric Snow <ericsnowcurrently@gmail.com>
Wed, 1 Nov 2023 23:36:40 +0000 (17:36 -0600)
committerGitHub <noreply@github.com>
Wed, 1 Nov 2023 23:36:40 +0000 (17:36 -0600)
This moves several general internal APIs out of _xxsubinterpretersmodule.c and into the new Python/crossinterp.c (and the corresponding internal headers).

Specifically:

* _Py_excinfo, etc.:  the initial implementation for non-object exception snapshots (in pycore_pyerrors.h and Python/errors.c)
* _PyXI_exception_info, etc.:  helpers for passing an exception beween interpreters (wraps _Py_excinfo)
* _PyXI_namespace, etc.:  helpers for copying a dict of attrs between interpreters
* _PyXI_Enter(), _PyXI_Exit():  functions that abstract out the transitions between one interpreter and a second that will do some work temporarily

Again, these were all abstracted out of _xxsubinterpretersmodule.c as generalizations.  I plan on proposing these as public API at some point.

Include/internal/pycore_crossinterp.h
Include/internal/pycore_interp.h
Include/internal/pycore_pyerrors.h
Include/internal/pycore_runtime.h
Include/internal/pycore_runtime_init.h
Lib/test/support/interpreters.py
Modules/_xxsubinterpretersmodule.c
Python/crossinterp.c
Python/errors.c
Python/pylifecycle.c
Python/pystate.c

index 59e4cd9ece780d0286deb29700f3db19b4149733..9600dfb9600e602919be3e883f9783c4d0c25137 100644 (file)
@@ -8,6 +8,8 @@ extern "C" {
 #  error "this header requires Py_BUILD_CORE define"
 #endif
 
+#include "pycore_pyerrors.h"
+
 
 /***************************/
 /* cross-interpreter calls */
@@ -124,6 +126,8 @@ struct _xidregitem {
 };
 
 struct _xidregistry {
+    int global;  /* builtin types or heap types */
+    int initialized;
     PyThread_type_lock mutex;
     struct _xidregitem *head;
 };
@@ -133,6 +137,130 @@ PyAPI_FUNC(int) _PyCrossInterpreterData_UnregisterClass(PyTypeObject *);
 PyAPI_FUNC(crossinterpdatafunc) _PyCrossInterpreterData_Lookup(PyObject *);
 
 
+/*****************************/
+/* runtime state & lifecycle */
+/*****************************/
+
+struct _xi_runtime_state {
+    // builtin types
+    // XXX Remove this field once we have a tp_* slot.
+    struct _xidregistry registry;
+};
+
+struct _xi_state {
+    // heap types
+    // XXX Remove this field once we have a tp_* slot.
+    struct _xidregistry registry;
+
+    // heap types
+    PyObject *PyExc_NotShareableError;
+};
+
+extern PyStatus _PyXI_Init(PyInterpreterState *interp);
+extern void _PyXI_Fini(PyInterpreterState *interp);
+
+
+/***************************/
+/* short-term data sharing */
+/***************************/
+
+typedef enum error_code {
+    _PyXI_ERR_NO_ERROR = 0,
+    _PyXI_ERR_UNCAUGHT_EXCEPTION = -1,
+    _PyXI_ERR_OTHER = -2,
+    _PyXI_ERR_NO_MEMORY = -3,
+    _PyXI_ERR_ALREADY_RUNNING = -4,
+    _PyXI_ERR_MAIN_NS_FAILURE = -5,
+    _PyXI_ERR_APPLY_NS_FAILURE = -6,
+    _PyXI_ERR_NOT_SHAREABLE = -7,
+} _PyXI_errcode;
+
+
+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 _PyXI_ERR_UNCAUGHT_EXCEPTION.
+    _Py_excinfo uncaught;
+} _PyXI_exception_info;
+
+PyAPI_FUNC(void) _PyXI_ApplyExceptionInfo(
+    _PyXI_exception_info *info,
+    PyObject *exctype);
+
+typedef struct xi_session _PyXI_session;
+typedef struct _sharedns _PyXI_namespace;
+
+PyAPI_FUNC(void) _PyXI_FreeNamespace(_PyXI_namespace *ns);
+PyAPI_FUNC(_PyXI_namespace *) _PyXI_NamespaceFromNames(PyObject *names);
+PyAPI_FUNC(int) _PyXI_FillNamespaceFromDict(
+    _PyXI_namespace *ns,
+    PyObject *nsobj,
+    _PyXI_session *session);
+PyAPI_FUNC(int) _PyXI_ApplyNamespace(
+    _PyXI_namespace *ns,
+    PyObject *nsobj,
+    PyObject *dflt);
+
+
+// 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.
+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_errcode _exc_override;
+};
+
+PyAPI_FUNC(int) _PyXI_Enter(
+    _PyXI_session *session,
+    PyInterpreterState *interp,
+    PyObject *nsupdates);
+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
index a067a60eca05dfc948f4e7a15b4670fa17ef066c..78b841afae937ea990c6db90ceae8f309a269ccf 100644 (file)
@@ -153,8 +153,8 @@ struct _is {
     Py_ssize_t co_extra_user_count;
     freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS];
 
-    // XXX Remove this field once we have a tp_* slot.
-    struct _xidregistry xidregistry;
+    /* cross-interpreter data and utils */
+    struct _xi_state xi;
 
 #ifdef HAVE_FORK
     PyObject *before_forkers;
index 184eb35e52b47b0603329ef19f6edad0c60b5ae7..67ef71c261654135e1b093d852764ffd6ad76b96 100644 (file)
@@ -68,6 +68,30 @@ extern PyStatus _PyErr_InitTypes(PyInterpreterState *);
 extern void _PyErr_FiniTypes(PyInterpreterState *);
 
 
+/* exception snapshots */
+
+// Ultimately we'd like to preserve enough information about the
+// exception and traceback that we could re-constitute (or at least
+// simulate, a la traceback.TracebackException), and even chain, a copy
+// of the exception in the calling interpreter.
+
+typedef struct _excinfo {
+    const char *type;
+    const char *msg;
+} _Py_excinfo;
+
+extern void _Py_excinfo_Clear(_Py_excinfo *info);
+extern int _Py_excinfo_Copy(_Py_excinfo *dest, _Py_excinfo *src);
+extern const char * _Py_excinfo_InitFromException(
+    _Py_excinfo *info,
+    PyObject *exc);
+extern void _Py_excinfo_Apply(_Py_excinfo *info, PyObject *exctype);
+extern const char * _Py_excinfo_AsUTF8(
+    _Py_excinfo *info,
+    char *buf,
+    size_t bufsize);
+
+
 /* other API */
 
 static inline PyObject* _PyErr_Occurred(PyThreadState *tstate)
index 320e5bbedc068a1604b5107cce8b088530acee0b..8fb73dd6b7dc0bd01fa152dba3b5ac2d1541ac8d 100644 (file)
@@ -200,8 +200,8 @@ typedef struct pyruntimestate {
      possible to facilitate out-of-process observability
      tools. */
 
-    // XXX Remove this field once we have a tp_* slot.
-    struct _xidregistry xidregistry;
+    /* cross-interpreter data and utils */
+    struct _xi_runtime_state xi;
 
     struct _pymem_allocators allocators;
     struct _obmalloc_global_state obmalloc;
index 0799b7e701ce954313d42fe2bd2f1732d7fe1a2f..fa5d8114abf0d7128039768359323ee7a11b16bd 100644 (file)
@@ -95,6 +95,11 @@ extern PyTypeObject _PyExc_MemoryError;
               until _PyInterpreterState_Enable() is called. */ \
             .next_id = -1, \
         }, \
+        .xi = { \
+            .registry = { \
+                .global = 1, \
+            }, \
+        }, \
         /* A TSS key must be initialized with Py_tss_NEEDS_INIT \
            in accordance with the specification. */ \
         .autoTSSkey = Py_tss_NEEDS_INIT, \
index 182f47b19f1dd4f9ad01f9218eb843a1d938d59e..ab9342b767dfaef4ab16eb2dd5b4bbe0fef9e326 100644 (file)
@@ -92,7 +92,7 @@ class Interpreter:
         return _interpreters.destroy(self._id)
 
     # XXX Rename "run" to "exec"?
-    def run(self, src_str, /, *, channels=None):
+    def run(self, src_str, /, channels=None):
         """Run the given source code in the interpreter.
 
         This is essentially the same as calling the builtin "exec"
index ce0d511bc5aa2dc124c47946ac4b62b106a958a8..001fa887847cbd6c83c5ec546b0fc76d74fd0f05 100644 (file)
@@ -7,6 +7,7 @@
 
 #include "Python.h"
 #include "pycore_crossinterp.h"   // struct _xid
+#include "pycore_pyerrors.h"      // _Py_excinfo
 #include "pycore_initconfig.h"    // _PyErr_SetFromPyStatus()
 #include "pycore_modsupport.h"    // _PyArg_BadArgument()
 #include "pycore_pyerrors.h"      // _PyErr_ChainExceptions1()
 #define MODULE_NAME "_xxsubinterpreters"
 
 
-static const char *
-_copy_raw_string(PyObject *strobj)
-{
-    const char *str = PyUnicode_AsUTF8(strobj);
-    if (str == NULL) {
-        return NULL;
-    }
-    char *copied = PyMem_RawMalloc(strlen(str)+1);
-    if (copied == NULL) {
-        PyErr_NoMemory();
-        return NULL;
-    }
-    strcpy(copied, str);
-    return copied;
-}
-
 static PyInterpreterState *
 _get_current_interp(void)
 {
@@ -62,21 +47,6 @@ add_new_exception(PyObject *mod, const char *name, PyObject *base)
 #define ADD_NEW_EXCEPTION(MOD, NAME, BASE) \
     add_new_exception(MOD, MODULE_NAME "." Py_STRINGIFY(NAME), BASE)
 
-static int
-_release_xid_data(_PyCrossInterpreterData *data)
-{
-    PyObject *exc = PyErr_GetRaisedException();
-    int res = _PyCrossInterpreterData_Release(data);
-    if (res < 0) {
-        /* The owning interpreter is already destroyed. */
-        _PyCrossInterpreterData_Clear(NULL, data);
-        // XXX Emit a warning?
-        PyErr_Clear();
-    }
-    PyErr_SetRaisedException(exc);
-    return res;
-}
-
 
 /* module state *************************************************************/
 
@@ -113,263 +83,6 @@ clear_module_state(module_state *state)
 }
 
 
-/* data-sharing-specific code ***********************************************/
-
-struct _sharednsitem {
-    const char *name;
-    _PyCrossInterpreterData data;
-};
-
-static void _sharednsitem_clear(struct _sharednsitem *);  // forward
-
-static int
-_sharednsitem_init(struct _sharednsitem *item, PyObject *key, PyObject *value)
-{
-    item->name = _copy_raw_string(key);
-    if (item->name == NULL) {
-        return -1;
-    }
-    if (_PyObject_GetCrossInterpreterData(value, &item->data) != 0) {
-        _sharednsitem_clear(item);
-        return -1;
-    }
-    return 0;
-}
-
-static void
-_sharednsitem_clear(struct _sharednsitem *item)
-{
-    if (item->name != NULL) {
-        PyMem_RawFree((void *)item->name);
-        item->name = NULL;
-    }
-    (void)_release_xid_data(&item->data);
-}
-
-static int
-_sharednsitem_apply(struct _sharednsitem *item, PyObject *ns)
-{
-    PyObject *name = PyUnicode_FromString(item->name);
-    if (name == NULL) {
-        return -1;
-    }
-    PyObject *value = _PyCrossInterpreterData_NewObject(&item->data);
-    if (value == NULL) {
-        Py_DECREF(name);
-        return -1;
-    }
-    int res = PyDict_SetItem(ns, name, value);
-    Py_DECREF(name);
-    Py_DECREF(value);
-    return res;
-}
-
-typedef struct _sharedns {
-    Py_ssize_t len;
-    struct _sharednsitem* items;
-} _sharedns;
-
-static _sharedns *
-_sharedns_new(Py_ssize_t len)
-{
-    _sharedns *shared = PyMem_RawCalloc(sizeof(_sharedns), 1);
-    if (shared == NULL) {
-        PyErr_NoMemory();
-        return NULL;
-    }
-    shared->len = len;
-    shared->items = PyMem_RawCalloc(sizeof(struct _sharednsitem), len);
-    if (shared->items == NULL) {
-        PyErr_NoMemory();
-        PyMem_RawFree(shared);
-        return NULL;
-    }
-    return shared;
-}
-
-static void
-_sharedns_free(_sharedns *shared)
-{
-    for (Py_ssize_t i=0; i < shared->len; i++) {
-        _sharednsitem_clear(&shared->items[i]);
-    }
-    PyMem_RawFree(shared->items);
-    PyMem_RawFree(shared);
-}
-
-static _sharedns *
-_get_shared_ns(PyObject *shareable)
-{
-    if (shareable == NULL || shareable == Py_None) {
-        return NULL;
-    }
-    Py_ssize_t len = PyDict_Size(shareable);
-    if (len == 0) {
-        return NULL;
-    }
-
-    _sharedns *shared = _sharedns_new(len);
-    if (shared == NULL) {
-        return NULL;
-    }
-    Py_ssize_t pos = 0;
-    for (Py_ssize_t i=0; i < len; i++) {
-        PyObject *key, *value;
-        if (PyDict_Next(shareable, &pos, &key, &value) == 0) {
-            break;
-        }
-        if (_sharednsitem_init(&shared->items[i], key, value) != 0) {
-            break;
-        }
-    }
-    if (PyErr_Occurred()) {
-        _sharedns_free(shared);
-        return NULL;
-    }
-    return shared;
-}
-
-static int
-_sharedns_apply(_sharedns *shared, PyObject *ns)
-{
-    for (Py_ssize_t i=0; i < shared->len; i++) {
-        if (_sharednsitem_apply(&shared->items[i], ns) != 0) {
-            return -1;
-        }
-    }
-    return 0;
-}
-
-// Ultimately we'd like to preserve enough information about the
-// exception and traceback that we could re-constitute (or at least
-// simulate, a la traceback.TracebackException), and even chain, a copy
-// of the exception in the calling interpreter.
-
-typedef struct _sharedexception {
-    PyInterpreterState *interp;
-#define ERR_NOT_SET 0
-#define ERR_NO_MEMORY 1
-#define ERR_ALREADY_RUNNING 2
-    int code;
-    const char *name;
-    const char *msg;
-} _sharedexception;
-
-static const struct _sharedexception no_exception = {
-    .name = NULL,
-    .msg = NULL,
-};
-
-static void
-_sharedexception_clear(_sharedexception *exc)
-{
-    if (exc->name != NULL) {
-        PyMem_RawFree((void *)exc->name);
-    }
-    if (exc->msg != NULL) {
-        PyMem_RawFree((void *)exc->msg);
-    }
-}
-
-static const char *
-_sharedexception_bind(PyObject *exc, int code, _sharedexception *sharedexc)
-{
-    if (sharedexc->interp == NULL) {
-        sharedexc->interp = PyInterpreterState_Get();
-    }
-
-    if (code != ERR_NOT_SET) {
-        assert(exc == NULL);
-        assert(code > 0);
-        sharedexc->code = code;
-        return NULL;
-    }
-
-    assert(exc != NULL);
-    const char *failure = NULL;
-
-    PyObject *nameobj = PyUnicode_FromString(Py_TYPE(exc)->tp_name);
-    if (nameobj == NULL) {
-        failure = "unable to format exception type name";
-        code = ERR_NO_MEMORY;
-        goto error;
-    }
-    sharedexc->name = _copy_raw_string(nameobj);
-    Py_DECREF(nameobj);
-    if (sharedexc->name == NULL) {
-        if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
-            failure = "out of memory copying exception type name";
-        } else {
-            failure = "unable to encode and copy exception type name";
-        }
-        code = ERR_NO_MEMORY;
-        goto error;
-    }
-
-    if (exc != NULL) {
-        PyObject *msgobj = PyObject_Str(exc);
-        if (msgobj == NULL) {
-            failure = "unable to format exception message";
-            code = ERR_NO_MEMORY;
-            goto error;
-        }
-        sharedexc->msg = _copy_raw_string(msgobj);
-        Py_DECREF(msgobj);
-        if (sharedexc->msg == NULL) {
-            if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
-                failure = "out of memory copying exception message";
-            } else {
-                failure = "unable to encode and copy exception message";
-            }
-            code = ERR_NO_MEMORY;
-            goto error;
-        }
-    }
-
-    return NULL;
-
-error:
-    assert(failure != NULL);
-    PyErr_Clear();
-    _sharedexception_clear(sharedexc);
-    *sharedexc = (_sharedexception){
-        .interp = sharedexc->interp,
-        .code = code,
-    };
-    return failure;
-}
-
-static void
-_sharedexception_apply(_sharedexception *exc, PyObject *wrapperclass)
-{
-    if (exc->name != NULL) {
-        assert(exc->code == ERR_NOT_SET);
-        if (exc->msg != NULL) {
-            PyErr_Format(wrapperclass, "%s: %s",  exc->name, exc->msg);
-        }
-        else {
-            PyErr_SetString(wrapperclass, exc->name);
-        }
-    }
-    else if (exc->msg != NULL) {
-        assert(exc->code == ERR_NOT_SET);
-        PyErr_SetString(wrapperclass, exc->msg);
-    }
-    else if (exc->code == ERR_NO_MEMORY) {
-        PyErr_NoMemory();
-    }
-    else if (exc->code == ERR_ALREADY_RUNNING) {
-        assert(exc->interp != NULL);
-        assert(_PyInterpreterState_IsRunningMain(exc->interp));
-        _PyInterpreterState_FailIfRunningMain(exc->interp);
-    }
-    else {
-        assert(exc->code == ERR_NOT_SET);
-        PyErr_SetNone(wrapperclass);
-    }
-}
-
-
 /* Python code **************************************************************/
 
 static const char *
@@ -489,43 +202,8 @@ exceptions_init(PyObject *mod)
 }
 
 static int
-_run_script(PyInterpreterState *interp,
-            const char *codestr, Py_ssize_t codestrlen,
-            _sharedns *shared, _sharedexception *sharedexc, int flags)
+_run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags)
 {
-    int errcode = ERR_NOT_SET;
-
-    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();
-        errcode = ERR_ALREADY_RUNNING;
-        goto error;
-    }
-
-    PyObject *excval = NULL;
-    PyObject *main_mod = PyUnstable_InterpreterState_GetMainModule(interp);
-    if (main_mod == NULL) {
-        goto error;
-    }
-    PyObject *ns = PyModule_GetDict(main_mod);  // borrowed
-    Py_DECREF(main_mod);
-    if (ns == NULL) {
-        goto error;
-    }
-    Py_INCREF(ns);
-
-    // Apply the cross-interpreter data.
-    if (shared != NULL) {
-        if (_sharedns_apply(shared, ns) != 0) {
-            Py_DECREF(ns);
-            goto error;
-        }
-    }
-
-    // Run the script/code/etc.
     PyObject *result = NULL;
     if (flags & RUN_TEXT) {
         result = PyRun_StringFlags(codestr, Py_file_input, ns, ns, NULL);
@@ -540,86 +218,46 @@ _run_script(PyInterpreterState *interp,
     else {
         Py_UNREACHABLE();
     }
-    Py_DECREF(ns);
     if (result == NULL) {
-        goto error;
-    }
-    else {
-        Py_DECREF(result);  // We throw away the result.
+        return -1;
     }
-    _PyInterpreterState_SetNotRunningMain(interp);
-
-    *sharedexc = no_exception;
+    Py_DECREF(result);  // We throw away the result.
     return 0;
-
-error:
-    excval = PyErr_GetRaisedException();
-    const char *failure = _sharedexception_bind(excval, errcode, sharedexc);
-    if (failure != NULL) {
-        fprintf(stderr,
-                "RunFailedError: script raised an uncaught exception (%s)",
-                failure);
-    }
-    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);
-    }
-    if (errcode != ERR_ALREADY_RUNNING) {
-        _PyInterpreterState_SetNotRunningMain(interp);
-    }
-    assert(!PyErr_Occurred());
-    return -1;
 }
 
 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)
 {
-    module_state *state = get_module_state(mod);
-    assert(state != NULL);
+    assert(!PyErr_Occurred());
+    _PyXI_session session = {0};
 
-    _sharedns *shared = _get_shared_ns(shareables);
-    if (shared == NULL && PyErr_Occurred()) {
+    // Prep and switch interpreters.
+    if (_PyXI_Enter(&session, interp, shareables) < 0) {
+        assert(!PyErr_Occurred());
+        _PyXI_ApplyExceptionInfo(session.exc, excwrapper);
+        assert(PyErr_Occurred());
         return -1;
     }
 
-    // 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);
-    }
-
     // Run the script.
-    _sharedexception exc = (_sharedexception){ .interp = interp };
-    int result = _run_script(interp, codestr, codestrlen, shared, &exc, flags);
+    int res = _run_script(session.main_ns, codestr, codestrlen, flags);
 
-    // Switch back.
-    if (save_tstate != NULL) {
-        PyThreadState_Clear(tstate);
-        PyThreadState_Swap(save_tstate);
-        PyThreadState_Delete(tstate);
-    }
+    // Clean up and switch back.
+    _PyXI_Exit(&session);
 
     // Propagate any exception out to the caller.
-    if (result < 0) {
-        assert(!PyErr_Occurred());
-        _sharedexception_apply(&exc, state->RunFailedError);
-        assert(PyErr_Occurred());
+    assert(!PyErr_Occurred());
+    if (res < 0) {
+        _PyXI_ApplyCapturedException(&session, excwrapper);
     }
-
-    if (shared != NULL) {
-        _sharedns_free(shared);
+    else {
+        assert(!_PyXI_HasCapturedException(&session));
     }
 
-    return result;
+    return res;
 }
 
 
@@ -805,7 +443,6 @@ PyDoc_STRVAR(get_main_doc,
 \n\
 Return the ID of main interpreter.");
 
-
 static PyUnicodeObject *
 convert_script_arg(PyObject *arg, const char *fname, const char *displayname,
                    const char *expected)
@@ -903,10 +540,12 @@ _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) {
+    if (res < 0) {
         return -1;
     }
 
@@ -981,7 +620,7 @@ interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)
         return NULL;
     }
 
-    int res = _interp_exec(self, id, (PyObject *)script, shared);
+    int res = _interp_exec(self, id, script, shared);
     Py_DECREF(script);
     if (res < 0) {
         return NULL;
index 17c476ba4369c473aa44ad4ba69d556da51db55a..00eccbdf979504a8fe6cff49328f94d6ad67ff8f 100644 (file)
@@ -4,6 +4,7 @@
 #include "Python.h"
 #include "pycore_ceval.h"         // _Py_simple_func
 #include "pycore_crossinterp.h"   // struct _xid
+#include "pycore_initconfig.h"    // _PyStatus_OK()
 #include "pycore_pyerrors.h"      // _PyErr_Clear()
 #include "pycore_pystate.h"       // _PyInterpreterState_GET()
 #include "pycore_weakref.h"       // _PyWeakref_GET_REF()
@@ -64,6 +65,38 @@ _PyCrossInterpreterData_Free(_PyCrossInterpreterData *xid)
 }
 
 
+/* exceptions */
+
+static PyStatus
+_init_not_shareable_error_type(PyInterpreterState *interp)
+{
+    const char *name = "_interpreters.NotShareableError";
+    PyObject *base = PyExc_ValueError;
+    PyObject *ns = NULL;
+    PyObject *exctype = PyErr_NewException(name, base, ns);
+    if (exctype == NULL) {
+        PyErr_Clear();
+        return _PyStatus_ERR("could not initialize NotShareableError");
+    }
+
+    interp->xi.PyExc_NotShareableError = exctype;
+    return _PyStatus_OK();
+}
+
+static void
+_fini_not_shareable_error_type(PyInterpreterState *interp)
+{
+    Py_CLEAR(interp->xi.PyExc_NotShareableError);
+}
+
+static PyObject *
+_get_not_shareable_error_type(PyInterpreterState *interp)
+{
+    assert(interp->xi.PyExc_NotShareableError != NULL);
+    return interp->xi.PyExc_NotShareableError;
+}
+
+
 /* defining cross-interpreter data */
 
 static inline void
@@ -171,25 +204,54 @@ _check_xidata(PyThreadState *tstate, _PyCrossInterpreterData *data)
     return 0;
 }
 
-crossinterpdatafunc _PyCrossInterpreterData_Lookup(PyObject *);
+static crossinterpdatafunc _lookup_getdata_from_registry(
+                                            PyInterpreterState *, PyObject *);
 
-/* This is a separate func from _PyCrossInterpreterData_Lookup in order
-   to keep the registry code separate. */
 static crossinterpdatafunc
-_lookup_getdata(PyObject *obj)
+_lookup_getdata(PyInterpreterState *interp, PyObject *obj)
+{
+   /* Cross-interpreter objects are looked up by exact match on the class.
+      We can reassess this policy when we move from a global registry to a
+      tp_* slot. */
+    return _lookup_getdata_from_registry(interp, obj);
+}
+
+crossinterpdatafunc
+_PyCrossInterpreterData_Lookup(PyObject *obj)
+{
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    return _lookup_getdata(interp, obj);
+}
+
+static inline void
+_set_xid_lookup_failure(PyInterpreterState *interp,
+                        PyObject *obj, const char *msg)
 {
-    crossinterpdatafunc getdata = _PyCrossInterpreterData_Lookup(obj);
-    if (getdata == NULL && PyErr_Occurred() == 0)
-        PyErr_Format(PyExc_ValueError,
+    PyObject *exctype = _get_not_shareable_error_type(interp);
+    assert(exctype != NULL);
+    if (msg != NULL) {
+        assert(obj == NULL);
+        PyErr_SetString(exctype, msg);
+    }
+    else if (obj == NULL) {
+        PyErr_SetString(exctype,
+                        "object does not support cross-interpreter data");
+    }
+    else {
+        PyErr_Format(exctype,
                      "%S does not support cross-interpreter data", obj);
-    return getdata;
+    }
 }
 
 int
 _PyObject_CheckCrossInterpreterData(PyObject *obj)
 {
-    crossinterpdatafunc getdata = _lookup_getdata(obj);
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    crossinterpdatafunc getdata = _lookup_getdata(interp, obj);
     if (getdata == NULL) {
+        if (!PyErr_Occurred()) {
+            _set_xid_lookup_failure(interp, obj, NULL);
+        }
         return -1;
     }
     return 0;
@@ -211,9 +273,12 @@ _PyObject_GetCrossInterpreterData(PyObject *obj, _PyCrossInterpreterData *data)
 
     // Call the "getdata" func for the object.
     Py_INCREF(obj);
-    crossinterpdatafunc getdata = _lookup_getdata(obj);
+    crossinterpdatafunc getdata = _lookup_getdata(interp, obj);
     if (getdata == NULL) {
         Py_DECREF(obj);
+        if (!PyErr_Occurred()) {
+            _set_xid_lookup_failure(interp, obj, NULL);
+        }
         return -1;
     }
     int res = getdata(tstate, obj, data);
@@ -300,6 +365,28 @@ _PyCrossInterpreterData_ReleaseAndRawFree(_PyCrossInterpreterData *data)
    alternative would be to add a tp_* slot for a class's
    crossinterpdatafunc. It would be simpler and more efficient. */
 
+static inline struct _xidregistry *
+_get_global_xidregistry(_PyRuntimeState *runtime)
+{
+    return &runtime->xi.registry;
+}
+
+static inline struct _xidregistry *
+_get_xidregistry(PyInterpreterState *interp)
+{
+    return &interp->xi.registry;
+}
+
+static inline struct _xidregistry *
+_get_xidregistry_for_type(PyInterpreterState *interp, PyTypeObject *cls)
+{
+    struct _xidregistry *registry = _get_global_xidregistry(interp->runtime);
+    if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) {
+        registry = _get_xidregistry(interp);
+    }
+    return registry;
+}
+
 static int
 _xidregistry_add_type(struct _xidregistry *xidregistry,
                       PyTypeObject *cls, crossinterpdatafunc getdata)
@@ -351,9 +438,8 @@ _xidregistry_remove_entry(struct _xidregistry *xidregistry,
     return next;
 }
 
-// This is used in pystate.c (for now).
-void
-_Py_xidregistry_clear(struct _xidregistry *xidregistry)
+static void
+_xidregistry_clear(struct _xidregistry *xidregistry)
 {
     struct _xidregitem *cur = xidregistry->head;
     xidregistry->head = NULL;
@@ -365,6 +451,22 @@ _Py_xidregistry_clear(struct _xidregistry *xidregistry)
     }
 }
 
+static void
+_xidregistry_lock(struct _xidregistry *registry)
+{
+    if (registry->mutex != NULL) {
+        PyThread_acquire_lock(registry->mutex, WAIT_LOCK);
+    }
+}
+
+static void
+_xidregistry_unlock(struct _xidregistry *registry)
+{
+    if (registry->mutex != NULL) {
+        PyThread_release_lock(registry->mutex);
+    }
+}
+
 static struct _xidregitem *
 _xidregistry_find_type(struct _xidregistry *xidregistry, PyTypeObject *cls)
 {
@@ -391,30 +493,6 @@ _xidregistry_find_type(struct _xidregistry *xidregistry, PyTypeObject *cls)
     return NULL;
 }
 
-static inline struct _xidregistry *
-_get_xidregistry(PyInterpreterState *interp, PyTypeObject *cls)
-{
-    struct _xidregistry *xidregistry = &interp->runtime->xidregistry;
-    if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) {
-        assert(interp->xidregistry.mutex == xidregistry->mutex);
-        xidregistry = &interp->xidregistry;
-    }
-    return xidregistry;
-}
-
-static void _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry);
-
-static inline void
-_ensure_builtins_xid(PyInterpreterState *interp, struct _xidregistry *xidregistry)
-{
-    if (xidregistry != &interp->xidregistry) {
-        assert(xidregistry == &interp->runtime->xidregistry);
-        if (xidregistry->head == NULL) {
-            _register_builtins_for_crossinterpreter_data(xidregistry);
-        }
-    }
-}
-
 int
 _PyCrossInterpreterData_RegisterClass(PyTypeObject *cls,
                                       crossinterpdatafunc getdata)
@@ -430,10 +508,8 @@ _PyCrossInterpreterData_RegisterClass(PyTypeObject *cls,
 
     int res = 0;
     PyInterpreterState *interp = _PyInterpreterState_GET();
-    struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
-    PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
-
-    _ensure_builtins_xid(interp, xidregistry);
+    struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls);
+    _xidregistry_lock(xidregistry);
 
     struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
     if (matched != NULL) {
@@ -445,7 +521,7 @@ _PyCrossInterpreterData_RegisterClass(PyTypeObject *cls,
     res = _xidregistry_add_type(xidregistry, cls, getdata);
 
 finally:
-    PyThread_release_lock(xidregistry->mutex);
+    _xidregistry_unlock(xidregistry);
     return res;
 }
 
@@ -454,8 +530,8 @@ _PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls)
 {
     int res = 0;
     PyInterpreterState *interp = _PyInterpreterState_GET();
-    struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
-    PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
+    struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls);
+    _xidregistry_lock(xidregistry);
 
     struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
     if (matched != NULL) {
@@ -467,30 +543,22 @@ _PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls)
         res = 1;
     }
 
-    PyThread_release_lock(xidregistry->mutex);
+    _xidregistry_unlock(xidregistry);
     return res;
 }
 
-
-/* Cross-interpreter objects are looked up by exact match on the class.
-   We can reassess this policy when we move from a global registry to a
-   tp_* slot. */
-
-crossinterpdatafunc
-_PyCrossInterpreterData_Lookup(PyObject *obj)
+static crossinterpdatafunc
+_lookup_getdata_from_registry(PyInterpreterState *interp, PyObject *obj)
 {
     PyTypeObject *cls = Py_TYPE(obj);
 
-    PyInterpreterState *interp = _PyInterpreterState_GET();
-    struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
-    PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
-
-    _ensure_builtins_xid(interp, xidregistry);
+    struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls);
+    _xidregistry_lock(xidregistry);
 
     struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
     crossinterpdatafunc func = matched != NULL ? matched->getdata : NULL;
 
-    PyThread_release_lock(xidregistry->mutex);
+    _xidregistry_unlock(xidregistry);
     return func;
 }
 
@@ -653,3 +721,811 @@ _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry)
         Py_FatalError("could not register float for cross-interpreter sharing");
     }
 }
+
+/* registry lifecycle */
+
+static void
+_xidregistry_init(struct _xidregistry *registry)
+{
+    if (registry->initialized) {
+        return;
+    }
+    registry->initialized = 1;
+
+    if (registry->global) {
+        // We manage the mutex lifecycle in pystate.c.
+        assert(registry->mutex != NULL);
+
+        // Registering the builtins is cheap so we don't bother doing it lazily.
+        assert(registry->head == NULL);
+        _register_builtins_for_crossinterpreter_data(registry);
+    }
+    else {
+        // Within an interpreter we rely on the GIL instead of a separate lock.
+        assert(registry->mutex == NULL);
+
+        // There's nothing else to initialize.
+    }
+}
+
+static void
+_xidregistry_fini(struct _xidregistry *registry)
+{
+    if (!registry->initialized) {
+        return;
+    }
+    registry->initialized = 0;
+
+    _xidregistry_clear(registry);
+
+    if (registry->global) {
+        // We manage the mutex lifecycle in pystate.c.
+        assert(registry->mutex != NULL);
+    }
+    else {
+        // There's nothing else to finalize.
+
+        // Within an interpreter we rely on the GIL instead of a separate lock.
+        assert(registry->mutex == NULL);
+    }
+}
+
+
+/*************************/
+/* convenience utilities */
+/*************************/
+
+static const char *
+_copy_string_obj_raw(PyObject *strobj)
+{
+    const char *str = PyUnicode_AsUTF8(strobj);
+    if (str == NULL) {
+        return NULL;
+    }
+
+    char *copied = PyMem_RawMalloc(strlen(str)+1);
+    if (copied == NULL) {
+        PyErr_NoMemory();
+        return NULL;
+    }
+    strcpy(copied, str);
+    return copied;
+}
+
+static int
+_release_xid_data(_PyCrossInterpreterData *data, int rawfree)
+{
+    PyObject *exc = PyErr_GetRaisedException();
+    int res = rawfree
+        ? _PyCrossInterpreterData_Release(data)
+        : _PyCrossInterpreterData_ReleaseAndRawFree(data);
+    if (res < 0) {
+        /* The owning interpreter is already destroyed. */
+        _PyCrossInterpreterData_Clear(NULL, data);
+        // XXX Emit a warning?
+        PyErr_Clear();
+    }
+    PyErr_SetRaisedException(exc);
+    return res;
+}
+
+
+/***************************/
+/* short-term data sharing */
+/***************************/
+
+/* error codes */
+
+static int
+_PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
+{
+    assert(!PyErr_Occurred());
+    switch (code) {
+    case _PyXI_ERR_NO_ERROR:  // fall through
+    case _PyXI_ERR_UNCAUGHT_EXCEPTION:
+        // There is nothing to apply.
+#ifdef Py_DEBUG
+        Py_UNREACHABLE();
+#endif
+        return 0;
+    case _PyXI_ERR_OTHER:
+        // XXX msg?
+        PyErr_SetNone(PyExc_RuntimeError);
+        break;
+    case _PyXI_ERR_NO_MEMORY:
+        PyErr_NoMemory();
+        break;
+    case _PyXI_ERR_ALREADY_RUNNING:
+        assert(interp != NULL);
+        assert(_PyInterpreterState_IsRunningMain(interp));
+        _PyInterpreterState_FailIfRunningMain(interp);
+        break;
+    case _PyXI_ERR_MAIN_NS_FAILURE:
+        PyErr_SetString(PyExc_RuntimeError,
+                        "failed to get __main__ namespace");
+        break;
+    case _PyXI_ERR_APPLY_NS_FAILURE:
+        PyErr_SetString(PyExc_RuntimeError,
+                        "failed to apply namespace to __main__");
+        break;
+    case _PyXI_ERR_NOT_SHAREABLE:
+        _set_xid_lookup_failure(interp, NULL, NULL);
+        break;
+    default:
+#ifdef Py_DEBUG
+        Py_UNREACHABLE();
+#else
+        PyErr_Format(PyExc_RuntimeError, "unsupported error code %d", code);
+#endif
+    }
+    assert(PyErr_Occurred());
+    return -1;
+}
+
+/* shared exceptions */
+
+static const char *
+_PyXI_InitExceptionInfo(_PyXI_exception_info *info,
+                        PyObject *excobj, _PyXI_errcode code)
+{
+    if (info->interp == NULL) {
+        info->interp = PyInterpreterState_Get();
+    }
+
+    const char *failure = NULL;
+    if (code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
+        // There is an unhandled exception we need to propagate.
+        failure = _Py_excinfo_InitFromException(&info->uncaught, excobj);
+        if (failure != NULL) {
+            // We failed to initialize info->uncaught.
+            // XXX Print the excobj/traceback?  Emit a warning?
+            // XXX Print the current exception/traceback?
+            if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
+                info->code = _PyXI_ERR_NO_MEMORY;
+            }
+            else {
+                info->code = _PyXI_ERR_OTHER;
+            }
+            PyErr_Clear();
+        }
+        else {
+            info->code = code;
+        }
+        assert(info->code != _PyXI_ERR_NO_ERROR);
+    }
+    else {
+        // There is an error code we need to propagate.
+        assert(excobj == NULL);
+        assert(code != _PyXI_ERR_NO_ERROR);
+        info->code = code;
+        _Py_excinfo_Clear(&info->uncaught);
+    }
+    return failure;
+}
+
+void
+_PyXI_ApplyExceptionInfo(_PyXI_exception_info *info, PyObject *exctype)
+{
+    if (info->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
+        // Raise an exception that proxies the propagated exception.
+        _Py_excinfo_Apply(&info->uncaught, exctype);
+    }
+    else if (info->code == _PyXI_ERR_NOT_SHAREABLE) {
+        // Propagate the exception directly.
+        _set_xid_lookup_failure(info->interp, NULL, info->uncaught.msg);
+    }
+    else {
+        // Raise an exception corresponding to the code.
+        assert(info->code != _PyXI_ERR_NO_ERROR);
+        (void)_PyXI_ApplyErrorCode(info->code, info->interp);
+        if (info->uncaught.type != NULL || info->uncaught.msg != NULL) {
+            // __context__ will be set to a proxy of the propagated exception.
+            PyObject *exc = PyErr_GetRaisedException();
+            _Py_excinfo_Apply(&info->uncaught, exctype);
+            PyObject *exc2 = PyErr_GetRaisedException();
+            PyException_SetContext(exc, exc2);
+            PyErr_SetRaisedException(exc);
+        }
+    }
+    assert(PyErr_Occurred());
+}
+
+/* shared namespaces */
+
+typedef struct _sharednsitem {
+    int64_t interpid;
+    const char *name;
+    _PyCrossInterpreterData *data;
+    _PyCrossInterpreterData _data;
+} _PyXI_namespace_item;
+
+static void _sharednsitem_clear(_PyXI_namespace_item *);  // forward
+
+static int
+_sharednsitem_init(_PyXI_namespace_item *item, int64_t interpid, PyObject *key)
+{
+    assert(interpid >= 0);
+    item->interpid = interpid;
+    item->name = _copy_string_obj_raw(key);
+    if (item->name == NULL) {
+        return -1;
+    }
+    item->data = NULL;
+    return 0;
+}
+
+static int
+_sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value)
+{
+    assert(item->name != NULL);
+    assert(item->data == NULL);
+    item->data = &item->_data;
+    if (item->interpid == PyInterpreterState_GetID(PyInterpreterState_Get())) {
+        item->data = &item->_data;
+    }
+    else {
+        item->data = PyMem_RawMalloc(sizeof(_PyCrossInterpreterData));
+        if (item->data == NULL) {
+            PyErr_NoMemory();
+            return -1;
+        }
+    }
+    if (_PyObject_GetCrossInterpreterData(value, item->data) != 0) {
+        if (item->data != &item->_data) {
+            PyMem_RawFree(item->data);
+        }
+        item->data = NULL;
+        // The caller may want to propagate PyExc_NotShareableError
+        // if currently switched between interpreters.
+        return -1;
+    }
+    return 0;
+}
+
+static void
+_sharednsitem_clear_data(_PyXI_namespace_item *item)
+{
+    _PyCrossInterpreterData *data = item->data;
+    if (data != NULL) {
+        item->data = NULL;
+        int rawfree = (data == &item->_data);
+        (void)_release_xid_data(data, rawfree);
+    }
+}
+
+static void
+_sharednsitem_clear(_PyXI_namespace_item *item)
+{
+    if (item->name != NULL) {
+        PyMem_RawFree((void *)item->name);
+        item->name = NULL;
+    }
+    _sharednsitem_clear_data(item);
+}
+
+static int
+_sharednsitem_copy_from_ns(struct _sharednsitem *item, PyObject *ns)
+{
+    assert(item->name != NULL);
+    assert(item->data == NULL);
+    PyObject *value = PyDict_GetItemString(ns, item->name);  // borrowed
+    if (value == NULL) {
+        if (PyErr_Occurred()) {
+            return -1;
+        }
+        // When applied, this item will be set to the default (or fail).
+        return 0;
+    }
+    if (_sharednsitem_set_value(item, value) < 0) {
+        return -1;
+    }
+    return 0;
+}
+
+static int
+_sharednsitem_apply(_PyXI_namespace_item *item, PyObject *ns, PyObject *dflt)
+{
+    PyObject *name = PyUnicode_FromString(item->name);
+    if (name == NULL) {
+        return -1;
+    }
+    PyObject *value;
+    if (item->data != NULL) {
+        value = _PyCrossInterpreterData_NewObject(item->data);
+        if (value == NULL) {
+            Py_DECREF(name);
+            return -1;
+        }
+    }
+    else {
+        value = Py_NewRef(dflt);
+    }
+    int res = PyDict_SetItem(ns, name, value);
+    Py_DECREF(name);
+    Py_DECREF(value);
+    return res;
+}
+
+struct _sharedns {
+    PyInterpreterState *interp;
+    Py_ssize_t len;
+    _PyXI_namespace_item *items;
+};
+
+static _PyXI_namespace *
+_sharedns_new(Py_ssize_t len)
+{
+    _PyXI_namespace *shared = PyMem_RawCalloc(sizeof(_PyXI_namespace), 1);
+    if (shared == NULL) {
+        PyErr_NoMemory();
+        return NULL;
+    }
+    shared->len = len;
+    shared->items = PyMem_RawCalloc(sizeof(struct _sharednsitem), len);
+    if (shared->items == NULL) {
+        PyErr_NoMemory();
+        PyMem_RawFree(shared);
+        return NULL;
+    }
+    return shared;
+}
+
+static void
+_free_xi_namespace(_PyXI_namespace *ns)
+{
+    for (Py_ssize_t i=0; i < ns->len; i++) {
+        _sharednsitem_clear(&ns->items[i]);
+    }
+    PyMem_RawFree(ns->items);
+    PyMem_RawFree(ns);
+}
+
+static int
+_pending_free_xi_namespace(void *arg)
+{
+    _PyXI_namespace *ns = (_PyXI_namespace *)arg;
+    _free_xi_namespace(ns);
+    return 0;
+}
+
+void
+_PyXI_FreeNamespace(_PyXI_namespace *ns)
+{
+    if (ns->len == 0) {
+        return;
+    }
+    PyInterpreterState *interp = ns->interp;
+    if (interp == NULL) {
+        assert(ns->items[0].name == NULL);
+        // No data was actually set, so we can free the items
+        // without clearing each item's XI data.
+        PyMem_RawFree(ns->items);
+        PyMem_RawFree(ns);
+    }
+    else {
+        // We can assume the first item represents all items.
+        assert(ns->items[0].data->interpid == interp->id);
+        if (interp == PyInterpreterState_Get()) {
+            // We can avoid pending calls.
+            _free_xi_namespace(ns);
+        }
+        else {
+            // We have to use a pending call due to data in another interpreter.
+            // XXX Make sure the pending call was added?
+            _PyEval_AddPendingCall(interp, _pending_free_xi_namespace, ns, 0);
+        }
+    }
+}
+
+_PyXI_namespace *
+_PyXI_NamespaceFromNames(PyObject *names)
+{
+    if (names == NULL || names == Py_None) {
+        return NULL;
+    }
+
+    Py_ssize_t len = PySequence_Size(names);
+    if (len <= 0) {
+        return NULL;
+    }
+
+    _PyXI_namespace *ns = _sharedns_new(len);
+    if (ns == NULL) {
+        return NULL;
+    }
+    int64_t interpid = PyInterpreterState_Get()->id;
+    for (Py_ssize_t i=0; i < len; i++) {
+        PyObject *key = PySequence_GetItem(names, i);
+        if (key == NULL) {
+            break;
+        }
+        struct _sharednsitem *item = &ns->items[i];
+        int res = _sharednsitem_init(item, interpid, key);
+        Py_DECREF(key);
+        if (res < 0) {
+            break;
+        }
+    }
+    if (PyErr_Occurred()) {
+        _PyXI_FreeNamespace(ns);
+        return NULL;
+    }
+    return ns;
+}
+
+static void _propagate_not_shareable_error(_PyXI_session *);
+
+// All items are expected to be shareable.
+static _PyXI_namespace *
+_PyXI_NamespaceFromDict(PyObject *nsobj, _PyXI_session *session)
+{
+    // session must be entered already, if provided.
+    assert(session == NULL || session->init_tstate != NULL);
+    if (nsobj == NULL || nsobj == Py_None) {
+        return NULL;
+    }
+    if (!PyDict_CheckExact(nsobj)) {
+        PyErr_SetString(PyExc_TypeError, "expected a dict");
+        return NULL;
+    }
+
+    Py_ssize_t len = PyDict_Size(nsobj);
+    if (len == 0) {
+        return NULL;
+    }
+
+    _PyXI_namespace *ns = _sharedns_new(len);
+    if (ns == NULL) {
+        return NULL;
+    }
+    ns->interp = PyInterpreterState_Get();
+    int64_t interpid = ns->interp->id;
+
+    Py_ssize_t pos = 0;
+    for (Py_ssize_t i=0; i < len; i++) {
+        PyObject *key, *value;
+        if (!PyDict_Next(nsobj, &pos, &key, &value)) {
+            goto error;
+        }
+        _PyXI_namespace_item *item = &ns->items[i];
+        if (_sharednsitem_init(item, interpid, key) != 0) {
+            goto error;
+        }
+        if (_sharednsitem_set_value(item, value) < 0) {
+            _sharednsitem_clear(item);
+            _propagate_not_shareable_error(session);
+            goto error;
+        }
+    }
+    return ns;
+
+error:
+    assert(PyErr_Occurred()
+           || (session != NULL && session->exc_override != NULL));
+    _PyXI_FreeNamespace(ns);
+    return NULL;
+}
+
+int
+_PyXI_FillNamespaceFromDict(_PyXI_namespace *ns, PyObject *nsobj,
+                            _PyXI_session *session)
+{
+    // session must be entered already, if provided.
+    assert(session == NULL || session->init_tstate != NULL);
+    for (Py_ssize_t i=0; i < ns->len; i++) {
+        _PyXI_namespace_item *item = &ns->items[i];
+        if (_sharednsitem_copy_from_ns(item, nsobj) < 0) {
+            _propagate_not_shareable_error(session);
+            // Clear out the ones we set so far.
+            for (Py_ssize_t j=0; j < i; j++) {
+                _sharednsitem_clear_data(&ns->items[j]);
+            }
+            return -1;
+        }
+    }
+    return 0;
+}
+
+int
+_PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj, PyObject *dflt)
+{
+    for (Py_ssize_t i=0; i < ns->len; i++) {
+        if (_sharednsitem_apply(&ns->items[i], nsobj, dflt) != 0) {
+            return -1;
+        }
+    }
+    return 0;
+}
+
+
+/**********************/
+/* high-level helpers */
+/**********************/
+
+/* enter/exit a cross-interpreter session */
+
+static void
+_enter_session(_PyXI_session *session, PyInterpreterState *interp)
+{
+    // Set here and cleared in _exit_session().
+    assert(!session->own_init_tstate);
+    assert(session->init_tstate == NULL);
+    assert(session->prev_tstate == NULL);
+    // 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->exc_override == NULL);
+    // Set elsewhere and cleared in _PyXI_ApplyCapturedException().
+    assert(session->exc == NULL);
+
+    // Switch to interpreter.
+    PyThreadState *tstate = PyThreadState_Get();
+    PyThreadState *prev = tstate;
+    if (interp != tstate->interp) {
+        tstate = PyThreadState_New(interp);
+        tstate->_whence = _PyThreadState_WHENCE_EXEC;
+        // XXX Possible GILState issues?
+        session->prev_tstate = PyThreadState_Swap(tstate);
+        assert(session->prev_tstate == prev);
+        session->own_init_tstate = 1;
+    }
+    session->init_tstate = tstate;
+    session->prev_tstate = prev;
+}
+
+static void
+_exit_session(_PyXI_session *session)
+{
+    PyThreadState *tstate = session->init_tstate;
+    assert(tstate != NULL);
+    assert(PyThreadState_Get() == tstate);
+
+    // Release any of the entered interpreters resources.
+    if (session->main_ns != NULL) {
+        Py_CLEAR(session->main_ns);
+    }
+
+    // Ensure this thread no longer owns __main__.
+    if (session->running) {
+        _PyInterpreterState_SetNotRunningMain(tstate->interp);
+        assert(!PyErr_Occurred());
+        session->running = 0;
+    }
+
+    // Switch back.
+    assert(session->prev_tstate != NULL);
+    if (session->prev_tstate != session->init_tstate) {
+        assert(session->own_init_tstate);
+        session->own_init_tstate = 0;
+        PyThreadState_Clear(tstate);
+        PyThreadState_Swap(session->prev_tstate);
+        PyThreadState_Delete(tstate);
+    }
+    else {
+        assert(!session->own_init_tstate);
+    }
+    session->prev_tstate = NULL;
+    session->init_tstate = NULL;
+}
+
+static void
+_propagate_not_shareable_error(_PyXI_session *session)
+{
+    if (session == NULL) {
+        return;
+    }
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    if (PyErr_ExceptionMatches(_get_not_shareable_error_type(interp))) {
+        // We want to propagate the exception directly.
+        session->_exc_override = _PyXI_ERR_NOT_SHAREABLE;
+        session->exc_override = &session->_exc_override;
+    }
+}
+
+static void
+_capture_current_exception(_PyXI_session *session)
+{
+    assert(session->exc == NULL);
+    if (!PyErr_Occurred()) {
+        assert(session->exc_override == NULL);
+        return;
+    }
+
+    // Handle the exception override.
+    _PyXI_errcode errcode = session->exc_override != NULL
+        ? *session->exc_override
+        : _PyXI_ERR_UNCAUGHT_EXCEPTION;
+    session->exc_override = NULL;
+
+    // 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();
+    }
+
+    // Capture the exception.
+    _PyXI_exception_info *exc = &session->_exc;
+    *exc = (_PyXI_exception_info){
+        .interp = session->init_tstate->interp,
+    };
+    const char *failure;
+    if (excval == NULL) {
+        failure = _PyXI_InitExceptionInfo(exc, NULL, errcode);
+    }
+    else {
+        failure = _PyXI_InitExceptionInfo(exc, excval,
+                                          _PyXI_ERR_UNCAUGHT_EXCEPTION);
+        if (failure == NULL && session->exc_override != NULL) {
+            exc->code = errcode;
+        }
+    }
+
+    // Handle capture failure.
+    if (failure != NULL) {
+        // XXX Make this error message more generic.
+        fprintf(stderr,
+                "RunFailedError: script raised an uncaught exception (%s)",
+                failure);
+        exc = NULL;
+    }
+
+    // a temporary hack  (famous last words)
+    if (excval != NULL) {
+        // XXX Store the traceback info (or rendered traceback) on
+        // _PyXI_excinfo, attach it to the exception when applied,
+        // and teach PyErr_Display() to print it.
+#ifdef Py_DEBUG
+        // XXX Drop this once _Py_excinfo picks up the slack.
+        PyErr_Display(NULL, excval, NULL);
+#endif
+        Py_DECREF(excval);
+    }
+
+    // Finished!
+    assert(!PyErr_Occurred());
+    session->exc = exc;
+}
+
+void
+_PyXI_ApplyCapturedException(_PyXI_session *session, PyObject *excwrapper)
+{
+    assert(!PyErr_Occurred());
+    assert(session->exc != NULL);
+    _PyXI_ApplyExceptionInfo(session->exc, excwrapper);
+    assert(PyErr_Occurred());
+    session->exc = NULL;
+}
+
+int
+_PyXI_HasCapturedException(_PyXI_session *session)
+{
+    return session->exc != NULL;
+}
+
+int
+_PyXI_Enter(_PyXI_session *session,
+            PyInterpreterState *interp, PyObject *nsupdates)
+{
+    // Convert the attrs for cross-interpreter use.
+    _PyXI_namespace *sharedns = NULL;
+    if (nsupdates != NULL) {
+        sharedns = _PyXI_NamespaceFromDict(nsupdates, NULL);
+        if (sharedns == NULL && PyErr_Occurred()) {
+            assert(session->exc == NULL);
+            return -1;
+        }
+    }
+
+    // Switch to the requested interpreter (if necessary).
+    _enter_session(session, interp);
+    _PyXI_errcode errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
+
+    // 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;
+        goto error;
+    }
+    session->running = 1;
+
+    // Cache __main__.__dict__.
+    PyObject *main_mod = PyUnstable_InterpreterState_GetMainModule(interp);
+    if (main_mod == NULL) {
+        errcode = _PyXI_ERR_MAIN_NS_FAILURE;
+        goto error;
+    }
+    PyObject *ns = PyModule_GetDict(main_mod);  // borrowed
+    Py_DECREF(main_mod);
+    if (ns == NULL) {
+        errcode = _PyXI_ERR_MAIN_NS_FAILURE;
+        goto error;
+    }
+    session->main_ns = Py_NewRef(ns);
+
+    // Apply the cross-interpreter data.
+    if (sharedns != NULL) {
+        if (_PyXI_ApplyNamespace(sharedns, ns, NULL) < 0) {
+            errcode = _PyXI_ERR_APPLY_NS_FAILURE;
+            goto error;
+        }
+        _PyXI_FreeNamespace(sharedns);
+    }
+
+    errcode = _PyXI_ERR_NO_ERROR;
+    assert(!PyErr_Occurred());
+    return 0;
+
+error:
+    assert(PyErr_Occurred());
+    // We want to propagate all exceptions here directly (best effort).
+    assert(errcode != _PyXI_ERR_UNCAUGHT_EXCEPTION);
+    session->exc_override = &errcode;
+    _capture_current_exception(session);
+    _exit_session(session);
+    if (sharedns != NULL) {
+        _PyXI_FreeNamespace(sharedns);
+    }
+    return -1;
+}
+
+void
+_PyXI_Exit(_PyXI_session *session)
+{
+    _capture_current_exception(session);
+    _exit_session(session);
+}
+
+
+/*********************/
+/* runtime lifecycle */
+/*********************/
+
+PyStatus
+_PyXI_Init(PyInterpreterState *interp)
+{
+    PyStatus status;
+
+    // Initialize the XID registry.
+    if (_Py_IsMainInterpreter(interp)) {
+        _xidregistry_init(_get_global_xidregistry(interp->runtime));
+    }
+    _xidregistry_init(_get_xidregistry(interp));
+
+    // Initialize exceptions (heap types).
+    status = _init_not_shareable_error_type(interp);
+    if (_PyStatus_EXCEPTION(status)) {
+        return status;
+    }
+
+    return _PyStatus_OK();
+}
+
+// _PyXI_Fini() must be called before the interpreter is cleared,
+// since we must clear some heap objects.
+
+void
+_PyXI_Fini(PyInterpreterState *interp)
+{
+    // Finalize exceptions (heap types).
+    _fini_not_shareable_error_type(interp);
+
+    // Finalize the XID registry.
+    _xidregistry_fini(_get_xidregistry(interp));
+    if (_Py_IsMainInterpreter(interp)) {
+        _xidregistry_fini(_get_global_xidregistry(interp->runtime));
+    }
+}
index f75c3e1fbd3f6e6c126cfe85e48c9ea5cc7e12ca..30be7faea55a6ecfae9088079f5eab546ac3be01 100644 (file)
@@ -1945,3 +1945,178 @@ PyErr_ProgramTextObject(PyObject *filename, int lineno)
 {
     return _PyErr_ProgramDecodedTextObject(filename, lineno, NULL);
 }
+
+
+/***********************/
+/* exception snapshots */
+/***********************/
+
+static const char *
+_copy_raw_string(const char *str)
+{
+    char *copied = PyMem_RawMalloc(strlen(str)+1);
+    if (copied == NULL) {
+        return NULL;
+    }
+    strcpy(copied, str);
+    return copied;
+}
+
+static int
+_exc_type_name_as_utf8(PyObject *exc, const char **p_typename)
+{
+    // XXX Use PyObject_GetAttrString(Py_TYPE(exc), '__name__')?
+    PyObject *nameobj = PyUnicode_FromString(Py_TYPE(exc)->tp_name);
+    if (nameobj == NULL) {
+        assert(PyErr_Occurred());
+        *p_typename = "unable to format exception type name";
+        return -1;
+    }
+    const char *name = PyUnicode_AsUTF8(nameobj);
+    if (name == NULL) {
+        assert(PyErr_Occurred());
+        Py_DECREF(nameobj);
+        *p_typename = "unable to encode exception type name";
+        return -1;
+    }
+    name = _copy_raw_string(name);
+    Py_DECREF(nameobj);
+    if (name == NULL) {
+        *p_typename = "out of memory copying exception type name";
+        return -1;
+    }
+    *p_typename = name;
+    return 0;
+}
+
+static int
+_exc_msg_as_utf8(PyObject *exc, const char **p_msg)
+{
+    PyObject *msgobj = PyObject_Str(exc);
+    if (msgobj == NULL) {
+        assert(PyErr_Occurred());
+        *p_msg = "unable to format exception message";
+        return -1;
+    }
+    const char *msg = PyUnicode_AsUTF8(msgobj);
+    if (msg == NULL) {
+        assert(PyErr_Occurred());
+        Py_DECREF(msgobj);
+        *p_msg = "unable to encode exception message";
+        return -1;
+    }
+    msg = _copy_raw_string(msg);
+    Py_DECREF(msgobj);
+    if (msg == NULL) {
+        assert(PyErr_ExceptionMatches(PyExc_MemoryError));
+        *p_msg = "out of memory copying exception message";
+        return -1;
+    }
+    *p_msg = msg;
+    return 0;
+}
+
+void
+_Py_excinfo_Clear(_Py_excinfo *info)
+{
+    if (info->type != NULL) {
+        PyMem_RawFree((void *)info->type);
+    }
+    if (info->msg != NULL) {
+        PyMem_RawFree((void *)info->msg);
+    }
+    *info = (_Py_excinfo){ NULL };
+}
+
+int
+_Py_excinfo_Copy(_Py_excinfo *dest, _Py_excinfo *src)
+{
+    // XXX Clear dest first?
+
+    if (src->type == NULL) {
+        dest->type = NULL;
+    }
+    else {
+        dest->type = _copy_raw_string(src->type);
+        if (dest->type == NULL) {
+            return -1;
+        }
+    }
+
+    if (src->msg == NULL) {
+        dest->msg = NULL;
+    }
+    else {
+        dest->msg = _copy_raw_string(src->msg);
+        if (dest->msg == NULL) {
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+const char *
+_Py_excinfo_InitFromException(_Py_excinfo *info, PyObject *exc)
+{
+    assert(exc != NULL);
+
+    // Extract the exception type name.
+    const char *typename = NULL;
+    if (_exc_type_name_as_utf8(exc, &typename) < 0) {
+        assert(typename != NULL);
+        return typename;
+    }
+
+    // Extract the exception message.
+    const char *msg = NULL;
+    if (_exc_msg_as_utf8(exc, &msg) < 0) {
+        assert(msg != NULL);
+        return msg;
+    }
+
+    info->type = typename;
+    info->msg = msg;
+    return NULL;
+}
+
+void
+_Py_excinfo_Apply(_Py_excinfo *info, PyObject *exctype)
+{
+    if (info->type != NULL) {
+        if (info->msg != NULL) {
+            PyErr_Format(exctype, "%s: %s",  info->type, info->msg);
+        }
+        else {
+            PyErr_SetString(exctype, info->type);
+        }
+    }
+    else if (info->msg != NULL) {
+        PyErr_SetString(exctype, info->msg);
+    }
+    else {
+        PyErr_SetNone(exctype);
+    }
+}
+
+const char *
+_Py_excinfo_AsUTF8(_Py_excinfo *info, char *buf, size_t bufsize)
+{
+    // XXX Dynamically allocate if no buf provided?
+    assert(buf != NULL);
+    if (info->type != NULL) {
+        if (info->msg != NULL) {
+            snprintf(buf, bufsize, "%s: %s",  info->type, info->msg);
+            return buf;
+        }
+        else {
+            return info->type;
+        }
+    }
+    else if (info->msg != NULL) {
+        return info->msg;
+    }
+    else {
+        return NULL;
+    }
+}
index 3c57056fb81e8180448038cdc40c335f86187def..ea84ca0b9c3c2a0ff2f44de545afb529a50f556f 100644 (file)
@@ -738,6 +738,7 @@ pycore_init_types(PyInterpreterState *interp)
     if (_PyStatus_EXCEPTION(status)) {
         return status;
     }
+
     return _PyStatus_OK();
 }
 
@@ -854,6 +855,11 @@ pycore_interp_init(PyThreadState *tstate)
         goto done;
     }
 
+    status = _PyXI_Init(interp);
+    if (_PyStatus_EXCEPTION(status)) {
+        goto done;
+    }
+
     const PyConfig *config = _PyInterpreterState_GetConfig(interp);
 
     status = _PyImport_InitCore(tstate, sysmod, config->_install_importlib);
@@ -1772,6 +1778,7 @@ finalize_interp_clear(PyThreadState *tstate)
 {
     int is_main_interp = _Py_IsMainInterpreter(tstate->interp);
 
+    _PyXI_Fini(tstate->interp);
     _PyExc_ClearExceptionGroupType(tstate->interp);
     _Py_clear_generic_types(tstate->interp);
 
index d97a03caf491c4c3d1b14e6c6c60c9f8a054b2e0..8970e17a3c101bfb688ae0744ec9bf1f5a1c2026 100644 (file)
@@ -382,7 +382,7 @@ _Py_COMP_DIAG_POP
 #define LOCKS_INIT(runtime) \
     { \
         &(runtime)->interpreters.mutex, \
-        &(runtime)->xidregistry.mutex, \
+        &(runtime)->xi.registry.mutex, \
         &(runtime)->getargs.mutex, \
         &(runtime)->unicode_state.ids.lock, \
         &(runtime)->imports.extensions.mutex, \
@@ -494,9 +494,6 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
     return _PyStatus_OK();
 }
 
-// This is defined in crossinterp.c (for now).
-extern void _Py_xidregistry_clear(struct _xidregistry *);
-
 void
 _PyRuntimeState_Fini(_PyRuntimeState *runtime)
 {
@@ -505,8 +502,6 @@ _PyRuntimeState_Fini(_PyRuntimeState *runtime)
     assert(runtime->object_state.interpreter_leaks == 0);
 #endif
 
-    _Py_xidregistry_clear(&runtime->xidregistry);
-
     if (gilstate_tss_initialized(runtime)) {
         gilstate_tss_fini(runtime);
     }
@@ -552,11 +547,6 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime)
     for (int i = 0; i < NUMLOCKS; i++) {
         reinit_err += _PyThread_at_fork_reinit(lockptrs[i]);
     }
-    /* PyOS_AfterFork_Child(), which calls this function, later calls
-       _PyInterpreterState_DeleteExceptMain(), so we only need to update
-       the main interpreter here. */
-    assert(runtime->interpreters.main != NULL);
-    runtime->interpreters.main->xidregistry.mutex = runtime->xidregistry.mutex;
 
     PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
 
@@ -720,9 +710,6 @@ init_interpreter(PyInterpreterState *interp,
     }
     interp->f_opcode_trace_set = false;
 
-    assert(runtime->xidregistry.mutex != NULL);
-    interp->xidregistry.mutex = runtime->xidregistry.mutex;
-
     interp->_initialized = 1;
     return _PyStatus_OK();
 }
@@ -948,10 +935,6 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
     Py_CLEAR(interp->sysdict);
     Py_CLEAR(interp->builtins);
 
-    _Py_xidregistry_clear(&interp->xidregistry);
-    /* The lock is owned by the runtime, so we don't free it here. */
-    interp->xidregistry.mutex = NULL;
-
     if (tstate->interp == interp) {
         /* We are now safe to fix tstate->_status.cleared. */
         // XXX Do this (much) earlier?