Skip to content

Commit 42b1615

Browse files
ericsnowcurrentlyaisk
authored andcommitted
pythongh-76785: Crossinterp utils additions (pythongh-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.
1 parent 6c7245d commit 42b1615

File tree

11 files changed

+1308
-471
lines changed

11 files changed

+1308
-471
lines changed

Include/internal/pycore_crossinterp.h

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11+
#include "pycore_pyerrors.h"
12+
1113

1214
/***************************/
1315
/* cross-interpreter calls */
@@ -124,6 +126,8 @@ struct _xidregitem {
124126
};
125127

126128
struct _xidregistry {
129+
int global; /* builtin types or heap types */
130+
int initialized;
127131
PyThread_type_lock mutex;
128132
struct _xidregitem *head;
129133
};
@@ -133,6 +137,130 @@ PyAPI_FUNC(int) _PyCrossInterpreterData_UnregisterClass(PyTypeObject *);
133137
PyAPI_FUNC(crossinterpdatafunc) _PyCrossInterpreterData_Lookup(PyObject *);
134138

135139

140+
/*****************************/
141+
/* runtime state & lifecycle */
142+
/*****************************/
143+
144+
struct _xi_runtime_state {
145+
// builtin types
146+
// XXX Remove this field once we have a tp_* slot.
147+
struct _xidregistry registry;
148+
};
149+
150+
struct _xi_state {
151+
// heap types
152+
// XXX Remove this field once we have a tp_* slot.
153+
struct _xidregistry registry;
154+
155+
// heap types
156+
PyObject *PyExc_NotShareableError;
157+
};
158+
159+
extern PyStatus _PyXI_Init(PyInterpreterState *interp);
160+
extern void _PyXI_Fini(PyInterpreterState *interp);
161+
162+
163+
/***************************/
164+
/* short-term data sharing */
165+
/***************************/
166+
167+
typedef enum error_code {
168+
_PyXI_ERR_NO_ERROR = 0,
169+
_PyXI_ERR_UNCAUGHT_EXCEPTION = -1,
170+
_PyXI_ERR_OTHER = -2,
171+
_PyXI_ERR_NO_MEMORY = -3,
172+
_PyXI_ERR_ALREADY_RUNNING = -4,
173+
_PyXI_ERR_MAIN_NS_FAILURE = -5,
174+
_PyXI_ERR_APPLY_NS_FAILURE = -6,
175+
_PyXI_ERR_NOT_SHAREABLE = -7,
176+
} _PyXI_errcode;
177+
178+
179+
typedef struct _sharedexception {
180+
// The originating interpreter.
181+
PyInterpreterState *interp;
182+
// The kind of error to propagate.
183+
_PyXI_errcode code;
184+
// The exception information to propagate, if applicable.
185+
// This is populated only for _PyXI_ERR_UNCAUGHT_EXCEPTION.
186+
_Py_excinfo uncaught;
187+
} _PyXI_exception_info;
188+
189+
PyAPI_FUNC(void) _PyXI_ApplyExceptionInfo(
190+
_PyXI_exception_info *info,
191+
PyObject *exctype);
192+
193+
typedef struct xi_session _PyXI_session;
194+
typedef struct _sharedns _PyXI_namespace;
195+
196+
PyAPI_FUNC(void) _PyXI_FreeNamespace(_PyXI_namespace *ns);
197+
PyAPI_FUNC(_PyXI_namespace *) _PyXI_NamespaceFromNames(PyObject *names);
198+
PyAPI_FUNC(int) _PyXI_FillNamespaceFromDict(
199+
_PyXI_namespace *ns,
200+
PyObject *nsobj,
201+
_PyXI_session *session);
202+
PyAPI_FUNC(int) _PyXI_ApplyNamespace(
203+
_PyXI_namespace *ns,
204+
PyObject *nsobj,
205+
PyObject *dflt);
206+
207+
208+
// A cross-interpreter session involves entering an interpreter
209+
// (_PyXI_Enter()), doing some work with it, and finally exiting
210+
// that interpreter (_PyXI_Exit()).
211+
//
212+
// At the boundaries of the session, both entering and exiting,
213+
// data may be exchanged between the previous interpreter and the
214+
// target one in a thread-safe way that does not violate the
215+
// isolation between interpreters. This includes setting objects
216+
// in the target's __main__ module on the way in, and capturing
217+
// uncaught exceptions on the way out.
218+
struct xi_session {
219+
// Once a session has been entered, this is the tstate that was
220+
// current before the session. If it is different from cur_tstate
221+
// then we must have switched interpreters. Either way, this will
222+
// be the current tstate once we exit the session.
223+
PyThreadState *prev_tstate;
224+
// Once a session has been entered, this is the current tstate.
225+
// It must be current when the session exits.
226+
PyThreadState *init_tstate;
227+
// This is true if init_tstate needs cleanup during exit.
228+
int own_init_tstate;
229+
230+
// This is true if, while entering the session, init_thread took
231+
// "ownership" of the interpreter's __main__ module. This means
232+
// it is the only thread that is allowed to run code there.
233+
// (Caveat: for now, users may still run exec() against the
234+
// __main__ module's dict, though that isn't advisable.)
235+
int running;
236+
// This is a cached reference to the __dict__ of the entered
237+
// interpreter's __main__ module. It is looked up when at the
238+
// beginning of the session as a convenience.
239+
PyObject *main_ns;
240+
241+
// This is set if the interpreter is entered and raised an exception
242+
// that needs to be handled in some special way during exit.
243+
_PyXI_errcode *exc_override;
244+
// This is set if exit captured an exception to propagate.
245+
_PyXI_exception_info *exc;
246+
247+
// -- pre-allocated memory --
248+
_PyXI_exception_info _exc;
249+
_PyXI_errcode _exc_override;
250+
};
251+
252+
PyAPI_FUNC(int) _PyXI_Enter(
253+
_PyXI_session *session,
254+
PyInterpreterState *interp,
255+
PyObject *nsupdates);
256+
PyAPI_FUNC(void) _PyXI_Exit(_PyXI_session *session);
257+
258+
PyAPI_FUNC(void) _PyXI_ApplyCapturedException(
259+
_PyXI_session *session,
260+
PyObject *excwrapper);
261+
PyAPI_FUNC(int) _PyXI_HasCapturedException(_PyXI_session *session);
262+
263+
136264
#ifdef __cplusplus
137265
}
138266
#endif

