cpython/Python/crossinterp.c
Eric Snow fd259fdabe
gh-76785: Handle Legacy Interpreters Properly (gh-117490)
This is similar to the situation with threading._DummyThread.  The methods (incl. __del__()) of interpreters.Interpreter objects must be careful with interpreters not created by interpreters.create().  The simplest thing to start with is to disable any method that modifies or runs in the interpreter.  As part of this, the runtime keeps track of where an interpreter was created.  We also handle interpreter "refcounts" properly.
2024-04-11 23:23:25 +00:00

1918 lines
50 KiB
C

/* API for managing interactions between isolated interpreters */
#include "Python.h"
#include "pycore_ceval.h" // _Py_simple_func
#include "pycore_crossinterp.h" // 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;
}
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 formating 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);
assert(_PyInterpreterState_IsRunningMain(interp));
_PyInterpreterState_FailIfRunningMain(interp);
break;
case _PyXI_ERR_MAIN_NS_FAILURE:
PyErr_SetString(PyExc_InterpreterError,
"failed to get __main__ namespace");
break;
case _PyXI_ERR_APPLY_NS_FAILURE:
PyErr_SetString(PyExc_InterpreterError,
"failed to apply namespace to __main__");
break;
case _PyXI_ERR_NOT_SHAREABLE:
_set_xid_lookup_failure(interp, NULL, NULL);
break;
default:
#ifdef Py_DEBUG
Py_UNREACHABLE();
#else
PyErr_Format(PyExc_RuntimeError, "unsupported error code %d", code);
#endif
}
assert(PyErr_Occurred());
return -1;
}
/* shared exceptions */
static const char *
_PyXI_InitError(_PyXI_error *error, PyObject *excobj, _PyXI_errcode code)
{
if (error->interp == NULL) {
error->interp = PyInterpreterState_Get();
}
const char *failure = NULL;
if (code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
// There is an unhandled exception we need to propagate.
failure = _PyXI_excinfo_InitFromException(&error->uncaught, excobj);
if (failure != NULL) {
// We failed to initialize error->uncaught.
// XXX Print the excobj/traceback? Emit a warning?
// XXX Print the current exception/traceback?
if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
error->code = _PyXI_ERR_NO_MEMORY;
}
else {
error->code = _PyXI_ERR_OTHER;
}
PyErr_Clear();
}
else {
error->code = code;
}
assert(error->code != _PyXI_ERR_NO_ERROR);
}
else {
// There is an error code we need to propagate.
assert(excobj == NULL);
assert(code != _PyXI_ERR_NO_ERROR);
error->code = code;
_PyXI_excinfo_Clear(&error->uncaught);
}
return failure;
}
PyObject *
_PyXI_ApplyError(_PyXI_error *error)
{
if (error->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
// Raise an exception that proxies the propagated exception.
return _PyXI_excinfo_AsObject(&error->uncaught);
}
else if (error->code == _PyXI_ERR_NOT_SHAREABLE) {
// Propagate the exception directly.
_set_xid_lookup_failure(error->interp, NULL, error->uncaught.msg);
}
else {
// Raise an exception corresponding to the code.
assert(error->code != _PyXI_ERR_NO_ERROR);
(void)_PyXI_ApplyErrorCode(error->code, error->interp);
if (error->uncaught.type.name != NULL || error->uncaught.msg != NULL) {
// __context__ will be set to a proxy of the propagated exception.
PyObject *exc = PyErr_GetRaisedException();
_PyXI_excinfo_Apply(&error->uncaught, PyExc_InterpreterError);
PyObject *exc2 = PyErr_GetRaisedException();
PyException_SetContext(exc, exc2);
PyErr_SetRaisedException(exc);
}
}
assert(PyErr_Occurred());
return NULL;
}
/* shared namespaces */
/* Shared namespaces are expected to have relatively short lifetimes.
This means dealloc of a shared namespace will normally happen "soon".
Namespace items hold cross-interpreter data, which must get released.
If the namespace/items are cleared in a different interpreter than
where the items' cross-interpreter data was set then that will cause
pending calls to be used to release the cross-interpreter data.
The tricky bit is that the pending calls can happen sufficiently
later that the namespace/items might already be deallocated. This is
a problem if the cross-interpreter data is allocated as part of a
namespace item. If that's the case then we must ensure the shared
namespace is only cleared/freed *after* that data has been released. */
typedef struct _sharednsitem {
const char *name;
_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_New(interp);
_PyThreadState_SetWhence(tstate, _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_New(interp);
_PyThreadState_SetWhence(tstate, _PyThreadState_WHENCE_INTERP);
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);
}