mirror of
https://github.com/python/cpython.git
synced 2024-11-24 18:34:43 +08:00
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:
parent
cde1071b2a
commit
9322ce90ac
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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, \
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
Py_DECREF(result); // We throw away the result.
|
||||
}
|
||||
_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);
|
||||
}
|
||||
else {
|
||||
assert(!_PyXI_HasCapturedException(&session));
|
||||
}
|
||||
|
||||
if (shared != NULL) {
|
||||
_sharedns_free(shared);
|
||||
}
|
||||
|
||||
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;
|
||||
|
File diff suppressed because it is too large
Load Diff
175
Python/errors.c
175
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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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?
|
||||
|
Loading…
Reference in New Issue
Block a user