| /* 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_Release(data) | |
| : _PyCrossInterpreterData_ReleaseAndRawFree(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) { | |
| 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); | |
| } |