summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Snow <ericsnowcurrently@gmail.com>2023-11-01 23:36:40 (GMT)
committerGitHub <noreply@github.com>2023-11-01 23:36:40 (GMT)
commit9322ce90ac8f4d4647a59bbfab48fad6f4e4e856 (patch)
treec3dd4e202f02a639bca0412b71d1464fd4f624c5
parentcde1071b2a72e8261ca66053ef61431b7f3a81fd (diff)
downloadcpython-9322ce90ac8f4d4647a59bbfab48fad6f4e4e856.zip
cpython-9322ce90ac8f4d4647a59bbfab48fad6f4e4e856.tar.gz
cpython-9322ce90ac8f4d4647a59bbfab48fad6f4e4e856.tar.bz2
gh-76785: Crossinterp utils additions (gh-111530)
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.
-rw-r--r--Include/internal/pycore_crossinterp.h128
-rw-r--r--Include/internal/pycore_interp.h4
-rw-r--r--Include/internal/pycore_pyerrors.h24
-rw-r--r--Include/internal/pycore_runtime.h4
-rw-r--r--Include/internal/pycore_runtime_init.h5
-rw-r--r--Lib/test/support/interpreters.py2
-rw-r--r--Modules/_xxsubinterpretersmodule.c419
-rw-r--r--Python/crossinterp.c992
-rw-r--r--Python/errors.c175
-rw-r--r--Python/pylifecycle.c7
-rw-r--r--Python/pystate.c19
11 files changed, 1308 insertions, 471 deletions
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 59e4cd9..9600dfb 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -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
diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h
index a067a60..78b841a 100644
--- a/Include/internal/pycore_interp.h
+++ b/Include/internal/pycore_interp.h
@@ -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;
diff --git a/Include/internal/pycore_pyerrors.h b/Include/internal/pycore_pyerrors.h
index 184eb35..67ef71c 100644
--- a/Include/internal/pycore_pyerrors.h
+++ b/Include/internal/pycore_pyerrors.h
@@ -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)
diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h
index 320e5bb..8fb73dd 100644
--- a/Include/internal/pycore_runtime.h
+++ b/Include/internal/pycore_runtime.h
@@ -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;
diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h
index 0799b7e..fa5d811 100644
--- a/Include/internal/pycore_runtime_init.h
+++ b/Include/internal/pycore_runtime_init.h
@@ -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, \
diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py
index 182f47b..ab9342b 100644
--- a/Lib/test/support/interpreters.py
+++ b/Lib/test/support/interpreters.py
@@ -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"
diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c
index ce0d511..001fa88 100644
--- a/Modules/_xxsubinterpretersmodule.c
+++ b/Modules/_xxsubinterpretersmodule.c
@@ -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()
@@ -19,22 +20,6 @@
#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;
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index 17c476b..00eccbd 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -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));
+ }
+}
diff --git a/Python/errors.c b/Python/errors.c
index f75c3e1..30be7fa 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -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;
+ }
+}
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index 3c57056..ea84ca0 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -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);
diff --git a/Python/pystate.c b/Python/pystate.c
index d97a03c..8970e17 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -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?