Include/internal/pycore_interp.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ struct _is {
153153
Py_ssize_t co_extra_user_count;
154154
freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS];
155155

156-
// XXX Remove this field once we have a tp_* slot.
157-
struct _xidregistry xidregistry;
156+
/* cross-interpreter data and utils */
157+
struct _xi_state xi;
158158

159159
#ifdef HAVE_FORK
160160
PyObject *before_forkers;

Include/internal/pycore_pyerrors.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,30 @@ extern PyStatus _PyErr_InitTypes(PyInterpreterState *);
6868
extern void _PyErr_FiniTypes(PyInterpreterState *);
6969

7070

71+
/* exception snapshots */
72+
73+
// Ultimately we'd like to preserve enough information about the
74+
// exception and traceback that we could re-constitute (or at least
75+
// simulate, a la traceback.TracebackException), and even chain, a copy
76+
// of the exception in the calling interpreter.
77+
78+
typedef struct _excinfo {
79+
const char *type;
80+
const char *msg;
81+
} _Py_excinfo;
82+
83+
extern void _Py_excinfo_Clear(_Py_excinfo *info);
84+
extern int _Py_excinfo_Copy(_Py_excinfo *dest, _Py_excinfo *src);
85+
extern const char * _Py_excinfo_InitFromException(
86+
_Py_excinfo *info,
87+
PyObject *exc);
88+
extern void _Py_excinfo_Apply(_Py_excinfo *info, PyObject *exctype);
89+
extern const char * _Py_excinfo_AsUTF8(
90+
_Py_excinfo *info,
91+
char *buf,
92+
size_t bufsize);
93+
94+
7195
/* other API */
7296

7397
static inline PyObject* _PyErr_Occurred(PyThreadState *tstate)

Include/internal/pycore_runtime.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,8 @@ typedef struct pyruntimestate {
200200
possible to facilitate out-of-process observability
201201
tools. */
202202

203-
// XXX Remove this field once we have a tp_* slot.
204-
struct _xidregistry xidregistry;
203+
/* cross-interpreter data and utils */
204+
struct _xi_runtime_state xi;
205205

206206
struct _pymem_allocators allocators;
207207
struct _obmalloc_global_state obmalloc;

Include/internal/pycore_runtime_init.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ extern PyTypeObject _PyExc_MemoryError;
9595
until _PyInterpreterState_Enable() is called. */ \
9696
.next_id = -1, \
9797
}, \
98+
.xi = { \
99+
.registry = { \
100+
.global = 1, \
101+
}, \
102+
}, \
98103
/* A TSS key must be initialized with Py_tss_NEEDS_INIT \
99104
in accordance with the specification. */ \
100105
.autoTSSkey = Py_tss_NEEDS_INIT, \

Lib/test/support/interpreters.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def close(self):
9292
return _interpreters.destroy(self._id)
9393

9494
# XXX Rename "run" to "exec"?
95-
def run(self, src_str, /, *, channels=None):
95+
def run(self, src_str, /, channels=None):
9696
"""Run the given source code in the interpreter.
9797
9898
This is essentially the same as calling the builtin "exec"

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy