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.
This commit is contained in:
Eric Snow 2023-11-01 17:36:40 -06:00 committed by GitHub
parent cde1071b2a
commit 9322ce90ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1309 additions and 472 deletions

View file

@ -8,6 +8,8 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define" # error "this header requires Py_BUILD_CORE define"
#endif #endif
#include "pycore_pyerrors.h"
/***************************/ /***************************/
/* cross-interpreter calls */ /* cross-interpreter calls */
@ -124,6 +126,8 @@ struct _xidregitem {
}; };
struct _xidregistry { struct _xidregistry {
int global; /* builtin types or heap types */
int initialized;
PyThread_type_lock mutex; PyThread_type_lock mutex;
struct _xidregitem *head; struct _xidregitem *head;
}; };
@ -133,6 +137,130 @@ PyAPI_FUNC(int) _PyCrossInterpreterData_UnregisterClass(PyTypeObject *);
PyAPI_FUNC(crossinterpdatafunc) _PyCrossInterpreterData_Lookup(PyObject *); 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 #ifdef __cplusplus
} }
#endif #endif

View file

@ -153,8 +153,8 @@ struct _is {
Py_ssize_t co_extra_user_count; Py_ssize_t co_extra_user_count;
freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS]; freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS];
// XXX Remove this field once we have a tp_* slot. /* cross-interpreter data and utils */
struct _xidregistry xidregistry; struct _xi_state xi;
#ifdef HAVE_FORK #ifdef HAVE_FORK
PyObject *before_forkers; PyObject *before_forkers;

View file

@ -68,6 +68,30 @@ extern PyStatus _PyErr_InitTypes(PyInterpreterState *);
extern void _PyErr_FiniTypes(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 */ /* other API */
static inline PyObject* _PyErr_Occurred(PyThreadState *tstate) static inline PyObject* _PyErr_Occurred(PyThreadState *tstate)

View file

@ -200,8 +200,8 @@ typedef struct pyruntimestate {
possible to facilitate out-of-process observability possible to facilitate out-of-process observability
tools. */ tools. */
// XXX Remove this field once we have a tp_* slot. /* cross-interpreter data and utils */
struct _xidregistry xidregistry; struct _xi_runtime_state xi;
struct _pymem_allocators allocators; struct _pymem_allocators allocators;
struct _obmalloc_global_state obmalloc; struct _obmalloc_global_state obmalloc;

View file

@ -95,6 +95,11 @@ extern PyTypeObject _PyExc_MemoryError;
until _PyInterpreterState_Enable() is called. */ \ until _PyInterpreterState_Enable() is called. */ \
.next_id = -1, \ .next_id = -1, \
}, \ }, \
.xi = { \
.registry = { \
.global = 1, \
}, \
}, \
/* A TSS key must be initialized with Py_tss_NEEDS_INIT \ /* A TSS key must be initialized with Py_tss_NEEDS_INIT \
in accordance with the specification. */ \ in accordance with the specification. */ \
.autoTSSkey = Py_tss_NEEDS_INIT, \ .autoTSSkey = Py_tss_NEEDS_INIT, \

View file

@ -92,7 +92,7 @@ class Interpreter:
return _interpreters.destroy(self._id) return _interpreters.destroy(self._id)
# XXX Rename "run" to "exec"? # 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. """Run the given source code in the interpreter.
This is essentially the same as calling the builtin "exec" This is essentially the same as calling the builtin "exec"

View file

@ -7,6 +7,7 @@
#include "Python.h" #include "Python.h"
#include "pycore_crossinterp.h" // struct _xid #include "pycore_crossinterp.h" // struct _xid
#include "pycore_pyerrors.h" // _Py_excinfo
#include "pycore_initconfig.h" // _PyErr_SetFromPyStatus() #include "pycore_initconfig.h" // _PyErr_SetFromPyStatus()
#include "pycore_modsupport.h" // _PyArg_BadArgument() #include "pycore_modsupport.h" // _PyArg_BadArgument()
#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() #include "pycore_pyerrors.h" // _PyErr_ChainExceptions1()
@ -19,22 +20,6 @@
#define MODULE_NAME "_xxsubinterpreters" #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 * static PyInterpreterState *
_get_current_interp(void) _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) \ #define ADD_NEW_EXCEPTION(MOD, NAME, BASE) \
add_new_exception(MOD, MODULE_NAME "." Py_STRINGIFY(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 *************************************************************/ /* 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 **************************************************************/ /* Python code **************************************************************/
static const char * static const char *
@ -489,43 +202,8 @@ exceptions_init(PyObject *mod)
} }
static int static int
_run_script(PyInterpreterState *interp, _run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags)
const char *codestr, Py_ssize_t codestrlen,
_sharedns *shared, _sharedexception *sharedexc, 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; PyObject *result = NULL;
if (flags & RUN_TEXT) { if (flags & RUN_TEXT) {
result = PyRun_StringFlags(codestr, Py_file_input, ns, ns, NULL); result = PyRun_StringFlags(codestr, Py_file_input, ns, ns, NULL);
@ -540,86 +218,46 @@ _run_script(PyInterpreterState *interp,
else { else {
Py_UNREACHABLE(); Py_UNREACHABLE();
} }
Py_DECREF(ns);
if (result == NULL) { if (result == NULL) {
goto error;
}
else {
Py_DECREF(result); // We throw away the result.
}
_PyInterpreterState_SetNotRunningMain(interp);
*sharedexc = no_exception;
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; return -1;
} }
Py_DECREF(result); // We throw away the result.
return 0;
}
static int static int
_run_in_interpreter(PyObject *mod, PyInterpreterState *interp, _run_in_interpreter(PyInterpreterState *interp,
const char *codestr, Py_ssize_t codestrlen, 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(!PyErr_Occurred());
assert(state != NULL); _PyXI_session session = {0};
_sharedns *shared = _get_shared_ns(shareables); // Prep and switch interpreters.
if (shared == NULL && PyErr_Occurred()) { if (_PyXI_Enter(&session, interp, shareables) < 0) {
assert(!PyErr_Occurred());
_PyXI_ApplyExceptionInfo(session.exc, excwrapper);
assert(PyErr_Occurred());
return -1; 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. // Run the script.
_sharedexception exc = (_sharedexception){ .interp = interp }; int res = _run_script(session.main_ns, codestr, codestrlen, flags);
int result = _run_script(interp, codestr, codestrlen, shared, &exc, flags);
// Switch back. // Clean up and switch back.
if (save_tstate != NULL) { _PyXI_Exit(&session);
PyThreadState_Clear(tstate);
PyThreadState_Swap(save_tstate);
PyThreadState_Delete(tstate);
}
// Propagate any exception out to the caller. // Propagate any exception out to the caller.
if (result < 0) {
assert(!PyErr_Occurred()); assert(!PyErr_Occurred());
_sharedexception_apply(&exc, state->RunFailedError); if (res < 0) {
assert(PyErr_Occurred()); _PyXI_ApplyCapturedException(&session, excwrapper);
}
else {
assert(!_PyXI_HasCapturedException(&session));
} }
if (shared != NULL) { return res;
_sharedns_free(shared);
}
return result;
} }
@ -805,7 +443,6 @@ PyDoc_STRVAR(get_main_doc,
\n\ \n\
Return the ID of main interpreter."); Return the ID of main interpreter.");
static PyUnicodeObject * static PyUnicodeObject *
convert_script_arg(PyObject *arg, const char *fname, const char *displayname, convert_script_arg(PyObject *arg, const char *fname, const char *displayname,
const char *expected) const char *expected)
@ -903,10 +540,12 @@ _interp_exec(PyObject *self,
} }
// Run the code in the interpreter. // Run the code in the interpreter.
int res = _run_in_interpreter(self, interp, codestr, codestrlen, module_state *state = get_module_state(self);
shared_arg, flags); assert(state != NULL);
int res = _run_in_interpreter(interp, codestr, codestrlen,
shared_arg, flags, state->RunFailedError);
Py_XDECREF(bytes_obj); Py_XDECREF(bytes_obj);
if (res != 0) { if (res < 0) {
return -1; return -1;
} }
@ -981,7 +620,7 @@ interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)
return NULL; return NULL;
} }
int res = _interp_exec(self, id, (PyObject *)script, shared); int res = _interp_exec(self, id, script, shared);
Py_DECREF(script); Py_DECREF(script);
if (res < 0) { if (res < 0) {
return NULL; return NULL;

File diff suppressed because it is too large Load diff

View file

@ -1945,3 +1945,178 @@ PyErr_ProgramTextObject(PyObject *filename, int lineno)
{ {
return _PyErr_ProgramDecodedTextObject(filename, lineno, NULL); 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;
}
}

View file

@ -738,6 +738,7 @@ pycore_init_types(PyInterpreterState *interp)
if (_PyStatus_EXCEPTION(status)) { if (_PyStatus_EXCEPTION(status)) {
return status; return status;
} }
return _PyStatus_OK(); return _PyStatus_OK();
} }
@ -854,6 +855,11 @@ pycore_interp_init(PyThreadState *tstate)
goto done; goto done;
} }
status = _PyXI_Init(interp);
if (_PyStatus_EXCEPTION(status)) {
goto done;
}
const PyConfig *config = _PyInterpreterState_GetConfig(interp); const PyConfig *config = _PyInterpreterState_GetConfig(interp);
status = _PyImport_InitCore(tstate, sysmod, config->_install_importlib); 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); int is_main_interp = _Py_IsMainInterpreter(tstate->interp);
_PyXI_Fini(tstate->interp);
_PyExc_ClearExceptionGroupType(tstate->interp); _PyExc_ClearExceptionGroupType(tstate->interp);
_Py_clear_generic_types(tstate->interp); _Py_clear_generic_types(tstate->interp);

