summaryrefslogtreecommitdiffstats
path: root/Include
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 /Include
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.
Diffstat (limited to 'Include')
-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
5 files changed, 161 insertions, 4 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, \