mirror of https://github.com/python/cpython
1924 lines
50 KiB
C
1924 lines
50 KiB
C
|
|
/* API for managing interactions between isolated interpreters */
|
|
|
|
#include "Python.h"
|
|
#include "pycore_ceval.h" // _Py_simple_func
|
|
#include "pycore_crossinterp.h" // _PyXIData_t
|
|
#include "pycore_initconfig.h" // _PyStatus_OK()
|
|
#include "pycore_namespace.h" //_PyNamespace_New()
|
|
#include "pycore_pyerrors.h" // _PyErr_Clear()
|
|
|
|
|
|
#define _PyXI_GET_GLOBAL_STATE(interp) (&(interp)->runtime->xi)
|
|
#define _PyXI_GET_STATE(interp) (&(interp)->xi)
|
|
|
|
|
|
/**************/
|
|
/* exceptions */
|
|
/**************/
|
|
|
|
static int init_exceptions(PyInterpreterState *);
|
|
static void fini_exceptions(PyInterpreterState *);
|
|
static int _init_not_shareable_error_type(PyInterpreterState *);
|
|
static void _fini_not_shareable_error_type(PyInterpreterState *);
|
|
static PyObject * _get_not_shareable_error_type(PyInterpreterState *);
|
|
#include "crossinterp_exceptions.h"
|
|
|
|
|
|
/***************************/
|
|
/* cross-interpreter calls */
|
|
/***************************/
|
|
|
|
int
|
|
_Py_CallInInterpreter(PyInterpreterState *interp,
|
|
_Py_simple_func func, void *arg)
|
|
{
|
|
if (interp == PyInterpreterState_Get()) {
|
|
return func(arg);
|
|
}
|
|
// XXX Emit a warning if this fails?
|
|
_PyEval_AddPendingCall(interp, (_Py_pending_call_func)func, arg, 0);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
_Py_CallInInterpreterAndRawFree(PyInterpreterState *interp,
|
|
_Py_simple_func func, void *arg)
|
|
{
|
|
if (interp == PyInterpreterState_Get()) {
|
|
int res = func(arg);
|
|
PyMem_RawFree(arg);
|
|
return res;
|
|
}
|
|
// XXX Emit a warning if this fails?
|
|
_PyEval_AddPendingCall(interp, func, arg, _Py_PENDING_RAWFREE);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**************************/
|
|
/* cross-interpreter data */
|
|
/**************************/
|
|
|
|
/* registry of {type -> xidatafunc} */
|
|
|
|
/* For now we use a global registry of shareable classes. An
|
|
alternative would be to add a tp_* slot for a class's
|
|
xidatafunc. It would be simpler and more efficient. */
|
|
|
|
static void xid_lookup_init(_PyXIData_lookup_t *);
|
|
static void xid_lookup_fini(_PyXIData_lookup_t *);
|
|
static xidatafunc lookup_getdata(PyInterpreterState *, PyObject *);
|
|
#include "crossinterp_data_lookup.h"
|
|
|
|
|
|
/* lifecycle */
|
|
|
|
_PyXIData_t *
|
|
_PyXIData_New(void)
|
|
{
|
|
_PyXIData_t *xid = PyMem_RawMalloc(sizeof(_PyXIData_t));
|
|
if (xid == NULL) {
|
|
PyErr_NoMemory();
|
|
}
|
|
return xid;
|
|
}
|
|
|
|
void
|
|
_PyXIData_Free(_PyXIData_t *xid)
|
|
{
|
|
PyInterpreterState *interp = PyInterpreterState_Get();
|
|
_PyXIData_Clear(interp, xid);
|
|
PyMem_RawFree(xid);
|
|
}
|
|
|
|
|
|
/* defining cross-interpreter data */
|
|
|
|
static inline void
|
|
_xidata_init(_PyXIData_t *data)
|
|
{
|
|
// If the value is being reused
|
|
// then _xidata_clear() should have been called already.
|
|
assert(data->data == NULL);
|
|
assert(data->obj == NULL);
|
|
*data = (_PyXIData_t){0};
|
|
_PyXIData_INTERPID(data) = -1;
|
|
}
|
|
|
|
static inline void
|
|
_xidata_clear(_PyXIData_t *data)
|
|
{
|
|
// _PyXIData_t only has two members that need to be
|
|
// cleaned up, if set: "data" must be freed and "obj" must be decref'ed.
|
|
// In both cases the original (owning) interpreter must be used,
|
|
// which is the caller's responsibility to ensure.
|
|
if (data->data != NULL) {
|
|
if (data->free != NULL) {
|
|
data->free(data->data);
|
|
}
|
|
data->data = NULL;
|
|
}
|
|
Py_CLEAR(data->obj);
|
|
}
|
|
|
|
void
|
|
_PyXIData_Init(_PyXIData_t *data,
|
|
PyInterpreterState *interp,
|
|
void *shared, PyObject *obj,
|
|
xid_newobjectfunc new_object)
|
|
{
|
|
assert(data != NULL);
|
|
assert(new_object != NULL);
|
|
_xidata_init(data);
|
|
data->data = shared;
|
|
if (obj != NULL) {
|
|
assert(interp != NULL);
|
|
// released in _PyXIData_Clear()
|
|
data->obj = Py_NewRef(obj);
|
|
}
|
|
// Ideally every object would know its owning interpreter.
|
|
// Until then, we have to rely on the caller to identify it
|
|
// (but we don't need it in all cases).
|
|
_PyXIData_INTERPID(data) = (interp != NULL)
|
|
? PyInterpreterState_GetID(interp)
|
|
: -1;
|
|
data->new_object = new_object;
|
|
}
|
|
|
|
int
|
|
_PyXIData_InitWithSize(_PyXIData_t *data,
|
|
PyInterpreterState *interp,
|
|
const size_t size, PyObject *obj,
|
|
xid_newobjectfunc new_object)
|
|
{
|
|
assert(size > 0);
|
|
// For now we always free the shared data in the same interpreter
|
|
// where it was allocated, so the interpreter is required.
|
|
assert(interp != NULL);
|
|
_PyXIData_Init(data, interp, NULL, obj, new_object);
|
|
data->data = PyMem_RawMalloc(size);
|
|
if (data->data == NULL) {
|
|
return -1;
|
|
}
|
|
data->free = PyMem_RawFree;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
_PyXIData_Clear(PyInterpreterState *interp, _PyXIData_t *data)
|
|
{
|
|
assert(data != NULL);
|
|
// This must be called in the owning interpreter.
|
|
assert(interp == NULL
|
|
|| _PyXIData_INTERPID(data) == -1
|
|
|| _PyXIData_INTERPID(data) == PyInterpreterState_GetID(interp));
|
|
_xidata_clear(data);
|
|
}
|
|
|
|
|
|
/* using cross-interpreter data */
|
|
|
|
static int
|
|
_check_xidata(PyThreadState *tstate, _PyXIData_t *data)
|
|
{
|
|
// data->data can be anything, including NULL, so we don't check it.
|
|
|
|
// data->obj may be NULL, so we don't check it.
|
|
|
|
if (_PyXIData_INTERPID(data) < 0) {
|
|
PyErr_SetString(PyExc_SystemError, "missing interp");
|
|
return -1;
|
|
}
|
|
|
|
if (data->new_object == NULL) {
|
|
PyErr_SetString(PyExc_SystemError, "missing new_object func");
|
|
return -1;
|
|
}
|
|
|
|
// data->free may be NULL, so we don't check it.
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void
|
|
_set_xid_lookup_failure(PyInterpreterState *interp,
|
|
PyObject *obj, const char *msg)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
int
|
|
_PyObject_CheckXIData(PyObject *obj)
|
|
{
|
|
PyInterpreterState *interp = PyInterpreterState_Get();
|
|
xidatafunc getdata = lookup_getdata(interp, obj);
|
|
if (getdata == NULL) {
|
|
if (!PyErr_Occurred()) {
|
|
_set_xid_lookup_failure(interp, obj, NULL);
|
|
}
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
_PyObject_GetXIData(PyObject *obj, _PyXIData_t *data)
|
|
{
|
|
PyThreadState *tstate = PyThreadState_Get();
|
|
PyInterpreterState *interp = tstate->interp;
|
|
|
|
// Reset data before re-populating.
|
|
*data = (_PyXIData_t){0};
|
|
_PyXIData_INTERPID(data) = -1;
|
|
|
|
// Call the "getdata" func for the object.
|
|
Py_INCREF(obj);
|
|
xidatafunc 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);
|
|
Py_DECREF(obj);
|
|
if (res != 0) {
|
|
return -1;
|
|
}
|
|
|
|
// Fill in the blanks and validate the result.
|
|
_PyXIData_INTERPID(data) = PyInterpreterState_GetID(interp);
|
|
if (_check_xidata(tstate, data) != 0) {
|
|
(void)_PyXIData_Release(data);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
PyObject *
|
|
_PyXIData_NewObject(_PyXIData_t *data)
|
|
{
|
|
return data->new_object(data);
|
|
}
|
|
|
|
static int
|
|
_call_clear_xidata(void *data)
|
|
{
|
|
_xidata_clear((_PyXIData_t *)data);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_xidata_release(_PyXIData_t *data, int rawfree)
|
|
{
|
|
if ((data->data == NULL || data->free == NULL) && data->obj == NULL) {
|
|
// Nothing to release!
|
|
if (rawfree) {
|
|
PyMem_RawFree(data);
|
|
}
|
|
else {
|
|
data->data = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Switch to the original interpreter.
|
|
PyInterpreterState *interp = _PyInterpreterState_LookUpID(
|
|
_PyXIData_INTERPID(data));
|
|
if (interp == NULL) {
|
|
// The interpreter was already destroyed.
|
|
// This function shouldn't have been called.
|
|
// XXX Someone leaked some memory...
|
|
assert(PyErr_Occurred());
|
|
if (rawfree) {
|
|
PyMem_RawFree(data);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// "Release" the data and/or the object.
|
|
if (rawfree) {
|
|
return _Py_CallInInterpreterAndRawFree(interp, _call_clear_xidata, data);
|
|
}
|
|
else {
|
|
return _Py_CallInInterpreter(interp, _call_clear_xidata, data);
|
|
}
|
|
}
|
|
|
|
int
|
|
_PyXIData_Release(_PyXIData_t *data)
|
|
{
|
|
return _xidata_release(data, 0);
|
|
}
|
|
|
|
int
|
|
_PyXIData_ReleaseAndRawFree(_PyXIData_t *data)
|
|
{
|
|
return _xidata_release(data, 1);
|
|
}
|
|
|
|
|
|
/*************************/
|
|
/* convenience utilities */
|
|
/*************************/
|
|
|
|
static const char *
|
|
_copy_string_obj_raw(PyObject *strobj, Py_ssize_t *p_size)
|
|
{
|
|
Py_ssize_t size = -1;
|
|
const char *str = PyUnicode_AsUTF8AndSize(strobj, &size);
|
|
if (str == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
char *copied = PyMem_RawMalloc(size+1);
|
|
if (copied == NULL) {
|
|
PyErr_NoMemory();
|
|
return NULL;
|
|
}
|
|
strcpy(copied, str);
|
|
if (p_size != NULL) {
|
|
*p_size = size;
|
|
}
|
|
return copied;
|
|
}
|
|
|
|
|
|
static int
|
|
_convert_exc_to_TracebackException(PyObject *exc, PyObject **p_tbexc)
|
|
{
|
|
PyObject *args = NULL;
|
|
PyObject *kwargs = NULL;
|
|
PyObject *create = NULL;
|
|
|
|
// This is inspired by _PyErr_Display().
|
|
PyObject *tbmod = PyImport_ImportModule("traceback");
|
|
if (tbmod == NULL) {
|
|
return -1;
|
|
}
|
|
PyObject *tbexc_type = PyObject_GetAttrString(tbmod, "TracebackException");
|
|
Py_DECREF(tbmod);
|
|
if (tbexc_type == NULL) {
|
|
return -1;
|
|
}
|
|
create = PyObject_GetAttrString(tbexc_type, "from_exception");
|
|
Py_DECREF(tbexc_type);
|
|
if (create == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
args = PyTuple_Pack(1, exc);
|
|
if (args == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
kwargs = PyDict_New();
|
|
if (kwargs == NULL) {
|
|
goto error;
|
|
}
|
|
if (PyDict_SetItemString(kwargs, "save_exc_type", Py_False) < 0) {
|
|
goto error;
|
|
}
|
|
if (PyDict_SetItemString(kwargs, "lookup_lines", Py_False) < 0) {
|
|
goto error;
|
|
}
|
|
|
|
PyObject *tbexc = PyObject_Call(create, args, kwargs);
|
|
Py_DECREF(args);
|
|
Py_DECREF(kwargs);
|
|
Py_DECREF(create);
|
|
if (tbexc == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
*p_tbexc = tbexc;
|
|
return 0;
|
|
|
|
error:
|
|
Py_XDECREF(args);
|
|
Py_XDECREF(kwargs);
|
|
Py_XDECREF(create);
|
|
return -1;
|
|
}
|
|
|
|
// We accommodate backports here.
|
|
#ifndef _Py_EMPTY_STR
|
|
# define _Py_EMPTY_STR &_Py_STR(empty)
|
|
#endif
|
|
|
|
static const char *
|
|
_format_TracebackException(PyObject *tbexc)
|
|
{
|
|
PyObject *lines = PyObject_CallMethod(tbexc, "format", NULL);
|
|
if (lines == NULL) {
|
|
return NULL;
|
|
}
|
|
assert(_Py_EMPTY_STR != NULL);
|
|
PyObject *formatted_obj = PyUnicode_Join(_Py_EMPTY_STR, lines);
|
|
Py_DECREF(lines);
|
|
if (formatted_obj == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
Py_ssize_t size = -1;
|
|
const char *formatted = _copy_string_obj_raw(formatted_obj, &size);
|
|
Py_DECREF(formatted_obj);
|
|
// We remove trailing the newline added by TracebackException.format().
|
|
assert(formatted[size-1] == '\n');
|
|
((char *)formatted)[size-1] = '\0';
|
|
return formatted;
|
|
}
|
|
|
|
|
|
static int
|
|
_release_xid_data(_PyXIData_t *data, int rawfree)
|
|
{
|
|
PyObject *exc = PyErr_GetRaisedException();
|
|
int res = rawfree
|
|
? _PyXIData_Release(data)
|
|
: _PyXIData_ReleaseAndRawFree(data);
|
|
if (res < 0) {
|
|
/* The owning interpreter is already destroyed. */
|
|
_PyXIData_Clear(NULL, data);
|
|
// XXX Emit a warning?
|
|
PyErr_Clear();
|
|
}
|
|
PyErr_SetRaisedException(exc);
|
|
return res;
|
|
}
|
|
|
|
|
|
/***********************/
|
|
/* exception snapshots */
|
|
/***********************/
|
|
|
|
static int
|
|
_excinfo_init_type_from_exception(struct _excinfo_type *info, PyObject *exc)
|
|
{
|
|
/* Note that this copies directly rather than into an intermediate
|
|
struct and does not clear on error. If we need that then we
|
|
should have a separate function to wrap this one
|
|
and do all that there. */
|
|
PyObject *strobj = NULL;
|
|
|
|
PyTypeObject *type = Py_TYPE(exc);
|
|
if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
|
assert(_Py_IsImmortal((PyObject *)type));
|
|
info->builtin = type;
|
|
}
|
|
else {
|
|
// Only builtin types are preserved.
|
|
info->builtin = NULL;
|
|
}
|
|
|
|
// __name__
|
|
strobj = PyType_GetName(type);
|
|
if (strobj == NULL) {
|
|
return -1;
|
|
}
|
|
info->name = _copy_string_obj_raw(strobj, NULL);
|
|
Py_DECREF(strobj);
|
|
if (info->name == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
// __qualname__
|
|
strobj = PyType_GetQualName(type);
|
|
if (strobj == NULL) {
|
|
return -1;
|
|
}
|
|
info->qualname = _copy_string_obj_raw(strobj, NULL);
|
|
Py_DECREF(strobj);
|
|
if (info->qualname == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
// __module__
|
|
strobj = PyType_GetModuleName(type);
|
|
if (strobj == NULL) {
|
|
return -1;
|
|
}
|
|
info->module = _copy_string_obj_raw(strobj, NULL);
|
|
Py_DECREF(strobj);
|
|
if (info->module == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_excinfo_init_type_from_object(struct _excinfo_type *info, PyObject *exctype)
|
|
{
|
|
PyObject *strobj = NULL;
|
|
|
|
// __name__
|
|
strobj = PyObject_GetAttrString(exctype, "__name__");
|
|
if (strobj == NULL) {
|
|
return -1;
|
|
}
|
|
info->name = _copy_string_obj_raw(strobj, NULL);
|
|
Py_DECREF(strobj);
|
|
if (info->name == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
// __qualname__
|
|
strobj = PyObject_GetAttrString(exctype, "__qualname__");
|
|
if (strobj == NULL) {
|
|
return -1;
|
|
}
|
|
info->qualname = _copy_string_obj_raw(strobj, NULL);
|
|
Py_DECREF(strobj);
|
|
if (info->qualname == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
// __module__
|
|
strobj = PyObject_GetAttrString(exctype, "__module__");
|
|
if (strobj == NULL) {
|
|
return -1;
|
|
}
|
|
info->module = _copy_string_obj_raw(strobj, NULL);
|
|
Py_DECREF(strobj);
|
|
if (info->module == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
_excinfo_clear_type(struct _excinfo_type *info)
|
|
{
|
|
if (info->builtin != NULL) {
|
|
assert(info->builtin->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN);
|
|
assert(_Py_IsImmortal((PyObject *)info->builtin));
|
|
}
|
|
if (info->name != NULL) {
|
|
PyMem_RawFree((void *)info->name);
|
|
}
|
|
if (info->qualname != NULL) {
|
|
PyMem_RawFree((void *)info->qualname);
|
|
}
|
|
if (info->module != NULL) {
|
|
PyMem_RawFree((void *)info->module);
|
|
}
|
|
*info = (struct _excinfo_type){NULL};
|
|
}
|
|
|
|
static void
|
|
_excinfo_normalize_type(struct _excinfo_type *info,
|
|
const char **p_module, const char **p_qualname)
|
|
{
|
|
if (info->name == NULL) {
|
|
assert(info->builtin == NULL);
|
|
assert(info->qualname == NULL);
|
|
assert(info->module == NULL);
|
|
// This is inspired by TracebackException.format_exception_only().
|
|
*p_module = NULL;
|
|
*p_qualname = NULL;
|
|
return;
|
|
}
|
|
|
|
const char *module = info->module;
|
|
const char *qualname = info->qualname;
|
|
if (qualname == NULL) {
|
|
qualname = info->name;
|
|
}
|
|
assert(module != NULL);
|
|
if (strcmp(module, "builtins") == 0) {
|
|
module = NULL;
|
|
}
|
|
else if (strcmp(module, "__main__") == 0) {
|
|
module = NULL;
|
|
}
|
|
*p_qualname = qualname;
|
|
*p_module = module;
|
|
}
|
|
|
|
static void
|
|
_PyXI_excinfo_Clear(_PyXI_excinfo *info)
|
|
{
|
|
_excinfo_clear_type(&info->type);
|
|
if (info->msg != NULL) {
|
|
PyMem_RawFree((void *)info->msg);
|
|
}
|
|
if (info->errdisplay != NULL) {
|
|
PyMem_RawFree((void *)info->errdisplay);
|
|
}
|
|
*info = (_PyXI_excinfo){{NULL}};
|
|
}
|
|
|
|
PyObject *
|
|
_PyXI_excinfo_format(_PyXI_excinfo *info)
|
|
{
|
|
const char *module, *qualname;
|
|
_excinfo_normalize_type(&info->type, &module, &qualname);
|
|
if (qualname != NULL) {
|
|
if (module != NULL) {
|
|
if (info->msg != NULL) {
|
|
return PyUnicode_FromFormat("%s.%s: %s",
|
|
module, qualname, info->msg);
|
|
}
|
|
else {
|
|
return PyUnicode_FromFormat("%s.%s", module, qualname);
|
|
}
|
|
}
|
|
else {
|
|
if (info->msg != NULL) {
|
|
return PyUnicode_FromFormat("%s: %s", qualname, info->msg);
|
|
}
|
|
else {
|
|
return PyUnicode_FromString(qualname);
|
|
}
|
|
}
|
|
}
|
|
else if (info->msg != NULL) {
|
|
return PyUnicode_FromString(info->msg);
|
|
}
|
|
else {
|
|
Py_RETURN_NONE;
|
|
}
|
|
}
|
|
|
|
static const char *
|
|
_PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
|
|
{
|
|
assert(exc != NULL);
|
|
|
|
if (PyErr_GivenExceptionMatches(exc, PyExc_MemoryError)) {
|
|
_PyXI_excinfo_Clear(info);
|
|
return NULL;
|
|
}
|
|
const char *failure = NULL;
|
|
|
|
if (_excinfo_init_type_from_exception(&info->type, exc) < 0) {
|
|
failure = "error while initializing exception type snapshot";
|
|
goto error;
|
|
}
|
|
|
|
// Extract the exception message.
|
|
PyObject *msgobj = PyObject_Str(exc);
|
|
if (msgobj == NULL) {
|
|
failure = "error while formatting exception";
|
|
goto error;
|
|
}
|
|
info->msg = _copy_string_obj_raw(msgobj, NULL);
|
|
Py_DECREF(msgobj);
|
|
if (info->msg == NULL) {
|
|
failure = "error while copying exception message";
|
|
goto error;
|
|
}
|
|
|
|
// Pickle a traceback.TracebackException.
|
|
PyObject *tbexc = NULL;
|
|
if (_convert_exc_to_TracebackException(exc, &tbexc) < 0) {
|
|
#ifdef Py_DEBUG
|
|
PyErr_FormatUnraisable("Exception ignored while creating TracebackException");
|
|
#endif
|
|
PyErr_Clear();
|
|
}
|
|
else {
|
|
info->errdisplay = _format_TracebackException(tbexc);
|
|
Py_DECREF(tbexc);
|
|
if (info->errdisplay == NULL) {
|
|
#ifdef Py_DEBUG
|
|
PyErr_FormatUnraisable("Exception ignored while formatting TracebackException");
|
|
#endif
|
|
PyErr_Clear();
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
|
|
error:
|
|
assert(failure != NULL);
|
|
_PyXI_excinfo_Clear(info);
|
|
return failure;
|
|
}
|
|
|
|
static const char *
|
|
_PyXI_excinfo_InitFromObject(_PyXI_excinfo *info, PyObject *obj)
|
|
{
|
|
const char *failure = NULL;
|
|
|
|
PyObject *exctype = PyObject_GetAttrString(obj, "type");
|
|
if (exctype == NULL) {
|
|
failure = "exception snapshot missing 'type' attribute";
|
|
goto error;
|
|
}
|
|
int res = _excinfo_init_type_from_object(&info->type, exctype);
|
|
Py_DECREF(exctype);
|
|
if (res < 0) {
|
|
failure = "error while initializing exception type snapshot";
|
|
goto error;
|
|
}
|
|
|
|
// Extract the exception message.
|
|
PyObject *msgobj = PyObject_GetAttrString(obj, "msg");
|
|
if (msgobj == NULL) {
|
|
failure = "exception snapshot missing 'msg' attribute";
|
|
goto error;
|
|
}
|
|
info->msg = _copy_string_obj_raw(msgobj, NULL);
|
|
Py_DECREF(msgobj);
|
|
if (info->msg == NULL) {
|
|
failure = "error while copying exception message";
|
|
goto error;
|
|
}
|
|
|
|
// Pickle a traceback.TracebackException.
|
|
PyObject *errdisplay = PyObject_GetAttrString(obj, "errdisplay");
|
|
if (errdisplay == NULL) {
|
|
failure = "exception snapshot missing 'errdisplay' attribute";
|
|
goto error;
|
|
}
|
|
info->errdisplay = _copy_string_obj_raw(errdisplay, NULL);
|
|
Py_DECREF(errdisplay);
|
|
if (info->errdisplay == NULL) {
|
|
failure = "error while copying exception error display";
|
|
goto error;
|
|
}
|
|
|
|
return NULL;
|
|
|
|
error:
|
|
assert(failure != NULL);
|
|
_PyXI_excinfo_Clear(info);
|
|
return failure;
|
|
}
|
|
|
|
static void
|
|
_PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype)
|
|
{
|
|
PyObject *tbexc = NULL;
|
|
if (info->errdisplay != NULL) {
|
|
tbexc = PyUnicode_FromString(info->errdisplay);
|
|
if (tbexc == NULL) {
|
|
PyErr_Clear();
|
|
}
|
|
}
|
|
|
|
PyObject *formatted = _PyXI_excinfo_format(info);
|
|
PyErr_SetObject(exctype, formatted);
|
|
Py_DECREF(formatted);
|
|
|
|
if (tbexc != NULL) {
|
|
PyObject *exc = PyErr_GetRaisedException();
|
|
if (PyObject_SetAttrString(exc, "_errdisplay", tbexc) < 0) {
|
|
#ifdef Py_DEBUG
|
|
PyErr_FormatUnraisable("Exception ignored when setting _errdisplay");
|
|
#endif
|
|
PyErr_Clear();
|
|
}
|
|
Py_DECREF(tbexc);
|
|
PyErr_SetRaisedException(exc);
|
|
}
|
|
}
|
|
|
|
static PyObject *
|
|
_PyXI_excinfo_TypeAsObject(_PyXI_excinfo *info)
|
|
{
|
|
PyObject *ns = _PyNamespace_New(NULL);
|
|
if (ns == NULL) {
|
|
return NULL;
|
|
}
|
|
int empty = 1;
|
|
|
|
if (info->type.name != NULL) {
|
|
PyObject *name = PyUnicode_FromString(info->type.name);
|
|
if (name == NULL) {
|
|
goto error;
|
|
}
|
|
int res = PyObject_SetAttrString(ns, "__name__", name);
|
|
Py_DECREF(name);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
empty = 0;
|
|
}
|
|
|
|
if (info->type.qualname != NULL) {
|
|
PyObject *qualname = PyUnicode_FromString(info->type.qualname);
|
|
if (qualname == NULL) {
|
|
goto error;
|
|
}
|
|
int res = PyObject_SetAttrString(ns, "__qualname__", qualname);
|
|
Py_DECREF(qualname);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
empty = 0;
|
|
}
|
|
|
|
if (info->type.module != NULL) {
|
|
PyObject *module = PyUnicode_FromString(info->type.module);
|
|
if (module == NULL) {
|
|
goto error;
|
|
}
|
|
int res = PyObject_SetAttrString(ns, "__module__", module);
|
|
Py_DECREF(module);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
empty = 0;
|
|
}
|
|
|
|
if (empty) {
|
|
Py_CLEAR(ns);
|
|
}
|
|
|
|
return ns;
|
|
|
|
error:
|
|
Py_DECREF(ns);
|
|
return NULL;
|
|
}
|
|
|
|
static PyObject *
|
|
_PyXI_excinfo_AsObject(_PyXI_excinfo *info)
|
|
{
|
|
PyObject *ns = _PyNamespace_New(NULL);
|
|
if (ns == NULL) {
|
|
return NULL;
|
|
}
|
|
int res;
|
|
|
|
PyObject *type = _PyXI_excinfo_TypeAsObject(info);
|
|
if (type == NULL) {
|
|
if (PyErr_Occurred()) {
|
|
goto error;
|
|
}
|
|
type = Py_NewRef(Py_None);
|
|
}
|
|
res = PyObject_SetAttrString(ns, "type", type);
|
|
Py_DECREF(type);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
|
|
PyObject *msg = info->msg != NULL
|
|
? PyUnicode_FromString(info->msg)
|
|
: Py_NewRef(Py_None);
|
|
if (msg == NULL) {
|
|
goto error;
|
|
}
|
|
res = PyObject_SetAttrString(ns, "msg", msg);
|
|
Py_DECREF(msg);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
|
|
PyObject *formatted = _PyXI_excinfo_format(info);
|
|
if (formatted == NULL) {
|
|
goto error;
|
|
}
|
|
res = PyObject_SetAttrString(ns, "formatted", formatted);
|
|
Py_DECREF(formatted);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
|
|
if (info->errdisplay != NULL) {
|
|
PyObject *tbexc = PyUnicode_FromString(info->errdisplay);
|
|
if (tbexc == NULL) {
|
|
PyErr_Clear();
|
|
}
|
|
else {
|
|
res = PyObject_SetAttrString(ns, "errdisplay", tbexc);
|
|
Py_DECREF(tbexc);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ns;
|
|
|
|
error:
|
|
Py_DECREF(ns);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int
|
|
_PyXI_InitExcInfo(_PyXI_excinfo *info, PyObject *exc)
|
|
{
|
|
assert(!PyErr_Occurred());
|
|
if (exc == NULL || exc == Py_None) {
|
|
PyErr_SetString(PyExc_ValueError, "missing exc");
|
|
return -1;
|
|
}
|
|
const char *failure;
|
|
if (PyExceptionInstance_Check(exc) || PyExceptionClass_Check(exc)) {
|
|
failure = _PyXI_excinfo_InitFromException(info, exc);
|
|
}
|
|
else {
|
|
failure = _PyXI_excinfo_InitFromObject(info, exc);
|
|
}
|
|
if (failure != NULL) {
|
|
PyErr_SetString(PyExc_Exception, failure);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
PyObject *
|
|
_PyXI_FormatExcInfo(_PyXI_excinfo *info)
|
|
{
|
|
return _PyXI_excinfo_format(info);
|
|
}
|
|
|
|
PyObject *
|
|
_PyXI_ExcInfoAsObject(_PyXI_excinfo *info)
|
|
{
|
|
return _PyXI_excinfo_AsObject(info);
|
|
}
|
|
|
|
void
|
|
_PyXI_ClearExcInfo(_PyXI_excinfo *info)
|
|
{
|
|
_PyXI_excinfo_Clear(info);
|
|
}
|
|
|
|
|
|
/***************************/
|
|
/* 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: _Py_FALLTHROUGH;
|
|
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_InterpreterError);
|
|
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_InterpreterError,
|
|
"failed to get __main__ namespace");
|
|
break;
|
|
case _PyXI_ERR_APPLY_NS_FAILURE:
|
|
PyErr_SetString(PyExc_InterpreterError,
|
|
"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_InitError(_PyXI_error *error, PyObject *excobj, _PyXI_errcode code)
|
|
{
|
|
if (error->interp == NULL) {
|
|
error->interp = PyInterpreterState_Get();
|
|
}
|
|
|
|
const char *failure = NULL;
|
|
if (code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
|
|
// There is an unhandled exception we need to propagate.
|
|
failure = _PyXI_excinfo_InitFromException(&error->uncaught, excobj);
|
|
if (failure != NULL) {
|
|
// We failed to initialize error->uncaught.
|
|
// XXX Print the excobj/traceback? Emit a warning?
|
|
// XXX Print the current exception/traceback?
|
|
if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
|
|
error->code = _PyXI_ERR_NO_MEMORY;
|
|
}
|
|
else {
|
|
error->code = _PyXI_ERR_OTHER;
|
|
}
|
|
PyErr_Clear();
|
|
}
|
|
else {
|
|
error->code = code;
|
|
}
|
|
assert(error->code != _PyXI_ERR_NO_ERROR);
|
|
}
|
|
else {
|
|
// There is an error code we need to propagate.
|
|
assert(excobj == NULL);
|
|
assert(code != _PyXI_ERR_NO_ERROR);
|
|
error->code = code;
|
|
_PyXI_excinfo_Clear(&error->uncaught);
|
|
}
|
|
return failure;
|
|
}
|
|
|
|
PyObject *
|
|
_PyXI_ApplyError(_PyXI_error *error)
|
|
{
|
|
if (error->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
|
|
// Raise an exception that proxies the propagated exception.
|
|
return _PyXI_excinfo_AsObject(&error->uncaught);
|
|
}
|
|
else if (error->code == _PyXI_ERR_NOT_SHAREABLE) {
|
|
// Propagate the exception directly.
|
|
_set_xid_lookup_failure(error->interp, NULL, error->uncaught.msg);
|
|
}
|
|
else {
|
|
// Raise an exception corresponding to the code.
|
|
assert(error->code != _PyXI_ERR_NO_ERROR);
|
|
(void)_PyXI_ApplyErrorCode(error->code, error->interp);
|
|
if (error->uncaught.type.name != NULL || error->uncaught.msg != NULL) {
|
|
// __context__ will be set to a proxy of the propagated exception.
|
|
PyObject *exc = PyErr_GetRaisedException();
|
|
_PyXI_excinfo_Apply(&error->uncaught, PyExc_InterpreterError);
|
|
PyObject *exc2 = PyErr_GetRaisedException();
|
|
PyException_SetContext(exc, exc2);
|
|
PyErr_SetRaisedException(exc);
|
|
}
|
|
}
|
|
assert(PyErr_Occurred());
|
|
return NULL;
|
|
}
|
|
|
|
/* shared namespaces */
|
|
|
|
/* Shared namespaces are expected to have relatively short lifetimes.
|
|
This means dealloc of a shared namespace will normally happen "soon".
|
|
Namespace items hold cross-interpreter data, which must get released.
|
|
If the namespace/items are cleared in a different interpreter than
|
|
where the items' cross-interpreter data was set then that will cause
|
|
pending calls to be used to release the cross-interpreter data.
|
|
The tricky bit is that the pending calls can happen sufficiently
|
|
later that the namespace/items might already be deallocated. This is
|
|
a problem if the cross-interpreter data is allocated as part of a
|
|
namespace item. If that's the case then we must ensure the shared
|
|
namespace is only cleared/freed *after* that data has been released. */
|
|
|
|
typedef struct _sharednsitem {
|
|
const char *name;
|
|
_PyXIData_t *data;
|
|
// We could have a "PyXIData _data" field, so it would
|
|
// be allocated as part of the item and avoid an extra allocation.
|
|
// However, doing so adds a bunch of complexity because we must
|
|
// ensure the item isn't freed before a pending call might happen
|
|
// in a different interpreter to release the XI data.
|
|
} _PyXI_namespace_item;
|
|
|
|
static int
|
|
_sharednsitem_is_initialized(_PyXI_namespace_item *item)
|
|
{
|
|
if (item->name != NULL) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_sharednsitem_init(_PyXI_namespace_item *item, PyObject *key)
|
|
{
|
|
item->name = _copy_string_obj_raw(key, NULL);
|
|
if (item->name == NULL) {
|
|
assert(!_sharednsitem_is_initialized(item));
|
|
return -1;
|
|
}
|
|
item->data = NULL;
|
|
assert(_sharednsitem_is_initialized(item));
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
_sharednsitem_has_value(_PyXI_namespace_item *item, int64_t *p_interpid)
|
|
{
|
|
if (item->data == NULL) {
|
|
return 0;
|
|
}
|
|
if (p_interpid != NULL) {
|
|
*p_interpid = _PyXIData_INTERPID(item->data);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
_sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value)
|
|
{
|
|
assert(_sharednsitem_is_initialized(item));
|
|
assert(item->data == NULL);
|
|
item->data = PyMem_RawMalloc(sizeof(_PyXIData_t));
|
|
if (item->data == NULL) {
|
|
PyErr_NoMemory();
|
|
return -1;
|
|
}
|
|
if (_PyObject_GetXIData(value, item->data) != 0) {
|
|
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_value(_PyXI_namespace_item *item)
|
|
{
|
|
_PyXIData_t *data = item->data;
|
|
if (data != NULL) {
|
|
item->data = NULL;
|
|
int rawfree = 1;
|
|
(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_value(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 = _PyXIData_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 {
|
|
Py_ssize_t len;
|
|
_PyXI_namespace_item *items;
|
|
};
|
|
|
|
static _PyXI_namespace *
|
|
_sharedns_new(void)
|
|
{
|
|
_PyXI_namespace *ns = PyMem_RawCalloc(sizeof(_PyXI_namespace), 1);
|
|
if (ns == NULL) {
|
|
PyErr_NoMemory();
|
|
return NULL;
|
|
}
|
|
*ns = (_PyXI_namespace){ 0 };
|
|
return ns;
|
|
}
|
|
|
|
static int
|
|
_sharedns_is_initialized(_PyXI_namespace *ns)
|
|
{
|
|
if (ns->len == 0) {
|
|
assert(ns->items == NULL);
|
|
return 0;
|
|
}
|
|
|
|
assert(ns->len > 0);
|
|
assert(ns->items != NULL);
|
|
assert(_sharednsitem_is_initialized(&ns->items[0]));
|
|
assert(ns->len == 1
|
|
|| _sharednsitem_is_initialized(&ns->items[ns->len - 1]));
|
|
return 1;
|
|
}
|
|
|
|
#define HAS_COMPLETE_DATA 1
|
|
#define HAS_PARTIAL_DATA 2
|
|
|
|
static int
|
|
_sharedns_has_xidata(_PyXI_namespace *ns, int64_t *p_interpid)
|
|
{
|
|
// We expect _PyXI_namespace to always be initialized.
|
|
assert(_sharedns_is_initialized(ns));
|
|
int res = 0;
|
|
_PyXI_namespace_item *item0 = &ns->items[0];
|
|
if (!_sharednsitem_is_initialized(item0)) {
|
|
return 0;
|
|
}
|
|
int64_t interpid0 = -1;
|
|
if (!_sharednsitem_has_value(item0, &interpid0)) {
|
|
return 0;
|
|
}
|
|
if (ns->len > 1) {
|
|
// At this point we know it is has at least partial data.
|
|
_PyXI_namespace_item *itemN = &ns->items[ns->len-1];
|
|
if (!_sharednsitem_is_initialized(itemN)) {
|
|
res = HAS_PARTIAL_DATA;
|
|
goto finally;
|
|
}
|
|
int64_t interpidN = -1;
|
|
if (!_sharednsitem_has_value(itemN, &interpidN)) {
|
|
res = HAS_PARTIAL_DATA;
|
|
goto finally;
|
|
}
|
|
assert(interpidN == interpid0);
|
|
}
|
|
res = HAS_COMPLETE_DATA;
|
|
*p_interpid = interpid0;
|
|
|
|
finally:
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
_sharedns_clear(_PyXI_namespace *ns)
|
|
{
|
|
if (!_sharedns_is_initialized(ns)) {
|
|
return;
|
|
}
|
|
|
|
// If the cross-interpreter data were allocated as part of
|
|
// _PyXI_namespace_item (instead of dynamically), this is where
|
|
// we would need verify that we are clearing the items in the
|
|
// correct interpreter, to avoid a race with releasing the XI data
|
|
// via a pending call. See _sharedns_has_xidata().
|
|
for (Py_ssize_t i=0; i < ns->len; i++) {
|
|
_sharednsitem_clear(&ns->items[i]);
|
|
}
|
|
PyMem_RawFree(ns->items);
|
|
ns->items = NULL;
|
|
ns->len = 0;
|
|
}
|
|
|
|
static void
|
|
_sharedns_free(_PyXI_namespace *ns)
|
|
{
|
|
_sharedns_clear(ns);
|
|
PyMem_RawFree(ns);
|
|
}
|
|
|
|
static int
|
|
_sharedns_init(_PyXI_namespace *ns, PyObject *names)
|
|
{
|
|
assert(!_sharedns_is_initialized(ns));
|
|
assert(names != NULL);
|
|
Py_ssize_t len = PyDict_CheckExact(names)
|
|
? PyDict_Size(names)
|
|
: PySequence_Size(names);
|
|
if (len < 0) {
|
|
return -1;
|
|
}
|
|
if (len == 0) {
|
|
PyErr_SetString(PyExc_ValueError, "empty namespaces not allowed");
|
|
return -1;
|
|
}
|
|
assert(len > 0);
|
|
|
|
// Allocate the items.
|
|
_PyXI_namespace_item *items =
|
|
PyMem_RawCalloc(sizeof(struct _sharednsitem), len);
|
|
if (items == NULL) {
|
|
PyErr_NoMemory();
|
|
return -1;
|
|
}
|
|
|
|
// Fill in the names.
|
|
Py_ssize_t i = -1;
|
|
if (PyDict_CheckExact(names)) {
|
|
Py_ssize_t pos = 0;
|
|
for (i=0; i < len; i++) {
|
|
PyObject *key;
|
|
if (!PyDict_Next(names, &pos, &key, NULL)) {
|
|
// This should not be possible.
|
|
assert(0);
|
|
goto error;
|
|
}
|
|
if (_sharednsitem_init(&items[i], key) < 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
else if (PySequence_Check(names)) {
|
|
for (i=0; i < len; i++) {
|
|
PyObject *key = PySequence_GetItem(names, i);
|
|
if (key == NULL) {
|
|
goto error;
|
|
}
|
|
int res = _sharednsitem_init(&items[i], key);
|
|
Py_DECREF(key);
|
|
if (res < 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
PyErr_SetString(PyExc_NotImplementedError,
|
|
"non-sequence namespace not supported");
|
|
goto error;
|
|
}
|
|
|
|
ns->items = items;
|
|
ns->len = len;
|
|
assert(_sharedns_is_initialized(ns));
|
|
return 0;
|
|
|
|
error:
|
|
for (Py_ssize_t j=0; j < i; j++) {
|
|
_sharednsitem_clear(&items[j]);
|
|
}
|
|
PyMem_RawFree(items);
|
|
assert(!_sharedns_is_initialized(ns));
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
_PyXI_FreeNamespace(_PyXI_namespace *ns)
|
|
{
|
|
if (!_sharedns_is_initialized(ns)) {
|
|
return;
|
|
}
|
|
|
|
int64_t interpid = -1;
|
|
if (!_sharedns_has_xidata(ns, &interpid)) {
|
|
_sharedns_free(ns);
|
|
return;
|
|
}
|
|
|
|
if (interpid == PyInterpreterState_GetID(PyInterpreterState_Get())) {
|
|
_sharedns_free(ns);
|
|
}
|
|
else {
|
|
// If we weren't always dynamically allocating the cross-interpreter
|
|
// data in each item then we would need to using a pending call
|
|
// to call _sharedns_free(), to avoid the race between freeing
|
|
// the shared namespace and releasing the XI data.
|
|
_sharedns_free(ns);
|
|
}
|
|
}
|
|
|
|
_PyXI_namespace *
|
|
_PyXI_NamespaceFromNames(PyObject *names)
|
|
{
|
|
if (names == NULL || names == Py_None) {
|
|
return NULL;
|
|
}
|
|
|
|
_PyXI_namespace *ns = _sharedns_new();
|
|
if (ns == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (_sharedns_init(ns, names) < 0) {
|
|
PyMem_RawFree(ns);
|
|
if (PySequence_Size(names) == 0) {
|
|
PyErr_Clear();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
return ns;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
static int _session_is_active(_PyXI_session *);
|
|
#endif
|
|
static void _propagate_not_shareable_error(_PyXI_session *);
|
|
|
|
int
|
|
_PyXI_FillNamespaceFromDict(_PyXI_namespace *ns, PyObject *nsobj,
|
|
_PyXI_session *session)
|
|
{
|
|
// session must be entered already, if provided.
|
|
assert(session == NULL || _session_is_active(session));
|
|
assert(_sharedns_is_initialized(ns));
|
|
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_value(&ns->items[j]);
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// 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_is_active(session));
|
|
if (nsobj == NULL || nsobj == Py_None) {
|
|
return NULL;
|
|
}
|
|
if (!PyDict_CheckExact(nsobj)) {
|
|
PyErr_SetString(PyExc_TypeError, "expected a dict");
|
|
return NULL;
|
|
}
|
|
|
|
_PyXI_namespace *ns = _sharedns_new();
|
|
if (ns == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (_sharedns_init(ns, nsobj) < 0) {
|
|
if (PyDict_Size(nsobj) == 0) {
|
|
PyMem_RawFree(ns);
|
|
PyErr_Clear();
|
|
return NULL;
|
|
}
|
|
goto error;
|
|
}
|
|
|
|
if (_PyXI_FillNamespaceFromDict(ns, nsobj, session) < 0) {
|
|
goto error;
|
|
}
|
|
|
|
return ns;
|
|
|
|
error:
|
|
assert(PyErr_Occurred()
|
|
|| (session != NULL && session->error_override != NULL));
|
|
_sharedns_free(ns);
|
|
return NULL;
|
|
}
|
|
|
|
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->error_override == NULL);
|
|
// Set elsewhere and cleared in _PyXI_ApplyCapturedException().
|
|
assert(session->error == NULL);
|
|
|
|
// Switch to interpreter.
|
|
PyThreadState *tstate = PyThreadState_Get();
|
|
PyThreadState *prev = tstate;
|
|
if (interp != tstate->interp) {
|
|
tstate = _PyThreadState_NewBound(interp, _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;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
static int
|
|
_session_is_active(_PyXI_session *session)
|
|
{
|
|
return (session->init_tstate != NULL);
|
|
}
|
|
#endif
|
|
|
|
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->_error_override = _PyXI_ERR_NOT_SHAREABLE;
|
|
session->error_override = &session->_error_override;
|
|
}
|
|
}
|
|
|
|
static void
|
|
_capture_current_exception(_PyXI_session *session)
|
|
{
|
|
assert(session->error == NULL);
|
|
if (!PyErr_Occurred()) {
|
|
assert(session->error_override == NULL);
|
|
return;
|
|
}
|
|
|
|
// Handle the exception override.
|
|
_PyXI_errcode *override = session->error_override;
|
|
session->error_override = NULL;
|
|
_PyXI_errcode errcode = override != NULL
|
|
? *override
|
|
: _PyXI_ERR_UNCAUGHT_EXCEPTION;
|
|
|
|
// 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_error *err = &session->_error;
|
|
*err = (_PyXI_error){
|
|
.interp = session->init_tstate->interp,
|
|
};
|
|
const char *failure;
|
|
if (excval == NULL) {
|
|
failure = _PyXI_InitError(err, NULL, errcode);
|
|
}
|
|
else {
|
|
failure = _PyXI_InitError(err, excval, _PyXI_ERR_UNCAUGHT_EXCEPTION);
|
|
Py_DECREF(excval);
|
|
if (failure == NULL && override != NULL) {
|
|
err->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);
|
|
err = NULL;
|
|
}
|
|
|
|
// Finished!
|
|
assert(!PyErr_Occurred());
|
|
session->error = err;
|
|
}
|
|
|
|
PyObject *
|
|
_PyXI_ApplyCapturedException(_PyXI_session *session)
|
|
{
|
|
assert(!PyErr_Occurred());
|
|
assert(session->error != NULL);
|
|
PyObject *res = _PyXI_ApplyError(session->error);
|
|
assert((res == NULL) != (PyErr_Occurred() == NULL));
|
|
session->error = NULL;
|
|
return res;
|
|
}
|
|
|
|
int
|
|
_PyXI_HasCapturedException(_PyXI_session *session)
|
|
{
|
|
return session->error != 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->error == 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->error_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)
|
|
{
|
|
// Initialize the XID lookup state (e.g. registry).
|
|
if (_Py_IsMainInterpreter(interp)) {
|
|
xid_lookup_init(&_PyXI_GET_GLOBAL_STATE(interp)->data_lookup);
|
|
}
|
|
xid_lookup_init(&_PyXI_GET_STATE(interp)->data_lookup);
|
|
|
|
// Initialize exceptions (heap types).
|
|
if (_init_not_shareable_error_type(interp) < 0) {
|
|
return _PyStatus_ERR("failed to initialize NotShareableError");
|
|
}
|
|
|
|
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 lookup state (e.g. registry).
|
|
xid_lookup_fini(&_PyXI_GET_STATE(interp)->data_lookup);
|
|
if (_Py_IsMainInterpreter(interp)) {
|
|
xid_lookup_fini(&_PyXI_GET_GLOBAL_STATE(interp)->data_lookup);
|
|
}
|
|
}
|
|
|
|
PyStatus
|
|
_PyXI_InitTypes(PyInterpreterState *interp)
|
|
{
|
|
if (init_exceptions(interp) < 0) {
|
|
PyErr_PrintEx(0);
|
|
return _PyStatus_ERR("failed to initialize an exception type");
|
|
}
|
|
return _PyStatus_OK();
|
|
}
|
|
|
|
void
|
|
_PyXI_FiniTypes(PyInterpreterState *interp)
|
|
{
|
|
fini_exceptions(interp);
|
|
}
|
|
|
|
|
|
/*************/
|
|
/* other API */
|
|
/*************/
|
|
|
|
PyInterpreterState *
|
|
_PyXI_NewInterpreter(PyInterpreterConfig *config, long *maybe_whence,
|
|
PyThreadState **p_tstate, PyThreadState **p_save_tstate)
|
|
{
|
|
PyThreadState *save_tstate = PyThreadState_Swap(NULL);
|
|
assert(save_tstate != NULL);
|
|
|
|
PyThreadState *tstate;
|
|
PyStatus status = Py_NewInterpreterFromConfig(&tstate, config);
|
|
if (PyStatus_Exception(status)) {
|
|
// Since no new thread state was created, there is no exception
|
|
// to propagate; raise a fresh one after swapping back in the
|
|
// old thread state.
|
|
PyThreadState_Swap(save_tstate);
|
|
_PyErr_SetFromPyStatus(status);
|
|
PyObject *exc = PyErr_GetRaisedException();
|
|
PyErr_SetString(PyExc_InterpreterError,
|
|
"sub-interpreter creation failed");
|
|
_PyErr_ChainExceptions1(exc);
|
|
return NULL;
|
|
}
|
|
assert(tstate != NULL);
|
|
PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate);
|
|
|
|
long whence = _PyInterpreterState_WHENCE_XI;
|
|
if (maybe_whence != NULL) {
|
|
whence = *maybe_whence;
|
|
}
|
|
_PyInterpreterState_SetWhence(interp, whence);
|
|
|
|
if (p_tstate != NULL) {
|
|
// We leave the new thread state as the current one.
|
|
*p_tstate = tstate;
|
|
}
|
|
else {
|
|
// Throw away the initial tstate.
|
|
PyThreadState_Clear(tstate);
|
|
PyThreadState_Swap(save_tstate);
|
|
PyThreadState_Delete(tstate);
|
|
save_tstate = NULL;
|
|
}
|
|
if (p_save_tstate != NULL) {
|
|
*p_save_tstate = save_tstate;
|
|
}
|
|
return interp;
|
|
}
|
|
|
|
void
|
|
_PyXI_EndInterpreter(PyInterpreterState *interp,
|
|
PyThreadState *tstate, PyThreadState **p_save_tstate)
|
|
{
|
|
#ifndef NDEBUG
|
|
long whence = _PyInterpreterState_GetWhence(interp);
|
|
#endif
|
|
assert(whence != _PyInterpreterState_WHENCE_RUNTIME);
|
|
|
|
if (!_PyInterpreterState_IsReady(interp)) {
|
|
assert(whence == _PyInterpreterState_WHENCE_UNKNOWN);
|
|
// PyInterpreterState_Clear() requires the GIL,
|
|
// which a not-ready does not have, so we don't clear it.
|
|
// That means there may be leaks here until clearing the
|
|
// interpreter is fixed.
|
|
PyInterpreterState_Delete(interp);
|
|
return;
|
|
}
|
|
assert(whence != _PyInterpreterState_WHENCE_UNKNOWN);
|
|
|
|
PyThreadState *save_tstate = NULL;
|
|
PyThreadState *cur_tstate = PyThreadState_GET();
|
|
if (tstate == NULL) {
|
|
if (PyThreadState_GetInterpreter(cur_tstate) == interp) {
|
|
tstate = cur_tstate;
|
|
}
|
|
else {
|
|
tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_FINI);
|
|
assert(tstate != NULL);
|
|
save_tstate = PyThreadState_Swap(tstate);
|
|
}
|
|
}
|
|
else {
|
|
assert(PyThreadState_GetInterpreter(tstate) == interp);
|
|
if (tstate != cur_tstate) {
|
|
assert(PyThreadState_GetInterpreter(cur_tstate) != interp);
|
|
save_tstate = PyThreadState_Swap(tstate);
|
|
}
|
|
}
|
|
|
|
Py_EndInterpreter(tstate);
|
|
|
|
if (p_save_tstate != NULL) {
|
|
save_tstate = *p_save_tstate;
|
|
}
|
|
PyThreadState_Swap(save_tstate);
|
|
}
|