View file

@ -382,7 +382,7 @@ _Py_COMP_DIAG_POP
#define LOCKS_INIT(runtime) \ #define LOCKS_INIT(runtime) \
{ \ { \
&(runtime)->interpreters.mutex, \ &(runtime)->interpreters.mutex, \
&(runtime)->xidregistry.mutex, \ &(runtime)->xi.registry.mutex, \
&(runtime)->getargs.mutex, \ &(runtime)->getargs.mutex, \
&(runtime)->unicode_state.ids.lock, \ &(runtime)->unicode_state.ids.lock, \
&(runtime)->imports.extensions.mutex, \ &(runtime)->imports.extensions.mutex, \
@ -494,9 +494,6 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
return _PyStatus_OK(); return _PyStatus_OK();
} }
// This is defined in crossinterp.c (for now).
extern void _Py_xidregistry_clear(struct _xidregistry *);
void void
_PyRuntimeState_Fini(_PyRuntimeState *runtime) _PyRuntimeState_Fini(_PyRuntimeState *runtime)
{ {
@ -505,8 +502,6 @@ _PyRuntimeState_Fini(_PyRuntimeState *runtime)
assert(runtime->object_state.interpreter_leaks == 0); assert(runtime->object_state.interpreter_leaks == 0);
#endif #endif
_Py_xidregistry_clear(&runtime->xidregistry);
if (gilstate_tss_initialized(runtime)) { if (gilstate_tss_initialized(runtime)) {
gilstate_tss_fini(runtime); gilstate_tss_fini(runtime);
} }
@ -552,11 +547,6 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime)
for (int i = 0; i < NUMLOCKS; i++) { for (int i = 0; i < NUMLOCKS; i++) {
reinit_err += _PyThread_at_fork_reinit(lockptrs[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); PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
@ -720,9 +710,6 @@ init_interpreter(PyInterpreterState *interp,
} }
interp->f_opcode_trace_set = false; interp->f_opcode_trace_set = false;
assert(runtime->xidregistry.mutex != NULL);
interp->xidregistry.mutex = runtime->xidregistry.mutex;
interp->_initialized = 1; interp->_initialized = 1;
return _PyStatus_OK(); return _PyStatus_OK();
} }
@ -948,10 +935,6 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
Py_CLEAR(interp->sysdict); Py_CLEAR(interp->sysdict);
Py_CLEAR(interp->builtins); 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) { if (tstate->interp == interp) {
/* We are now safe to fix tstate->_status.cleared. */ /* We are now safe to fix tstate->_status.cleared. */
// XXX Do this (much) earlier? // XXX Do this (much) earlier?