diff options
| author | shadchin <[email protected]> | 2026-02-03 21:59:07 +0300 |
|---|---|---|
| committer | shadchin <[email protected]> | 2026-02-03 22:28:51 +0300 |
| commit | bce46f28de392862d5c6c3b185d844ee7c623be3 (patch) | |
| tree | 424878b5b90144f98970ce4a2745990c77330ad2 /contrib/tools/python3/Python/crossinterp.c | |
| parent | 0e0ee9fa48ce9411b4038aa769493d22ff6c10a2 (diff) | |
Import Python 3.13.11
commit_hash:bbb53cefb159aa3e7afaa475fd19d5a03b66945f
Diffstat (limited to 'contrib/tools/python3/Python/crossinterp.c')
| -rw-r--r-- | contrib/tools/python3/Python/crossinterp.c | 1922 |
1 files changed, 1922 insertions, 0 deletions
diff --git a/contrib/tools/python3/Python/crossinterp.c b/contrib/tools/python3/Python/crossinterp.c new file mode 100644 index 00000000000..2f6324d300d --- /dev/null +++ b/contrib/tools/python3/Python/crossinterp.c @@ -0,0 +1,1922 @@ + +/* API for managing interactions between isolated interpreters */ + +#include "Python.h" +#include "pycore_ceval.h" // _Py_simple_func +#include "pycore_crossinterp.h" // struct _xid +#include "pycore_initconfig.h" // _PyStatus_OK() +#include "pycore_namespace.h" //_PyNamespace_New() +#include "pycore_pyerrors.h" // _PyErr_Clear() +#include "pycore_weakref.h" // _PyWeakref_GET_REF() + + +/**************/ +/* 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 -> crossinterpdatafunc} */ + +/* For now we use a global registry of shareable classes. An + alternative would be to add a tp_* slot for a class's + crossinterpdatafunc. It would be simpler and more efficient. */ + +static void xid_lookup_init(PyInterpreterState *); +static void xid_lookup_fini(PyInterpreterState *); +static crossinterpdatafunc lookup_getdata(PyInterpreterState *, PyObject *); +#include "crossinterp_data_lookup.h" + + +/* lifecycle */ + +_PyCrossInterpreterData * +_PyCrossInterpreterData_New(void) +{ + _PyCrossInterpreterData *xid = PyMem_RawMalloc( + sizeof(_PyCrossInterpreterData)); + if (xid == NULL) { + PyErr_NoMemory(); + } + return xid; +} + +void +_PyCrossInterpreterData_Free(_PyCrossInterpreterData *xid) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + _PyCrossInterpreterData_Clear(interp, xid); + PyMem_RawFree(xid); +} + + +/* defining cross-interpreter data */ + +static inline void +_xidata_init(_PyCrossInterpreterData *data) +{ + // If the value is being reused + // then _xidata_clear() should have been called already. + assert(data->data == NULL); + assert(data->obj == NULL); + *data = (_PyCrossInterpreterData){0}; + _PyCrossInterpreterData_INTERPID(data) = -1; +} + +static inline void +_xidata_clear(_PyCrossInterpreterData *data) +{ + // _PyCrossInterpreterData 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 +_PyCrossInterpreterData_Init(_PyCrossInterpreterData *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 _PyCrossInterpreterData_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). + _PyCrossInterpreterData_INTERPID(data) = (interp != NULL) + ? PyInterpreterState_GetID(interp) + : -1; + data->new_object = new_object; +} + +int +_PyCrossInterpreterData_InitWithSize(_PyCrossInterpreterData *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); + _PyCrossInterpreterData_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 +_PyCrossInterpreterData_Clear(PyInterpreterState *interp, + _PyCrossInterpreterData *data) +{ + assert(data != NULL); + // This must be called in the owning interpreter. + assert(interp == NULL + || _PyCrossInterpreterData_INTERPID(data) == -1 + || _PyCrossInterpreterData_INTERPID(data) == PyInterpreterState_GetID(interp)); + _xidata_clear(data); +} + + +/* using cross-interpreter data */ + +static int +_check_xidata(PyThreadState *tstate, _PyCrossInterpreterData *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 (_PyCrossInterpreterData_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_CheckCrossInterpreterData(PyObject *obj) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + crossinterpdatafunc getdata = lookup_getdata(interp, obj); + if (getdata == NULL) { + if (!PyErr_Occurred()) { + _set_xid_lookup_failure(interp, obj, NULL); + } + return -1; + } + return 0; +} + +int +_PyObject_GetCrossInterpreterData(PyObject *obj, _PyCrossInterpreterData *data) +{ + PyThreadState *tstate = PyThreadState_Get(); + PyInterpreterState *interp = tstate->interp; + + // Reset data before re-populating. + *data = (_PyCrossInterpreterData){0}; + _PyCrossInterpreterData_INTERPID(data) = -1; + + // Call the "getdata" func for the object. + Py_INCREF(obj); + crossinterpdatafunc 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. + _PyCrossInterpreterData_INTERPID(data) = PyInterpreterState_GetID(interp); + if (_check_xidata(tstate, data) != 0) { + (void)_PyCrossInterpreterData_Release(data); + return -1; + } + + return 0; +} + +PyObject * +_PyCrossInterpreterData_NewObject(_PyCrossInterpreterData *data) +{ + return data->new_object(data); +} + +static int +_call_clear_xidata(void *data) +{ + _xidata_clear((_PyCrossInterpreterData *)data); + return 0; +} + +static int +_xidata_release(_PyCrossInterpreterData *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( + _PyCrossInterpreterData_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 +_PyCrossInterpreterData_Release(_PyCrossInterpreterData *data) +{ + return _xidata_release(data, 0); +} + +int +_PyCrossInterpreterData_ReleaseAndRawFree(_PyCrossInterpreterData *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; + } + + if (size != (Py_ssize_t)strlen(str)) { + PyErr_SetString(PyExc_ValueError, "found embedded NULL character"); + 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(_PyCrossInterpreterData *data, int rawfree) +{ + PyObject *exc = PyErr_GetRaisedException(); + int res = rawfree + ? _PyCrossInterpreterData_ReleaseAndRawFree(data) + : _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; +} + + +/***********************/ +/* 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: // fall through + 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); + // In 3.14+ we use _PyErr_SetInterpreterAlreadyRunning(). + PyErr_SetString(PyExc_InterpreterError, "interpreter already running"); + 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; + _PyCrossInterpreterData *data; + // We could have a "PyCrossInterpreterData _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 = _PyCrossInterpreterData_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(_PyCrossInterpreterData)); + if (item->data == NULL) { + PyErr_NoMemory(); + return -1; + } + if (_PyObject_GetCrossInterpreterData(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) +{ + _PyCrossInterpreterData *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 = _PyCrossInterpreterData_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) { + assert(PyDict_Check(nsupdates)); + 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). + xid_lookup_init(interp); + + // 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(interp); +} + +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); +} |
