cpython/Modules/_xxsubinterpretersmodule.c

3577 lines
90 KiB
C

/* _interpreters module */
/* low-level access to interpreter primitives */
#include "Python.h"
#include "frameobject.h"
#include "interpreteridobject.h"
// XXX Emit a warning?
#define IGNORE_FAILURE(msg) \
fprintf(stderr, " -----\nRunFailedError: %s\n", msg); \
PyErr_PrintEx(0); \
fprintf(stderr, " -----\n"); \
PyErr_Clear();
typedef void (*_deallocfunc)(void *);
static PyInterpreterState *
_get_current(void)
{
// _PyInterpreterState_Get() aborts if lookup fails, so don't need
// to check the result for NULL.
return _PyInterpreterState_Get();
}
/* string utils *************************************************************/
// PyMem_Free() must be used to dealocate the resulting string.
static char *
_strdup_and_size(const char *data, Py_ssize_t *psize, _deallocfunc *dealloc)
{
if (data == NULL) {
if (psize != NULL) {
*psize = 0;
}
if (dealloc != NULL) {
*dealloc = NULL;
}
return "";
}
Py_ssize_t size;
if (psize == NULL) {
size = strlen(data);
} else {
size = *psize;
if (size == 0) {
size = strlen(data);
*psize = size; // The size "return" value.
}
}
char *copied = PyMem_Malloc(size+1);
if (copied == NULL) {
PyErr_NoMemory();
return NULL;
}
if (dealloc != NULL) {
*dealloc = PyMem_Free;
}
memcpy(copied, data, size+1);
return copied;
}
static const char *
_pyobj_get_str_and_size(PyObject *obj, Py_ssize_t *psize)
{
if (PyUnicode_Check(obj)) {
return PyUnicode_AsUTF8AndSize(obj, psize);
} else {
const char *data = NULL;
PyBytes_AsStringAndSize(obj, (char **)&data, psize);
return data;
}
}
/* "raw" strings */
typedef struct _rawstring {
Py_ssize_t size;
const char *data;
_deallocfunc dealloc;
} _rawstring;
static void
_rawstring_init(_rawstring *raw)
{
raw->size = 0;
raw->data = NULL;
raw->dealloc = NULL;
}
static _rawstring *
_rawstring_new(void)
{
_rawstring *raw = PyMem_NEW(_rawstring, 1);
if (raw == NULL) {
PyErr_NoMemory();
return NULL;
}
_rawstring_init(raw);
return raw;
}
static void
_rawstring_clear(_rawstring *raw)
{
if (raw->data != NULL && raw->dealloc != NULL) {
(*raw->dealloc)((void *)raw->data);
}
_rawstring_init(raw);
}
static void
_rawstring_free(_rawstring *raw)
{
_rawstring_clear(raw);
PyMem_Free(raw);
}
static int
_rawstring_is_clear(_rawstring *raw)
{
return raw->size == 0 && raw->data == NULL && raw->dealloc == NULL;
}
//static void
//_rawstring_move(_rawstring *raw, _rawstring *src)
//{
// raw->size = src->size;
// raw->data = src->data;
// raw->dealloc = src->dealloc;
// _rawstring_init(src);
//}
static void
_rawstring_proxy(_rawstring *raw, const char *str)
{
if (str == NULL) {
str = "";
}
raw->size = strlen(str);
raw->data = str;
raw->dealloc = NULL;
}
static int
_rawstring_buffer(_rawstring *raw, Py_ssize_t size)
{
raw->data = PyMem_Malloc(size+1);
if (raw->data == NULL) {
PyErr_NoMemory();
return -1;
}
raw->size = size;
raw->dealloc = PyMem_Free;
return 0;
}
static int
_rawstring_strcpy(_rawstring *raw, const char *str, Py_ssize_t size)
{
_deallocfunc dealloc = NULL;
const char *copied = _strdup_and_size(str, &size, &dealloc);
if (copied == NULL) {
return -1;
}
raw->size = size;
raw->dealloc = dealloc;
raw->data = copied;
return 0;
}
static int
_rawstring_from_pyobj(_rawstring *raw, PyObject *obj)
{
Py_ssize_t size = 0;
const char *data = _pyobj_get_str_and_size(obj, &size);
if (PyErr_Occurred()) {
return -1;
}
if (_rawstring_strcpy(raw, data, size) != 0) {
return -1;
}
return 0;
}
static int
_rawstring_from_pyobj_attr(_rawstring *raw, PyObject *obj, const char *attr)
{
int res = -1;
PyObject *valueobj = PyObject_GetAttrString(obj, attr);
if (valueobj == NULL) {
goto done;
}
if (!PyUnicode_Check(valueobj)) {
// XXX PyObject_Str()? Repr()?
goto done;
}
const char *valuestr = PyUnicode_AsUTF8(valueobj);
if (valuestr == NULL) {
if (PyErr_Occurred()) {
goto done;
}
} else if (_rawstring_strcpy(raw, valuestr, 0) != 0) {
_rawstring_clear(raw);
goto done;
}
res = 0;
done:
Py_XDECREF(valueobj);
return res;
}
static PyObject *
_rawstring_as_pybytes(_rawstring *raw)
{
return PyBytes_FromStringAndSize(raw->data, raw->size);
}
/* object utils *************************************************************/
static void
_pyobj_identify_type(PyObject *obj, _rawstring *modname, _rawstring *clsname)
{
PyObject *objtype = (PyObject *)Py_TYPE(obj);
// Try __module__ and __name__.
if (_rawstring_from_pyobj_attr(modname, objtype, "__module__") != 0) {
// Fall back to the previous values in "modname".
IGNORE_FAILURE("bad __module__");
}
if (_rawstring_from_pyobj_attr(clsname, objtype, "__name__") != 0) {
// Fall back to the previous values in "clsname".
IGNORE_FAILURE("bad __name__");
}
// XXX Fall back to __qualname__?
// XXX Fall back to tp_name?
}
static PyObject *
_pyobj_get_class(const char *modname, const char *clsname)
{
assert(clsname != NULL);
if (modname == NULL) {
modname = "builtins";
}
PyObject *module = PyImport_ImportModule(modname);
if (module == NULL) {
return NULL;
}
PyObject *cls = PyObject_GetAttrString(module, clsname);
Py_DECREF(module);
return cls;
}
static PyObject *
_pyobj_create(const char *modname, const char *clsname, PyObject *arg)
{
PyObject *cls = _pyobj_get_class(modname, clsname);
if (cls == NULL) {
return NULL;
}
PyObject *obj = NULL;
if (arg == NULL) {
obj = _PyObject_CallNoArg(cls);
} else {
obj = PyObject_CallFunction(cls, "O", arg);
}
Py_DECREF(cls);
return obj;
}
/* object snapshots */
typedef struct _objsnapshot {
// If modname is NULL then try "builtins" and "__main__".
_rawstring modname;
// clsname is required.
_rawstring clsname;
// The rest are optional.
// The serialized exception.
_rawstring *serialized;
} _objsnapshot;
static void
_objsnapshot_init(_objsnapshot *osn)
{
_rawstring_init(&osn->modname);
_rawstring_init(&osn->clsname);
osn->serialized = NULL;
}
//static _objsnapshot *
//_objsnapshot_new(void)
//{
// _objsnapshot *osn = PyMem_NEW(_objsnapshot, 1);
// if (osn == NULL) {
// PyErr_NoMemory();
// return NULL;
// }
// _objsnapshot_init(osn);
// return osn;
//}
static void
_objsnapshot_clear(_objsnapshot *osn)
{
_rawstring_clear(&osn->modname);
_rawstring_clear(&osn->clsname);
if (osn->serialized != NULL) {
_rawstring_free(osn->serialized);
osn->serialized = NULL;
}
}
//static void
//_objsnapshot_free(_objsnapshot *osn)
//{
// _objsnapshot_clear(osn);
// PyMem_Free(osn);
//}
#ifndef NDEBUG
static int
_objsnapshot_is_clear(_objsnapshot *osn)
{
return osn->serialized == NULL
&& _rawstring_is_clear(&osn->modname)
&& _rawstring_is_clear(&osn->clsname);
}
#endif
static void
_objsnapshot_summarize(_objsnapshot *osn, _rawstring *rawbuf, const char *msg)
{
if (msg == NULL || *msg == '\0') {
// XXX Keep it NULL?
// XXX Keep it an empty string?
// XXX Use something more informative?
msg = "<no message>";
}
const char *clsname = osn->clsname.data;
const char *modname = osn->modname.data;
if (modname && *modname == '\0') {
modname = NULL;
}
// Prep the buffer.
Py_ssize_t size = strlen(clsname);
if (modname != NULL) {
if (strcmp(modname, "builtins") == 0) {
modname = NULL;
} else if (strcmp(modname, "__main__") == 0) {
modname = NULL;
} else {
size += strlen(modname) + 1;
}
}
if (msg != NULL) {
size += strlen(": ") + strlen(msg);
}
if (modname != NULL || msg != NULL) {
if (_rawstring_buffer(rawbuf, size) != 0) {
IGNORE_FAILURE("could not summarize object snapshot");
return;
}
}
// ...else we'll proxy clsname as-is, so no need to allocate a buffer.
// XXX Use __qualname__ somehow?
char *buf = (char *)rawbuf->data;
if (modname != NULL) {
if (msg != NULL) {
snprintf(buf, size+1, "%s.%s: %s", modname, clsname, msg);
} else {
snprintf(buf, size+1, "%s.%s", modname, clsname);
}
} else if (msg != NULL) {
snprintf(buf, size+1, "%s: %s", clsname, msg);
} else {
_rawstring_proxy(rawbuf, clsname);
}
}
static _rawstring *
_objsnapshot_get_minimal_summary(_objsnapshot *osn, PyObject *obj)
{
const char *str = NULL;
PyObject *objstr = PyObject_Str(obj);
if (objstr == NULL) {
PyErr_Clear();
} else {
str = PyUnicode_AsUTF8(objstr);
if (str == NULL) {
PyErr_Clear();
}
}
_rawstring *summary = _rawstring_new();
if (summary == NULL) {
return NULL;
}
_objsnapshot_summarize(osn, summary, str);
return summary;
}
static void
_objsnapshot_extract(_objsnapshot *osn, PyObject *obj)
{
assert(_objsnapshot_is_clear(osn));
// Get the "qualname".
_rawstring_proxy(&osn->modname, "<unknown>");
_rawstring_proxy(&osn->clsname, "<unknown>");
_pyobj_identify_type(obj, &osn->modname, &osn->clsname);
// Serialize the object.
// XXX Use marshal?
PyObject *pickle = PyImport_ImportModule("pickle");
if (pickle == NULL) {
IGNORE_FAILURE("could not serialize object: pickle import failed");
return;
}
PyObject *objdata = PyObject_CallMethod(pickle, "dumps", "(O)", obj);
Py_DECREF(pickle);
if (objdata == NULL) {
IGNORE_FAILURE("could not serialize object: pickle.dumps failed");
} else {
_rawstring *serialized = _rawstring_new();
int res = _rawstring_from_pyobj(serialized, objdata);
Py_DECREF(objdata);
if (res != 0) {
IGNORE_FAILURE("could not serialize object: raw str failed");
_rawstring_free(serialized);
} else if (serialized->size == 0) {
_rawstring_free(serialized);
} else {
osn->serialized = serialized;
}
}
}
static PyObject *
_objsnapshot_resolve_serialized(_objsnapshot *osn)
{
assert(osn->serialized != NULL);
// XXX Use marshal?
PyObject *pickle = PyImport_ImportModule("pickle");
if (pickle == NULL) {
return NULL;
}
PyObject *objdata = _rawstring_as_pybytes(osn->serialized);
if (objdata == NULL) {
return NULL;
} else {
PyObject *obj = PyObject_CallMethod(pickle, "loads", "O", objdata);
Py_DECREF(objdata);
return obj;
}
}
static PyObject *
_objsnapshot_resolve_naive(_objsnapshot *osn, PyObject *arg)
{
if (_rawstring_is_clear(&osn->clsname)) {
// We can't proceed without at least the class name.
PyErr_SetString(PyExc_ValueError, "missing class name");
return NULL;
}
if (osn->modname.data != NULL) {
return _pyobj_create(osn->modname.data, osn->clsname.data, arg);
} else {
PyObject *obj = _pyobj_create("builtins", osn->clsname.data, arg);
if (obj == NULL) {
PyErr_Clear();
obj = _pyobj_create("__main__", osn->clsname.data, arg);
}
return obj;
}
}
static PyObject *
_objsnapshot_resolve(_objsnapshot *osn)
{
if (osn->serialized != NULL) {
PyObject *obj = _objsnapshot_resolve_serialized(osn);
if (obj != NULL) {
return obj;
}
IGNORE_FAILURE("could not de-serialize object");
}
// Fall back to naive resolution.
return _objsnapshot_resolve_naive(osn, NULL);
}
/* exception utils **********************************************************/
// _pyexc_create is inspired by _PyErr_SetObject().
static PyObject *
_pyexc_create(PyObject *exctype, const char *msg, PyObject *tb)
{
assert(exctype != NULL && PyExceptionClass_Check(exctype));
PyObject *curtype = NULL, *curexc = NULL, *curtb = NULL;
PyErr_Fetch(&curtype, &curexc, &curtb);
// Create the object.
PyObject *exc = NULL;
if (msg != NULL) {
PyObject *msgobj = PyUnicode_FromString(msg);
if (msgobj == NULL) {
IGNORE_FAILURE("could not deserialize propagated error message");
}
exc = _PyObject_CallOneArg(exctype, msgobj);
Py_XDECREF(msgobj);
} else {
exc = _PyObject_CallNoArg(exctype);
}
if (exc == NULL) {
return NULL;
}
// Set the traceback, if any.
if (tb == NULL) {
tb = curtb;
}
if (tb != NULL) {
// This does *not* steal a reference!
PyException_SetTraceback(exc, tb);
}
PyErr_Restore(curtype, curexc, curtb);
return exc;
}
/* traceback snapshots */
typedef struct _tbsnapshot {
_rawstring tbs_funcname;
_rawstring tbs_filename;
int tbs_lineno;
struct _tbsnapshot *tbs_next;
} _tbsnapshot;
static void
_tbsnapshot_init(_tbsnapshot *tbs)
{
_rawstring_init(&tbs->tbs_funcname);
_rawstring_init(&tbs->tbs_filename);
tbs->tbs_lineno = -1;
tbs->tbs_next = NULL;
}
static _tbsnapshot *
_tbsnapshot_new(void)
{
_tbsnapshot *tbs = PyMem_NEW(_tbsnapshot, 1);
if (tbs == NULL) {
PyErr_NoMemory();
return NULL;
}
_tbsnapshot_init(tbs);
return tbs;
}
static void _tbsnapshot_free(_tbsnapshot *); // forward
static void
_tbsnapshot_clear(_tbsnapshot *tbs)
{
_rawstring_clear(&tbs->tbs_funcname);
_rawstring_clear(&tbs->tbs_filename);
tbs->tbs_lineno = -1;
if (tbs->tbs_next != NULL) {
_tbsnapshot_free(tbs->tbs_next);
tbs->tbs_next = NULL;
}
}
static void
_tbsnapshot_free(_tbsnapshot *tbs)
{
_tbsnapshot_clear(tbs);
PyMem_Free(tbs);
}
#ifndef NDEBUG
static int
_tbsnapshot_is_clear(_tbsnapshot *tbs)
{
return tbs->tbs_lineno == -1 && tbs->tbs_next == NULL
&& _rawstring_is_clear(&tbs->tbs_funcname)
&& _rawstring_is_clear(&tbs->tbs_filename);
}
#endif
static int
_tbsnapshot_from_pytb(_tbsnapshot *tbs, PyTracebackObject *pytb)
{
assert(_tbsnapshot_is_clear(tbs));
assert(pytb != NULL);
PyCodeObject *pycode = pytb->tb_frame->f_code;
const char *funcname = PyUnicode_AsUTF8(pycode->co_name);
if (_rawstring_strcpy(&tbs->tbs_funcname, funcname, 0) != 0) {
goto error;
}
const char *filename = PyUnicode_AsUTF8(pycode->co_filename);
if (_rawstring_strcpy(&tbs->tbs_filename, filename, 0) != 0) {
goto error;
}
tbs->tbs_lineno = pytb->tb_lineno;
return 0;
error:
_tbsnapshot_clear(tbs);
return -1;
}
static int
_tbsnapshot_extract(_tbsnapshot *tbs, PyTracebackObject *pytb)
{
assert(_tbsnapshot_is_clear(tbs));
assert(pytb != NULL);
_tbsnapshot *next = NULL;
while (pytb->tb_next != NULL) {
_tbsnapshot *_next = _tbsnapshot_new();
if (_next == NULL) {
goto error;
}
if (_tbsnapshot_from_pytb(_next, pytb) != 0) {
goto error;
}
if (next != NULL) {
_next->tbs_next = next;
}
next = _next;
pytb = pytb->tb_next;
}
if (_tbsnapshot_from_pytb(tbs, pytb) != 0) {
goto error;
}
tbs->tbs_next = next;
return 0;
error:
_tbsnapshot_clear(tbs);
return -1;
}
static PyObject *
_tbsnapshot_resolve(_tbsnapshot *tbs)
{
assert(!PyErr_Occurred());
// At this point there should be no traceback set yet.
while (tbs != NULL) {
const char *funcname = tbs->tbs_funcname.data;
const char *filename = tbs->tbs_filename.data;
_PyTraceback_Add(funcname ? funcname : "",
filename ? filename : "",
tbs->tbs_lineno);
tbs = tbs->tbs_next;
}
PyObject *exctype = NULL, *excval = NULL, *tb = NULL;
PyErr_Fetch(&exctype, &excval, &tb);
// Leave it cleared.
return tb;
}
/* exception snapshots */
typedef struct _excsnapshot {
_objsnapshot es_object;
_rawstring *es_msg;
struct _excsnapshot *es_cause;
struct _excsnapshot *es_context;
char es_suppress_context;
struct _tbsnapshot *es_traceback;
} _excsnapshot;
static void
_excsnapshot_init(_excsnapshot *es)
{
_objsnapshot_init(&es->es_object);
es->es_msg = NULL;
es->es_cause = NULL;
es->es_context = NULL;
es->es_suppress_context = 0;
es->es_traceback = NULL;
}
static _excsnapshot *
_excsnapshot_new(void) {
_excsnapshot *es = PyMem_NEW(_excsnapshot, 1);
if (es == NULL) {
PyErr_NoMemory();
return NULL;
}
_excsnapshot_init(es);
return es;
}
static void _excsnapshot_free(_excsnapshot *); // forward
static void
_excsnapshot_clear(_excsnapshot *es)
{
_objsnapshot_clear(&es->es_object);
if (es->es_msg != NULL) {
_rawstring_free(es->es_msg);
es->es_msg = NULL;
}
if (es->es_cause != NULL) {
_excsnapshot_free(es->es_cause);
es->es_cause = NULL;
}
if (es->es_context != NULL) {
_excsnapshot_free(es->es_context);
es->es_context = NULL;
}
es->es_suppress_context = 0;
if (es->es_traceback != NULL) {
_tbsnapshot_free(es->es_traceback);
es->es_traceback = NULL;
}
}
static void
_excsnapshot_free(_excsnapshot *es)
{
_excsnapshot_clear(es);
PyMem_Free(es);
}
#ifndef NDEBUG
static int
_excsnapshot_is_clear(_excsnapshot *es)
{
return es->es_suppress_context == 0
&& es->es_cause == NULL
&& es->es_context == NULL
&& es->es_traceback == NULL
&& es->es_msg == NULL
&& _objsnapshot_is_clear(&es->es_object);
}
#endif
static PyObject *
_excsnapshot_get_exc_naive(_excsnapshot *es)
{
_rawstring buf;
const char *msg = NULL;
if (es->es_msg != NULL) {
msg = es->es_msg->data;
} else {
_objsnapshot_summarize(&es->es_object, &buf, NULL);
if (buf.size > 0) {
msg = buf.data;
}
}
PyObject *exc = NULL;
// XXX Use _objsnapshot_resolve_naive()?
const char *modname = es->es_object.modname.size > 0
? es->es_object.modname.data
: NULL;
PyObject *exctype = _pyobj_get_class(modname, es->es_object.clsname.data);
if (exctype != NULL) {
exc = _pyexc_create(exctype, msg, NULL);
Py_DECREF(exctype);
if (exc != NULL) {
return exc;
}
PyErr_Clear();
} else {
PyErr_Clear();
}
exctype = PyExc_Exception;
return _pyexc_create(exctype, msg, NULL);
}
static PyObject *
_excsnapshot_get_exc(_excsnapshot *es)
{
assert(!_objsnapshot_is_clear(&es->es_object));
PyObject *exc = _objsnapshot_resolve(&es->es_object);
if (exc == NULL) {
// Fall back to resolving the object.
PyObject *curtype = NULL, *curexc = NULL, *curtb = NULL;
PyErr_Fetch(&curtype, &curexc, &curtb);
exc = _excsnapshot_get_exc_naive(es);
if (exc == NULL) {
PyErr_Restore(curtype, curexc, curtb);
return NULL;
}
}
// People can do some weird stuff...
if (!PyExceptionInstance_Check(exc)) {
// We got a bogus "exception".
Py_DECREF(exc);
PyErr_SetString(PyExc_TypeError, "expected exception");
return NULL;
}
return exc;
}
static void _excsnapshot_extract(_excsnapshot *, PyObject *);
static void
_excsnapshot_extract(_excsnapshot *es, PyObject *excobj)
{
assert(_excsnapshot_is_clear(es));
assert(PyExceptionInstance_Check(excobj));
_objsnapshot_extract(&es->es_object, excobj);
es->es_msg = _objsnapshot_get_minimal_summary(&es->es_object, excobj);
if (es->es_msg == NULL) {
PyErr_Clear();
}
PyBaseExceptionObject *exc = (PyBaseExceptionObject *)excobj;
if (exc->cause != NULL && exc->cause != Py_None) {
es->es_cause = _excsnapshot_new();
_excsnapshot_extract(es->es_cause, exc->cause);
}
if (exc->context != NULL && exc->context != Py_None) {
es->es_context = _excsnapshot_new();
_excsnapshot_extract(es->es_context, exc->context);
}
es->es_suppress_context = exc->suppress_context;
PyObject *tb = PyException_GetTraceback(excobj);
if (PyErr_Occurred()) {
IGNORE_FAILURE("could not get traceback");
} else if (tb == Py_None) {
Py_DECREF(tb);
tb = NULL;
}
if (tb != NULL) {
es->es_traceback = _tbsnapshot_new();
if (_tbsnapshot_extract(es->es_traceback,
(PyTracebackObject *)tb) != 0) {
IGNORE_FAILURE("could not extract __traceback__");
}
}
}
static PyObject *
_excsnapshot_resolve(_excsnapshot *es)
{
PyObject *exc = _excsnapshot_get_exc(es);
if (exc == NULL) {
return NULL;
}
if (es->es_traceback != NULL) {
PyObject *tb = _tbsnapshot_resolve(es->es_traceback);
if (tb == NULL) {
// The snapshot is still somewhat useful without this.
IGNORE_FAILURE("could not deserialize traceback");
} else {
// This does not steal references.
PyException_SetTraceback(exc, tb);
Py_DECREF(tb);
}
}
// NULL means "not set".
if (es->es_context != NULL) {
PyObject *context = _excsnapshot_resolve(es->es_context);
if (context == NULL) {
// The snapshot is still useful without this.
IGNORE_FAILURE("could not deserialize __context__");
} else {
// This steals references but we have one to give.
PyException_SetContext(exc, context);
}
}
// NULL means "not set".
if (es->es_cause != NULL) {
PyObject *cause = _excsnapshot_resolve(es->es_cause);
if (cause == NULL) {
// The snapshot is still useful without this.
IGNORE_FAILURE("could not deserialize __cause__");
} else {
// This steals references, but we have one to give.
PyException_SetCause(exc, cause);
}
}
// NULL means "not set".
((PyBaseExceptionObject *)exc)->suppress_context = es->es_suppress_context;
return exc;
}
/* data-sharing-specific code ***********************************************/
/* shared "object" */
struct _sharednsitem {
_rawstring name;
_PyCrossInterpreterData data;
};
static void _sharednsitem_clear(struct _sharednsitem *); // forward
static int
_sharednsitem_init(struct _sharednsitem *item, PyObject *key, PyObject *value)
{
if (_rawstring_from_pyobj(&item->name, key) != 0) {
return -1;
}
if (_PyObject_GetCrossInterpreterData(value, &item->data) != 0) {
_sharednsitem_clear(item);
return -1;
}
return 0;
}
static void
_sharednsitem_clear(struct _sharednsitem *item)
{
_rawstring_clear(&item->name);
_PyCrossInterpreterData_Release(&item->data);
}
static int
_sharednsitem_apply(struct _sharednsitem *item, PyObject *ns)
{
PyObject *name = PyUnicode_FromString(item->name.data);
if (name == NULL) {
return -1;
}
PyObject *value = _PyCrossInterpreterData_NewObject(&item->data);
if (value == NULL) {
Py_DECREF(name);
return -1;
}
int res = PyDict_SetItem(ns, name, value);
Py_DECREF(name);
Py_DECREF(value);
return res;
}
typedef struct _sharedns {
Py_ssize_t len;
struct _sharednsitem* items;
} _sharedns;
static _sharedns *
_sharedns_new(Py_ssize_t len)
{
_sharedns *shared = PyMem_NEW(_sharedns, 1);
if (shared == NULL) {
PyErr_NoMemory();
return NULL;
}
shared->len = len;
shared->items = PyMem_NEW(struct _sharednsitem, len);
if (shared->items == NULL) {
PyErr_NoMemory();
PyMem_Free(shared);
return NULL;
}
return shared;
}
static void
_sharedns_free(_sharedns *shared)
{
for (Py_ssize_t i=0; i < shared->len; i++) {
_sharednsitem_clear(&shared->items[i]);
}
PyMem_Free(shared->items);
PyMem_Free(shared);
}
static _sharedns *
_get_shared_ns(PyObject *shareable)
{
if (shareable == NULL || shareable == Py_None) {
return NULL;
}
Py_ssize_t len = PyDict_Size(shareable);
if (len == 0) {
return NULL;
}
_sharedns *shared = _sharedns_new(len);
if (shared == NULL) {
return NULL;
}
Py_ssize_t pos = 0;
for (Py_ssize_t i=0; i < len; i++) {
PyObject *key, *value;
if (PyDict_Next(shareable, &pos, &key, &value) == 0) {
break;
}
if (_sharednsitem_init(&shared->items[i], key, value) != 0) {
break;
}
}
if (PyErr_Occurred()) {
_sharedns_free(shared);
return NULL;
}
return shared;
}
static int
_sharedns_apply(_sharedns *shared, PyObject *ns)
{
for (Py_ssize_t i=0; i < shared->len; i++) {
if (_sharednsitem_apply(&shared->items[i], ns) != 0) {
return -1;
}
}
return 0;
}
/* shared exception */
// Ultimately we'd like to preserve enough information about the
// exception and traceback that we could re-constitute (or at least
// simulate, a la traceback.TracebackException), and even chain, a copy
// of the exception in the calling interpreter.
typedef struct _sharedexception {
_excsnapshot snapshot;
_rawstring msg;
} _sharedexception;
static void
_sharedexception_init(_sharedexception *she)
{
_excsnapshot_init(&she->snapshot);
_rawstring_init(&she->msg);
}
static _sharedexception *
_sharedexception_new(void)
{
_sharedexception *she = PyMem_NEW(_sharedexception, 1);
if (she == NULL) {
PyErr_NoMemory();
return NULL;
}
_sharedexception_init(she);
return she;
}
static void
_sharedexception_clear(_sharedexception *she)
{
_excsnapshot_clear(&she->snapshot);
_rawstring_clear(&she->msg);
}
static void
_sharedexception_free(_sharedexception *she)
{
_sharedexception_clear(she);
PyMem_Free(she);
}
#ifndef NDEBUG
static int
_sharedexception_is_clear(_sharedexception *she)
{
return 1
&& _excsnapshot_is_clear(&she->snapshot)
&& _rawstring_is_clear(&she->msg);
}
#endif
static PyObject *
_sharedexception_get_cause(_sharedexception *sharedexc)
{
// FYI, "cause" is already normalized.
PyObject *cause = _excsnapshot_resolve(&sharedexc->snapshot);
if (cause == NULL) {
if (PyErr_Occurred()) {
IGNORE_FAILURE("could not deserialize exc snapshot");
}
return NULL;
}
// XXX Ensure "cause" has a traceback.
return cause;
}
static void
_sharedexception_extract(_sharedexception *she, PyObject *exc)
{
assert(_sharedexception_is_clear(she));
assert(exc != NULL);
_excsnapshot_extract(&she->snapshot, exc);
// Compose the message.
const char *msg = NULL;
PyObject *msgobj = PyUnicode_FromFormat("%S", exc);
if (msgobj == NULL) {
IGNORE_FAILURE("unable to format exception message");
} else {
msg = PyUnicode_AsUTF8(msgobj);
if (PyErr_Occurred()) {
PyErr_Clear();
}
}
_objsnapshot_summarize(&she->snapshot.es_object, &she->msg, msg);
Py_XDECREF(msgobj);
}
static PyObject *
_sharedexception_resolve(_sharedexception *sharedexc, PyObject *wrapperclass)
{
assert(!PyErr_Occurred());
// Get the exception object (already normalized).
PyObject *exc = _pyexc_create(wrapperclass, sharedexc->msg.data, NULL);
assert(exc != NULL);
// Set __cause__, is possible.
PyObject *cause = _sharedexception_get_cause(sharedexc);
if (cause != NULL) {
// Set __context__.
Py_INCREF(cause); // PyException_SetContext() steals a reference.
PyException_SetContext(exc, cause);
// Set __cause__.
Py_INCREF(cause); // PyException_SetCause() steals a reference.
PyException_SetCause(exc, cause);
}
return exc;
}
/* channel-specific code ****************************************************/
#define CHANNEL_SEND 1
#define CHANNEL_BOTH 0
#define CHANNEL_RECV -1
static PyObject *ChannelError;
static PyObject *ChannelNotFoundError;
static PyObject *ChannelClosedError;
static PyObject *ChannelEmptyError;
static PyObject *ChannelNotEmptyError;
static int
channel_exceptions_init(PyObject *ns)
{
// XXX Move the exceptions into per-module memory?
// A channel-related operation failed.
ChannelError = PyErr_NewException("_xxsubinterpreters.ChannelError",
PyExc_RuntimeError, NULL);
if (ChannelError == NULL) {
return -1;
}
if (PyDict_SetItemString(ns, "ChannelError", ChannelError) != 0) {
return -1;
}
// An operation tried to use a channel that doesn't exist.
ChannelNotFoundError = PyErr_NewException(
"_xxsubinterpreters.ChannelNotFoundError", ChannelError, NULL);
if (ChannelNotFoundError == NULL) {
return -1;
}
if (PyDict_SetItemString(ns, "ChannelNotFoundError", ChannelNotFoundError) != 0) {
return -1;
}
// An operation tried to use a closed channel.
ChannelClosedError = PyErr_NewException(
"_xxsubinterpreters.ChannelClosedError", ChannelError, NULL);
if (ChannelClosedError == NULL) {
return -1;
}
if (PyDict_SetItemString(ns, "ChannelClosedError", ChannelClosedError) != 0) {
return -1;
}
// An operation tried to pop from an empty channel.
ChannelEmptyError = PyErr_NewException(
"_xxsubinterpreters.ChannelEmptyError", ChannelError, NULL);
if (ChannelEmptyError == NULL) {
return -1;
}
if (PyDict_SetItemString(ns, "ChannelEmptyError", ChannelEmptyError) != 0) {
return -1;
}
// An operation tried to close a non-empty channel.
ChannelNotEmptyError = PyErr_NewException(
"_xxsubinterpreters.ChannelNotEmptyError", ChannelError, NULL);
if (ChannelNotEmptyError == NULL) {
return -1;
}
if (PyDict_SetItemString(ns, "ChannelNotEmptyError", ChannelNotEmptyError) != 0) {
return -1;
}
return 0;
}
/* the channel queue */
struct _channelitem;
typedef struct _channelitem {
_PyCrossInterpreterData *data;
struct _channelitem *next;
} _channelitem;
static _channelitem *
_channelitem_new(void)
{
_channelitem *item = PyMem_NEW(_channelitem, 1);
if (item == NULL) {
PyErr_NoMemory();
return NULL;
}
item->data = NULL;
item->next = NULL;
return item;
}
static void
_channelitem_clear(_channelitem *item)
{
if (item->data != NULL) {
_PyCrossInterpreterData_Release(item->data);
PyMem_Free(item->data);
item->data = NULL;
}
item->next = NULL;
}
static void
_channelitem_free(_channelitem *item)
{
_channelitem_clear(item);
PyMem_Free(item);
}
static void
_channelitem_free_all(_channelitem *item)
{
while (item != NULL) {
_channelitem *last = item;
item = item->next;
_channelitem_free(last);
}
}
static _PyCrossInterpreterData *
_channelitem_popped(_channelitem *item)
{
_PyCrossInterpreterData *data = item->data;
item->data = NULL;
_channelitem_free(item);
return data;
}
typedef struct _channelqueue {
int64_t count;
_channelitem *first;
_channelitem *last;
} _channelqueue;
static _channelqueue *
_channelqueue_new(void)
{
_channelqueue *queue = PyMem_NEW(_channelqueue, 1);
if (queue == NULL) {
PyErr_NoMemory();
return NULL;
}
queue->count = 0;
queue->first = NULL;
queue->last = NULL;
return queue;
}
static void
_channelqueue_clear(_channelqueue *queue)
{
_channelitem_free_all(queue->first);
queue->count = 0;
queue->first = NULL;
queue->last = NULL;
}
static void
_channelqueue_free(_channelqueue *queue)
{
_channelqueue_clear(queue);
PyMem_Free(queue);
}
static int
_channelqueue_put(_channelqueue *queue, _PyCrossInterpreterData *data)
{
_channelitem *item = _channelitem_new();
if (item == NULL) {
return -1;
}
item->data = data;
queue->count += 1;
if (queue->first == NULL) {
queue->first = item;
}
else {
queue->last->next = item;
}
queue->last = item;
return 0;
}
static _PyCrossInterpreterData *
_channelqueue_get(_channelqueue *queue)
{
_channelitem *item = queue->first;
if (item == NULL) {
return NULL;
}
queue->first = item->next;
if (queue->last == item) {
queue->last = NULL;
}
queue->count -= 1;
return _channelitem_popped(item);
}
/* channel-interpreter associations */
struct _channelend;
typedef struct _channelend {
struct _channelend *next;
int64_t interp;
int open;
} _channelend;
static _channelend *
_channelend_new(int64_t interp)
{
_channelend *end = PyMem_NEW(_channelend, 1);
if (end == NULL) {
PyErr_NoMemory();
return NULL;
}
end->next = NULL;
end->interp = interp;
end->open = 1;
return end;
}
static void
_channelend_free(_channelend *end)
{
PyMem_Free(end);
}
static void
_channelend_free_all(_channelend *end)
{
while (end != NULL) {
_channelend *last = end;
end = end->next;
_channelend_free(last);
}
}
static _channelend *
_channelend_find(_channelend *first, int64_t interp, _channelend **pprev)
{
_channelend *prev = NULL;
_channelend *end = first;
while (end != NULL) {
if (end->interp == interp) {
break;
}
prev = end;
end = end->next;
}
if (pprev != NULL) {
*pprev = prev;
}
return end;
}
typedef struct _channelassociations {
// Note that the list entries are never removed for interpreter
// for which the channel is closed. This should not be a problem in
// practice. Also, a channel isn't automatically closed when an
// interpreter is destroyed.
int64_t numsendopen;
int64_t numrecvopen;
_channelend *send;
_channelend *recv;
} _channelends;
static _channelends *
_channelends_new(void)
{
_channelends *ends = PyMem_NEW(_channelends, 1);
if (ends== NULL) {
return NULL;
}
ends->numsendopen = 0;
ends->numrecvopen = 0;
ends->send = NULL;
ends->recv = NULL;
return ends;
}
static void
_channelends_clear(_channelends *ends)
{
_channelend_free_all(ends->send);
ends->send = NULL;
ends->numsendopen = 0;
_channelend_free_all(ends->recv);
ends->recv = NULL;
ends->numrecvopen = 0;
}
static void
_channelends_free(_channelends *ends)
{
_channelends_clear(ends);
PyMem_Free(ends);
}
static _channelend *
_channelends_add(_channelends *ends, _channelend *prev, int64_t interp,
int send)
{
_channelend *end = _channelend_new(interp);
if (end == NULL) {
return NULL;
}
if (prev == NULL) {
if (send) {
ends->send = end;
}
else {
ends->recv = end;
}
}
else {
prev->next = end;
}
if (send) {
ends->numsendopen += 1;
}
else {
ends->numrecvopen += 1;
}
return end;
}
static int
_channelends_associate(_channelends *ends, int64_t interp, int send)
{
_channelend *prev;
_channelend *end = _channelend_find(send ? ends->send : ends->recv,
interp, &prev);
if (end != NULL) {
if (!end->open) {
PyErr_SetString(ChannelClosedError, "channel already closed");
return -1;
}
// already associated
return 0;
}
if (_channelends_add(ends, prev, interp, send) == NULL) {
return -1;
}
return 0;
}
static int
_channelends_is_open(_channelends *ends)
{
if (ends->numsendopen != 0 || ends->numrecvopen != 0) {
return 1;
}
if (ends->send == NULL && ends->recv == NULL) {
return 1;
}
return 0;
}
static void
_channelends_close_end(_channelends *ends, _channelend *end, int send)
{
end->open = 0;
if (send) {
ends->numsendopen -= 1;
}
else {
ends->numrecvopen -= 1;
}
}
static int
_channelends_close_interpreter(_channelends *ends, int64_t interp, int which)
{
_channelend *prev;
_channelend *end;
if (which >= 0) { // send/both
end = _channelend_find(ends->send, interp, &prev);
if (end == NULL) {
// never associated so add it
end = _channelends_add(ends, prev, interp, 1);
if (end == NULL) {
return -1;
}
}
_channelends_close_end(ends, end, 1);
}
if (which <= 0) { // recv/both
end = _channelend_find(ends->recv, interp, &prev);
if (end == NULL) {
// never associated so add it
end = _channelends_add(ends, prev, interp, 0);
if (end == NULL) {
return -1;
}
}
_channelends_close_end(ends, end, 0);
}
return 0;
}
static void
_channelends_close_all(_channelends *ends, int which, int force)
{
// XXX Handle the ends.
// XXX Handle force is True.
// Ensure all the "send"-associated interpreters are closed.
_channelend *end;
for (end = ends->send; end != NULL; end = end->next) {
_channelends_close_end(ends, end, 1);
}
// Ensure all the "recv"-associated interpreters are closed.
for (end = ends->recv; end != NULL; end = end->next) {
_channelends_close_end(ends, end, 0);
}
}
/* channels */
struct _channel;
struct _channel_closing;
static void _channel_clear_closing(struct _channel *);
static void _channel_finish_closing(struct _channel *);
typedef struct _channel {
PyThread_type_lock mutex;
_channelqueue *queue;
_channelends *ends;
int open;
struct _channel_closing *closing;
} _PyChannelState;
static _PyChannelState *
_channel_new(void)
{
_PyChannelState *chan = PyMem_NEW(_PyChannelState, 1);
if (chan == NULL) {
return NULL;
}
chan->mutex = PyThread_allocate_lock();
if (chan->mutex == NULL) {
PyMem_Free(chan);
PyErr_SetString(ChannelError,
"can't initialize mutex for new channel");
return NULL;
}
chan->queue = _channelqueue_new();
if (chan->queue == NULL) {
PyMem_Free(chan);
return NULL;
}
chan->ends = _channelends_new();
if (chan->ends == NULL) {
_channelqueue_free(chan->queue);
PyMem_Free(chan);
return NULL;
}
chan->open = 1;
chan->closing = NULL;
return chan;
}
static void
_channel_free(_PyChannelState *chan)
{
_channel_clear_closing(chan);
PyThread_acquire_lock(chan->mutex, WAIT_LOCK);
_channelqueue_free(chan->queue);
_channelends_free(chan->ends);
PyThread_release_lock(chan->mutex);
PyThread_free_lock(chan->mutex);
PyMem_Free(chan);
}
static int
_channel_add(_PyChannelState *chan, int64_t interp,
_PyCrossInterpreterData *data)
{
int res = -1;
PyThread_acquire_lock(chan->mutex, WAIT_LOCK);
if (!chan->open) {
PyErr_SetString(ChannelClosedError, "channel closed");
goto done;
}
if (_channelends_associate(chan->ends, interp, 1) != 0) {
goto done;
}
if (_channelqueue_put(chan->queue, data) != 0) {
goto done;
}
res = 0;
done:
PyThread_release_lock(chan->mutex);
return res;
}
static _PyCrossInterpreterData *
_channel_next(_PyChannelState *chan, int64_t interp)
{
_PyCrossInterpreterData *data = NULL;
PyThread_acquire_lock(chan->mutex, WAIT_LOCK);
if (!chan->open) {
PyErr_SetString(ChannelClosedError, "channel closed");
goto done;
}
if (_channelends_associate(chan->ends, interp, 0) != 0) {
goto done;
}
data = _channelqueue_get(chan->queue);
if (data == NULL && !PyErr_Occurred() && chan->closing != NULL) {
chan->open = 0;
}
done:
PyThread_release_lock(chan->mutex);
if (chan->queue->count == 0) {
_channel_finish_closing(chan);
}
return data;
}
static int
_channel_close_interpreter(_PyChannelState *chan, int64_t interp, int end)
{
PyThread_acquire_lock(chan->mutex, WAIT_LOCK);
int res = -1;
if (!chan->open) {
PyErr_SetString(ChannelClosedError, "channel already closed");
goto done;
}
if (_channelends_close_interpreter(chan->ends, interp, end) != 0) {
goto done;
}
chan->open = _channelends_is_open(chan->ends);
res = 0;
done:
PyThread_release_lock(chan->mutex);
return res;
}
static int
_channel_close_all(_PyChannelState *chan, int end, int force)
{
int res = -1;
PyThread_acquire_lock(chan->mutex, WAIT_LOCK);
if (!chan->open) {
PyErr_SetString(ChannelClosedError, "channel already closed");
goto done;
}
if (!force && chan->queue->count > 0) {
PyErr_SetString(ChannelNotEmptyError,
"may not be closed if not empty (try force=True)");
goto done;
}
chan->open = 0;
// We *could* also just leave these in place, since we've marked
// the channel as closed already.
_channelends_close_all(chan->ends, end, force);
res = 0;
done:
PyThread_release_lock(chan->mutex);
return res;
}
/* the set of channels */
struct _channelref;
typedef struct _channelref {
int64_t id;
_PyChannelState *chan;
struct _channelref *next;
Py_ssize_t objcount;
} _channelref;
static _channelref *
_channelref_new(int64_t id, _PyChannelState *chan)
{
_channelref *ref = PyMem_NEW(_channelref, 1);
if (ref == NULL) {
return NULL;
}
ref->id = id;
ref->chan = chan;
ref->next = NULL;
ref->objcount = 0;
return ref;
}
//static void
//_channelref_clear(_channelref *ref)
//{
// ref->id = -1;
// ref->chan = NULL;
// ref->next = NULL;
// ref->objcount = 0;
//}
static void
_channelref_free(_channelref *ref)
{
if (ref->chan != NULL) {
_channel_clear_closing(ref->chan);
}
//_channelref_clear(ref);
PyMem_Free(ref);
}
static _channelref *
_channelref_find(_channelref *first, int64_t id, _channelref **pprev)
{
_channelref *prev = NULL;
_channelref *ref = first;
while (ref != NULL) {
if (ref->id == id) {
break;
}
prev = ref;
ref = ref->next;
}
if (pprev != NULL) {
*pprev = prev;
}
return ref;
}
typedef struct _channels {
PyThread_type_lock mutex;
_channelref *head;
int64_t numopen;
int64_t next_id;
} _channels;
static int
_channels_init(_channels *channels)
{
if (channels->mutex == NULL) {
channels->mutex = PyThread_allocate_lock();
if (channels->mutex == NULL) {
PyErr_SetString(ChannelError,
"can't initialize mutex for channel management");
return -1;
}
}
channels->head = NULL;
channels->numopen = 0;
channels->next_id = 0;
return 0;
}
static int64_t
_channels_next_id(_channels *channels) // needs lock
{
int64_t id = channels->next_id;
if (id < 0) {
/* overflow */
PyErr_SetString(ChannelError,
"failed to get a channel ID");
return -1;
}
channels->next_id += 1;
return id;
}
static _PyChannelState *
_channels_lookup(_channels *channels, int64_t id, PyThread_type_lock *pmutex)
{
_PyChannelState *chan = NULL;
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
if (pmutex != NULL) {
*pmutex = NULL;
}
_channelref *ref = _channelref_find(channels->head, id, NULL);
if (ref == NULL) {
PyErr_Format(ChannelNotFoundError, "channel %" PRId64 " not found", id);
goto done;
}
if (ref->chan == NULL || !ref->chan->open) {
PyErr_Format(ChannelClosedError, "channel %" PRId64 " closed", id);
goto done;
}
if (pmutex != NULL) {
// The mutex will be closed by the caller.
*pmutex = channels->mutex;
}
chan = ref->chan;
done:
if (pmutex == NULL || *pmutex == NULL) {
PyThread_release_lock(channels->mutex);
}
return chan;
}
static int64_t
_channels_add(_channels *channels, _PyChannelState *chan)
{
int64_t cid = -1;
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
// Create a new ref.
int64_t id = _channels_next_id(channels);
if (id < 0) {
goto done;
}
_channelref *ref = _channelref_new(id, chan);
if (ref == NULL) {
goto done;
}
// Add it to the list.
// We assume that the channel is a new one (not already in the list).
ref->next = channels->head;
channels->head = ref;
channels->numopen += 1;
cid = id;
done:
PyThread_release_lock(channels->mutex);
return cid;
}
/* forward */
static int _channel_set_closing(struct _channelref *, PyThread_type_lock);
static int
_channels_close(_channels *channels, int64_t cid, _PyChannelState **pchan,
int end, int force)
{
int res = -1;
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
if (pchan != NULL) {
*pchan = NULL;
}
_channelref *ref = _channelref_find(channels->head, cid, NULL);
if (ref == NULL) {
PyErr_Format(ChannelNotFoundError, "channel %" PRId64 " not found", cid);
goto done;
}
if (ref->chan == NULL) {
PyErr_Format(ChannelClosedError, "channel %" PRId64 " closed", cid);
goto done;
}
else if (!force && end == CHANNEL_SEND && ref->chan->closing != NULL) {
PyErr_Format(ChannelClosedError, "channel %" PRId64 " closed", cid);
goto done;
}
else {
if (_channel_close_all(ref->chan, end, force) != 0) {
if (end == CHANNEL_SEND &&
PyErr_ExceptionMatches(ChannelNotEmptyError)) {
if (ref->chan->closing != NULL) {
PyErr_Format(ChannelClosedError,
"channel %" PRId64 " closed", cid);
goto done;
}
// Mark the channel as closing and return. The channel
// will be cleaned up in _channel_next().
PyErr_Clear();
if (_channel_set_closing(ref, channels->mutex) != 0) {
goto done;
}
if (pchan != NULL) {
*pchan = ref->chan;
}
res = 0;
}
goto done;
}
if (pchan != NULL) {
*pchan = ref->chan;
}
else {
_channel_free(ref->chan);
}
ref->chan = NULL;
}
res = 0;
done:
PyThread_release_lock(channels->mutex);
return res;
}
static void
_channels_remove_ref(_channels *channels, _channelref *ref, _channelref *prev,
_PyChannelState **pchan)
{
if (ref == channels->head) {
channels->head = ref->next;
}
else {
prev->next = ref->next;
}
channels->numopen -= 1;
if (pchan != NULL) {
*pchan = ref->chan;
}
_channelref_free(ref);
}
static int
_channels_remove(_channels *channels, int64_t id, _PyChannelState **pchan)
{
int res = -1;
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
if (pchan != NULL) {
*pchan = NULL;
}
_channelref *prev = NULL;
_channelref *ref = _channelref_find(channels->head, id, &prev);
if (ref == NULL) {
PyErr_Format(ChannelNotFoundError, "channel %" PRId64 " not found", id);
goto done;
}
_channels_remove_ref(channels, ref, prev, pchan);
res = 0;
done:
PyThread_release_lock(channels->mutex);
return res;
}
static int
_channels_add_id_object(_channels *channels, int64_t id)
{
int res = -1;
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
_channelref *ref = _channelref_find(channels->head, id, NULL);
if (ref == NULL) {
PyErr_Format(ChannelNotFoundError, "channel %" PRId64 " not found", id);
goto done;
}
ref->objcount += 1;
res = 0;
done:
PyThread_release_lock(channels->mutex);
return res;
}
static void
_channels_drop_id_object(_channels *channels, int64_t id)
{
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
_channelref *prev = NULL;
_channelref *ref = _channelref_find(channels->head, id, &prev);
if (ref == NULL) {
// Already destroyed.
goto done;
}
ref->objcount -= 1;
// Destroy if no longer used.
if (ref->objcount == 0) {
_PyChannelState *chan = NULL;
_channels_remove_ref(channels, ref, prev, &chan);
if (chan != NULL) {
_channel_free(chan);
}
}
done:
PyThread_release_lock(channels->mutex);
}
static int64_t *
_channels_list_all(_channels *channels, int64_t *count)
{
int64_t *cids = NULL;
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
int64_t *ids = PyMem_NEW(int64_t, (Py_ssize_t)(channels->numopen));
if (ids == NULL) {
goto done;
}
_channelref *ref = channels->head;
for (int64_t i=0; ref != NULL; ref = ref->next, i++) {
ids[i] = ref->id;
}
*count = channels->numopen;
cids = ids;
done:
PyThread_release_lock(channels->mutex);
return cids;
}
/* support for closing non-empty channels */
struct _channel_closing {
struct _channelref *ref;
};
static int
_channel_set_closing(struct _channelref *ref, PyThread_type_lock mutex) {
struct _channel *chan = ref->chan;
if (chan == NULL) {
// already closed
return 0;
}
int res = -1;
PyThread_acquire_lock(chan->mutex, WAIT_LOCK);
if (chan->closing != NULL) {
PyErr_SetString(ChannelClosedError, "channel closed");
goto done;
}
chan->closing = PyMem_NEW(struct _channel_closing, 1);
if (chan->closing == NULL) {
goto done;
}
chan->closing->ref = ref;
res = 0;
done:
PyThread_release_lock(chan->mutex);
return res;
}
static void
_channel_clear_closing(struct _channel *chan) {
PyThread_acquire_lock(chan->mutex, WAIT_LOCK);
if (chan->closing != NULL) {
PyMem_Free(chan->closing);
chan->closing = NULL;
}
PyThread_release_lock(chan->mutex);
}
static void
_channel_finish_closing(struct _channel *chan) {
struct _channel_closing *closing = chan->closing;
if (closing == NULL) {
return;
}
_channelref *ref = closing->ref;
_channel_clear_closing(chan);
// Do the things that would have been done in _channels_close().
ref->chan = NULL;
_channel_free(chan);
}
/* "high"-level channel-related functions */
static int64_t
_channel_create(_channels *channels)
{
_PyChannelState *chan = _channel_new();
if (chan == NULL) {
return -1;
}
int64_t id = _channels_add(channels, chan);
if (id < 0) {
_channel_free(chan);
return -1;
}
return id;
}
static int
_channel_destroy(_channels *channels, int64_t id)
{
_PyChannelState *chan = NULL;
if (_channels_remove(channels, id, &chan) != 0) {
return -1;
}
if (chan != NULL) {
_channel_free(chan);
}
return 0;
}
static int
_channel_send(_channels *channels, int64_t id, PyObject *obj)
{
PyInterpreterState *interp = _get_current();
if (interp == NULL) {
return -1;
}
// Look up the channel.
PyThread_type_lock mutex = NULL;
_PyChannelState *chan = _channels_lookup(channels, id, &mutex);
if (chan == NULL) {
return -1;
}
// Past this point we are responsible for releasing the mutex.
if (chan->closing != NULL) {
PyErr_Format(ChannelClosedError, "channel %" PRId64 " closed", id);
PyThread_release_lock(mutex);
return -1;
}
// Convert the object to cross-interpreter data.
_PyCrossInterpreterData *data = PyMem_NEW(_PyCrossInterpreterData, 1);
if (data == NULL) {
PyThread_release_lock(mutex);
return -1;
}
if (_PyObject_GetCrossInterpreterData(obj, data) != 0) {
PyThread_release_lock(mutex);
PyMem_Free(data);
return -1;
}
// Add the data to the channel.
int res = _channel_add(chan, PyInterpreterState_GetID(interp), data);
PyThread_release_lock(mutex);
if (res != 0) {
_PyCrossInterpreterData_Release(data);
PyMem_Free(data);
return -1;
}
return 0;
}
static PyObject *
_channel_recv(_channels *channels, int64_t id)
{
PyInterpreterState *interp = _get_current();
if (interp == NULL) {
return NULL;
}
// Look up the channel.
PyThread_type_lock mutex = NULL;
_PyChannelState *chan = _channels_lookup(channels, id, &mutex);
if (chan == NULL) {
return NULL;
}
// Past this point we are responsible for releasing the mutex.
// Pop off the next item from the channel.
_PyCrossInterpreterData *data = _channel_next(chan, PyInterpreterState_GetID(interp));
PyThread_release_lock(mutex);
if (data == NULL) {
return NULL;
}
// Convert the data back to an object.
PyObject *obj = _PyCrossInterpreterData_NewObject(data);
_PyCrossInterpreterData_Release(data);
PyMem_Free(data);
if (obj == NULL) {
return NULL;
}
return obj;
}
static int
_channel_drop(_channels *channels, int64_t id, int send, int recv)
{
PyInterpreterState *interp = _get_current();
if (interp == NULL) {
return -1;
}
// Look up the channel.
PyThread_type_lock mutex = NULL;
_PyChannelState *chan = _channels_lookup(channels, id, &mutex);
if (chan == NULL) {
return -1;
}
// Past this point we are responsible for releasing the mutex.
// Close one or both of the two ends.
int res = _channel_close_interpreter(chan, PyInterpreterState_GetID(interp), send-recv);
PyThread_release_lock(mutex);
return res;
}
static int
_channel_close(_channels *channels, int64_t id, int end, int force)
{
return _channels_close(channels, id, NULL, end, force);
}
static int
_channel_is_associated(_channels *channels, int64_t cid, int64_t interp,
int send)
{
_PyChannelState *chan = _channels_lookup(channels, cid, NULL);
if (chan == NULL) {
return -1;
} else if (send && chan->closing != NULL) {
PyErr_Format(ChannelClosedError, "channel %" PRId64 " closed", cid);
return -1;
}
_channelend *end = _channelend_find(send ? chan->ends->send : chan->ends->recv,
interp, NULL);
return (end != NULL && end->open);
}
/* ChannelID class */
static PyTypeObject ChannelIDtype;
typedef struct channelid {
PyObject_HEAD
int64_t id;
int end;
int resolve;
_channels *channels;
} channelid;
static int
channel_id_converter(PyObject *arg, void *ptr)
{
int64_t cid;
if (PyObject_TypeCheck(arg, &ChannelIDtype)) {
cid = ((channelid *)arg)->id;
}
else if (PyIndex_Check(arg)) {
cid = PyLong_AsLongLong(arg);
if (cid == -1 && PyErr_Occurred()) {
return 0;
}
if (cid < 0) {
PyErr_Format(PyExc_ValueError,
"channel ID must be a non-negative int, got %R", arg);
return 0;
}
}
else {
PyErr_Format(PyExc_TypeError,
"channel ID must be an int, got %.100s",
Py_TYPE(arg)->tp_name);
return 0;
}
*(int64_t *)ptr = cid;
return 1;
}
static channelid *
newchannelid(PyTypeObject *cls, int64_t cid, int end, _channels *channels,
int force, int resolve)
{
channelid *self = PyObject_New(channelid, cls);
if (self == NULL) {
return NULL;
}
self->id = cid;
self->end = end;
self->resolve = resolve;
self->channels = channels;
if (_channels_add_id_object(channels, cid) != 0) {
if (force && PyErr_ExceptionMatches(ChannelNotFoundError)) {
PyErr_Clear();
}
else {
Py_DECREF((PyObject *)self);
return NULL;
}
}
return self;
}
static _channels * _global_channels(void);
static PyObject *
channelid_new(PyTypeObject *cls, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"id", "send", "recv", "force", "_resolve", NULL};
int64_t cid;
int send = -1;
int recv = -1;
int force = 0;
int resolve = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
"O&|$pppp:ChannelID.__new__", kwlist,
channel_id_converter, &cid, &send, &recv, &force, &resolve))
return NULL;
// Handle "send" and "recv".
if (send == 0 && recv == 0) {
PyErr_SetString(PyExc_ValueError,
"'send' and 'recv' cannot both be False");
return NULL;
}
int end = 0;
if (send == 1) {
if (recv == 0 || recv == -1) {
end = CHANNEL_SEND;
}
}
else if (recv == 1) {
end = CHANNEL_RECV;
}
return (PyObject *)newchannelid(cls, cid, end, _global_channels(),
force, resolve);
}
static void
channelid_dealloc(PyObject *v)
{
int64_t cid = ((channelid *)v)->id;
_channels *channels = ((channelid *)v)->channels;
Py_TYPE(v)->tp_free(v);
_channels_drop_id_object(channels, cid);
}
static PyObject *
channelid_repr(PyObject *self)
{
PyTypeObject *type = Py_TYPE(self);
const char *name = _PyType_Name(type);
channelid *cid = (channelid *)self;
const char *fmt;
if (cid->end == CHANNEL_SEND) {
fmt = "%s(%" PRId64 ", send=True)";
}
else if (cid->end == CHANNEL_RECV) {
fmt = "%s(%" PRId64 ", recv=True)";
}
else {
fmt = "%s(%" PRId64 ")";
}
return PyUnicode_FromFormat(fmt, name, cid->id);
}
static PyObject *
channelid_str(PyObject *self)
{
channelid *cid = (channelid *)self;
return PyUnicode_FromFormat("%" PRId64 "", cid->id);
}
static PyObject *
channelid_int(PyObject *self)
{
channelid *cid = (channelid *)self;
return PyLong_FromLongLong(cid->id);
}
static PyNumberMethods channelid_as_number = {
0, /* nb_add */
0, /* nb_subtract */
0, /* nb_multiply */
0, /* nb_remainder */
0, /* nb_divmod */
0, /* nb_power */
0, /* nb_negative */
0, /* nb_positive */
0, /* nb_absolute */
0, /* nb_bool */
0, /* nb_invert */
0, /* nb_lshift */
0, /* nb_rshift */
0, /* nb_and */
0, /* nb_xor */
0, /* nb_or */
(unaryfunc)channelid_int, /* nb_int */
0, /* nb_reserved */
0, /* nb_float */
0, /* nb_inplace_add */
0, /* nb_inplace_subtract */
0, /* nb_inplace_multiply */
0, /* nb_inplace_remainder */
0, /* nb_inplace_power */
0, /* nb_inplace_lshift */
0, /* nb_inplace_rshift */
0, /* nb_inplace_and */
0, /* nb_inplace_xor */
0, /* nb_inplace_or */
0, /* nb_floor_divide */
0, /* nb_true_divide */
0, /* nb_inplace_floor_divide */
0, /* nb_inplace_true_divide */
(unaryfunc)channelid_int, /* nb_index */
};
static Py_hash_t
channelid_hash(PyObject *self)
{
channelid *cid = (channelid *)self;
PyObject *id = PyLong_FromLongLong(cid->id);
if (id == NULL) {
return -1;
}
Py_hash_t hash = PyObject_Hash(id);
Py_DECREF(id);
return hash;
}
static PyObject *
channelid_richcompare(PyObject *self, PyObject *other, int op)
{
if (op != Py_EQ && op != Py_NE) {
Py_RETURN_NOTIMPLEMENTED;
}
if (!PyObject_TypeCheck(self, &ChannelIDtype)) {
Py_RETURN_NOTIMPLEMENTED;
}
channelid *cid = (channelid *)self;
int equal;
if (PyObject_TypeCheck(other, &ChannelIDtype)) {
channelid *othercid = (channelid *)other;
equal = (cid->end == othercid->end) && (cid->id == othercid->id);
}
else if (PyLong_Check(other)) {
/* Fast path */
int overflow;
long long othercid = PyLong_AsLongLongAndOverflow(other, &overflow);
if (othercid == -1 && PyErr_Occurred()) {
return NULL;
}
equal = !overflow && (othercid >= 0) && (cid->id == othercid);
}
else if (PyNumber_Check(other)) {
PyObject *pyid = PyLong_FromLongLong(cid->id);
if (pyid == NULL) {
return NULL;
}
PyObject *res = PyObject_RichCompare(pyid, other, op);
Py_DECREF(pyid);
return res;
}
else {
Py_RETURN_NOTIMPLEMENTED;
}
if ((op == Py_EQ && equal) || (op == Py_NE && !equal)) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
}
static PyObject *
_channel_from_cid(PyObject *cid, int end)
{
PyObject *highlevel = PyImport_ImportModule("interpreters");
if (highlevel == NULL) {
PyErr_Clear();
highlevel = PyImport_ImportModule("test.support.interpreters");
if (highlevel == NULL) {
return NULL;
}
}
const char *clsname = (end == CHANNEL_RECV) ? "RecvChannel" :
"SendChannel";
PyObject *cls = PyObject_GetAttrString(highlevel, clsname);
Py_DECREF(highlevel);
if (cls == NULL) {
return NULL;
}
PyObject *chan = PyObject_CallFunctionObjArgs(cls, cid, NULL);
Py_DECREF(cls);
if (chan == NULL) {
return NULL;
}
return chan;
}
struct _channelid_xid {
int64_t id;
int end;
int resolve;
};
static PyObject *
_channelid_from_xid(_PyCrossInterpreterData *data)
{
struct _channelid_xid *xid = (struct _channelid_xid *)data->data;
// Note that we do not preserve the "resolve" flag.
PyObject *cid = (PyObject *)newchannelid(&ChannelIDtype, xid->id, xid->end,
_global_channels(), 0, 0);
if (xid->end == 0) {
return cid;
}
if (!xid->resolve) {
return cid;
}
/* Try returning a high-level channel end but fall back to the ID. */
PyObject *chan = _channel_from_cid(cid, xid->end);
if (chan == NULL) {
PyErr_Clear();
return cid;
}
Py_DECREF(cid);
return chan;
}
static int
_channelid_shared(PyObject *obj, _PyCrossInterpreterData *data)
{
struct _channelid_xid *xid = PyMem_NEW(struct _channelid_xid, 1);
if (xid == NULL) {
return -1;
}
xid->id = ((channelid *)obj)->id;
xid->end = ((channelid *)obj)->end;
xid->resolve = ((channelid *)obj)->resolve;
data->data = xid;
Py_INCREF(obj);
data->obj = obj;
data->new_object = _channelid_from_xid;
data->free = PyMem_Free;
return 0;
}
static PyObject *
channelid_end(PyObject *self, void *end)
{
int force = 1;
channelid *cid = (channelid *)self;
if (end != NULL) {
return (PyObject *)newchannelid(Py_TYPE(self), cid->id, *(int *)end,
cid->channels, force, cid->resolve);
}
if (cid->end == CHANNEL_SEND) {
return PyUnicode_InternFromString("send");
}
if (cid->end == CHANNEL_RECV) {
return PyUnicode_InternFromString("recv");
}
return PyUnicode_InternFromString("both");
}
static int _channelid_end_send = CHANNEL_SEND;
static int _channelid_end_recv = CHANNEL_RECV;
static PyGetSetDef channelid_getsets[] = {
{"end", (getter)channelid_end, NULL,
PyDoc_STR("'send', 'recv', or 'both'")},
{"send", (getter)channelid_end, NULL,
PyDoc_STR("the 'send' end of the channel"), &_channelid_end_send},
{"recv", (getter)channelid_end, NULL,
PyDoc_STR("the 'recv' end of the channel"), &_channelid_end_recv},
{NULL}
};
PyDoc_STRVAR(channelid_doc,
"A channel ID identifies a channel and may be used as an int.");
static PyTypeObject ChannelIDtype = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"_xxsubinterpreters.ChannelID", /* tp_name */
sizeof(channelid), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)channelid_dealloc, /* tp_dealloc */
0, /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_as_async */
(reprfunc)channelid_repr, /* tp_repr */
&channelid_as_number, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
channelid_hash, /* tp_hash */
0, /* tp_call */
(reprfunc)channelid_str, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
channelid_doc, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
channelid_richcompare, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
channelid_getsets, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
// Note that we do not set tp_new to channelid_new. Instead we
// set it to NULL, meaning it cannot be instantiated from Python
// code. We do this because there is a strong relationship between
// channel IDs and the channel lifecycle, so this limitation avoids
// related complications.
NULL, /* tp_new */
};
/* interpreter-specific code ************************************************/
static PyObject * RunFailedError = NULL;
static int
interp_exceptions_init(PyObject *ns)
{
// XXX Move the exceptions into per-module memory?
if (RunFailedError == NULL) {
// An uncaught exception came out of interp_run_string().
RunFailedError = PyErr_NewException("_xxsubinterpreters.RunFailedError",
PyExc_RuntimeError, NULL);
if (RunFailedError == NULL) {
return -1;
}
if (PyDict_SetItemString(ns, "RunFailedError", RunFailedError) != 0) {
return -1;
}
}
return 0;
}
static int
_is_running(PyInterpreterState *interp)
{
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
if (PyThreadState_Next(tstate) != NULL) {
PyErr_SetString(PyExc_RuntimeError,
"interpreter has more than one thread");
return -1;
}
assert(!PyErr_Occurred());
PyFrameObject *frame = PyThreadState_GetFrame(tstate);
if (frame == NULL) {
return 0;
}
int executing = (int)(frame->f_executing);
Py_DECREF(frame);
return executing;
}
static int
_ensure_not_running(PyInterpreterState *interp)
{
int is_running = _is_running(interp);
if (is_running < 0) {
return -1;
}
if (is_running) {
PyErr_Format(PyExc_RuntimeError, "interpreter already running");
return -1;
}
return 0;
}
static int
_run_script(PyInterpreterState *interp, const char *codestr,
_sharedns *shared, _sharedexception **pexc)
{
assert(!PyErr_Occurred()); // ...in the called interpreter.
PyObject *main_mod = _PyInterpreterState_GetMainModule(interp);
if (main_mod == NULL) {
goto error;
}
PyObject *ns = PyModule_GetDict(main_mod); // borrowed
Py_DECREF(main_mod);
if (ns == NULL) {
goto error;
}
Py_INCREF(ns);
// Apply the cross-interpreter data.
if (shared != NULL) {
if (_sharedns_apply(shared, ns) != 0) {
Py_DECREF(ns);
goto error;
}
}
// Run the string (see PyRun_SimpleStringFlags).
PyObject *result = PyRun_StringFlags(codestr, Py_file_input, ns, ns, NULL);
Py_DECREF(ns);
if (result == NULL) {
goto error;
}
else {
Py_DECREF(result); // We throw away the result.
}
*pexc = NULL;
return 0;
PyObject *exctype = NULL, *exc = NULL, *tb = NULL;
error:
PyErr_Fetch(&exctype, &exc, &tb);
// First normalize the exception.
PyErr_NormalizeException(&exctype, &exc, &tb);
assert(PyExceptionInstance_Check(exc));
if (tb != NULL) {
PyException_SetTraceback(exc, tb);
}
// Behave as though the exception was caught in this thread.
PyErr_SetExcInfo(exctype, exc, tb); // Like entering "except" block.
// Serialize the exception.
_sharedexception *sharedexc = _sharedexception_new();
if (sharedexc == NULL) {
IGNORE_FAILURE("script raised an uncaught exception");
} else {
_sharedexception_extract(sharedexc, exc);
assert(!PyErr_Occurred());
}
// Clear the exception.
PyErr_SetExcInfo(NULL, NULL, NULL); // Like leaving "except" block.
PyErr_Clear(); // Do not re-raise.
// "Return" the serialized exception.
*pexc = sharedexc;
return -1;
}
static int
_run_script_in_interpreter(PyInterpreterState *interp, const char *codestr,
PyObject *shareables)
{
assert(!PyErr_Occurred()); // ...in the calling interpreter.
if (_ensure_not_running(interp) < 0) {
return -1;
}
_sharedns *shared = _get_shared_ns(shareables);
if (shared == NULL && PyErr_Occurred()) {
return -1;
}
#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
// Switch to interpreter.
PyThreadState *new_tstate = PyInterpreterState_ThreadHead(interp);
PyThreadState *save1 = PyEval_SaveThread();
(void)PyThreadState_Swap(new_tstate);
// Run the script.
_sharedexception *exc = NULL;
int result = _run_script(interp, codestr, shared, &exc);
// Switch back.
PyEval_RestoreThread(save1);
#else
// Switch to interpreter.
PyThreadState *save_tstate = NULL;
if (interp != PyInterpreterState_Get()) {
// XXX Using the "head" thread isn't strictly correct.
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
// XXX Possible GILState issues?
save_tstate = PyThreadState_Swap(tstate);
}
// Run the script.
_sharedexception *sharedexc = NULL;
int result = _run_script(interp, codestr, shared, &sharedexc);
// Switch back.
if (save_tstate != NULL) {
PyThreadState_Swap(save_tstate);
}
#endif
// Propagate any exception out to the caller.
if (sharedexc != NULL) {
assert(!PyErr_Occurred());
PyObject *exc = _sharedexception_resolve(sharedexc, RunFailedError);
// XXX This is not safe once interpreters no longer share allocators.
_sharedexception_free(sharedexc);
PyObject *exctype = (PyObject *)Py_TYPE(exc);
Py_INCREF(exctype); // PyErr_Restore() steals a reference.
PyErr_Restore(exctype, exc, PyException_GetTraceback(exc));
}
else if (result != 0) {
// We were unable to allocate a shared exception.
PyErr_NoMemory();
}
if (shared != NULL) {
_sharedns_free(shared);
}
return result;
}
/* module level code ********************************************************/
/* globals is the process-global state for the module. It holds all
the data that we need to share between interpreters, so it cannot
hold PyObject values. */
static struct globals {
_channels channels;
} _globals = {{0}};
static int
_init_globals(void)
{
if (_channels_init(&_globals.channels) != 0) {
return -1;
}
return 0;
}
static _channels *
_global_channels(void) {
return &_globals.channels;
}
static PyObject *
interp_create(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"isolated", NULL};
int isolated = 1;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|$i:create", kwlist,
&isolated)) {
return NULL;
}
// Create and initialize the new interpreter.
PyThreadState *save_tstate = PyThreadState_Swap(NULL);
// XXX Possible GILState issues?
PyThreadState *tstate = _Py_NewInterpreter(isolated);
PyThreadState_Swap(save_tstate);
if (tstate == NULL) {
/* Since no new thread state was created, there is no exception to
propagate; raise a fresh one after swapping in the old thread
state. */
PyErr_SetString(PyExc_RuntimeError, "interpreter creation failed");
return NULL;
}
PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate);
PyObject *idobj = _PyInterpreterState_GetIDObject(interp);
if (idobj == NULL) {
// XXX Possible GILState issues?
save_tstate = PyThreadState_Swap(tstate);
Py_EndInterpreter(tstate);
PyThreadState_Swap(save_tstate);
return NULL;
}
_PyInterpreterState_RequireIDRef(interp, 1);
return idobj;
}
PyDoc_STRVAR(create_doc,
"create() -> ID\n\
\n\
Create a new interpreter and return a unique generated ID.");
static PyObject *
interp_destroy(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"id", NULL};
PyObject *id;
// XXX Use "L" for id?
if (!PyArg_ParseTupleAndKeywords(args, kwds,
"O:destroy", kwlist, &id)) {
return NULL;
}
// Look up the interpreter.
PyInterpreterState *interp = _PyInterpreterID_LookUp(id);
if (interp == NULL) {
return NULL;
}
// Ensure we don't try to destroy the current interpreter.
PyInterpreterState *current = _get_current();
if (current == NULL) {
return NULL;
}
if (interp == current) {
PyErr_SetString(PyExc_RuntimeError,
"cannot destroy the current interpreter");
return NULL;
}
// Ensure the interpreter isn't running.
/* XXX We *could* support destroying a running interpreter but
aren't going to worry about it for now. */
if (_ensure_not_running(interp) < 0) {
return NULL;
}
// Destroy the interpreter.
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
// XXX Possible GILState issues?
PyThreadState *save_tstate = PyThreadState_Swap(tstate);
Py_EndInterpreter(tstate);
PyThreadState_Swap(save_tstate);
Py_RETURN_NONE;
}
PyDoc_STRVAR(destroy_doc,
"destroy(id)\n\
\n\
Destroy the identified interpreter.\n\
\n\
Attempting to destroy the current interpreter results in a RuntimeError.\n\
So does an unrecognized ID.");
static PyObject *
interp_list_all(PyObject *self, PyObject *Py_UNUSED(ignored))
{
PyObject *ids, *id;
PyInterpreterState *interp;
ids = PyList_New(0);
if (ids == NULL) {
return NULL;
}
interp = PyInterpreterState_Head();
while (interp != NULL) {
id = _PyInterpreterState_GetIDObject(interp);
if (id == NULL) {
Py_DECREF(ids);
return NULL;
}
// insert at front of list
int res = PyList_Insert(ids, 0, id);
Py_DECREF(id);
if (res < 0) {
Py_DECREF(ids);
return NULL;
}
interp = PyInterpreterState_Next(interp);
}
return ids;
}
PyDoc_STRVAR(list_all_doc,
"list_all() -> [ID]\n\
\n\
Return a list containing the ID of every existing interpreter.");
static PyObject *
interp_get_current(PyObject *self, PyObject *Py_UNUSED(ignored))
{
PyInterpreterState *interp =_get_current();
if (interp == NULL) {
return NULL;
}
return _PyInterpreterState_GetIDObject(interp);
}
PyDoc_STRVAR(get_current_doc,
"get_current() -> ID\n\
\n\
Return the ID of current interpreter.");
static PyObject *
interp_get_main(PyObject *self, PyObject *Py_UNUSED(ignored))
{
// Currently, 0 is always the main interpreter.
int64_t id = 0;
return _PyInterpreterID_New(id);
}
PyDoc_STRVAR(get_main_doc,
"get_main() -> ID\n\
\n\
Return the ID of main interpreter.");
static PyObject *
interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"id", "script", "shared", NULL};
PyObject *id, *code;
PyObject *shared = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
"OU|O:run_string", kwlist,
&id, &code, &shared)) {
return NULL;
}
// Look up the interpreter.
PyInterpreterState *interp = _PyInterpreterID_LookUp(id);
if (interp == NULL) {
return NULL;
}
// Extract code.
Py_ssize_t size;
const char *codestr = PyUnicode_AsUTF8AndSize(code, &size);
if (codestr == NULL) {
return NULL;
}
if (strlen(codestr) != (size_t)size) {
PyErr_SetString(PyExc_ValueError,
"source code string cannot contain null bytes");
return NULL;
}
// Run the code in the interpreter.
if (_run_script_in_interpreter(interp, codestr, shared) != 0) {
return NULL;
}
Py_RETURN_NONE;
}
PyDoc_STRVAR(run_string_doc,
"run_string(id, script, shared)\n\
\n\
Execute the provided string in the identified interpreter.\n\
\n\
See PyRun_SimpleStrings.");
static PyObject *
object_is_shareable(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"obj", NULL};
PyObject *obj;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
"O:is_shareable", kwlist, &obj)) {
return NULL;
}
if (_PyObject_CheckCrossInterpreterData(obj) == 0) {
Py_RETURN_TRUE;
}
PyErr_Clear();
Py_RETURN_FALSE;
}
PyDoc_STRVAR(is_shareable_doc,
"is_shareable(obj) -> bool\n\
\n\
Return True if the object's data may be shared between interpreters and\n\
False otherwise.");
static PyObject *
interp_is_running(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"id", NULL};
PyObject *id;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
"O:is_running", kwlist, &id)) {
return NULL;
}
PyInterpreterState *interp = _PyInterpreterID_LookUp(id);
if (interp == NULL) {
return NULL;
}
int is_running = _is_running(interp);
if (is_running < 0) {
return NULL;
}
if (is_running) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
}
PyDoc_STRVAR(is_running_doc,
"is_running(id) -> bool\n\
\n\
Return whether or not the identified interpreter is running.");
static PyObject *
channel_create(PyObject *self, PyObject *Py_UNUSED(ignored))
{
int64_t cid = _channel_create(&_globals.channels);
if (cid < 0) {
return NULL;
}
PyObject *id = (PyObject *)newchannelid(&ChannelIDtype, cid, 0,
&_globals.channels, 0, 0);
if (id == NULL) {
if (_channel_destroy(&_globals.channels, cid) != 0) {
// XXX issue a warning?
}
return NULL;
}
assert(((channelid *)id)->channels != NULL);
return id;
}
PyDoc_STRVAR(channel_create_doc,
"channel_create() -> cid\n\
\n\
Create a new cross-interpreter channel and return a unique generated ID.");
static PyObject *
channel_destroy(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"cid", NULL};
int64_t cid;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:channel_destroy", kwlist,
channel_id_converter, &cid)) {
return NULL;
}
if (_channel_destroy(&_globals.channels, cid) != 0) {
return NULL;
}
Py_RETURN_NONE;
}
PyDoc_STRVAR(channel_destroy_doc,
"channel_destroy(cid)\n\
\n\
Close and finalize the channel. Afterward attempts to use the channel\n\
will behave as though it never existed.");
static PyObject *
channel_list_all(PyObject *self, PyObject *Py_UNUSED(ignored))
{
int64_t count = 0;
int64_t *cids = _channels_list_all(&_globals.channels, &count);
if (cids == NULL) {
if (count == 0) {
return PyList_New(0);
}
return NULL;
}
PyObject *ids = PyList_New((Py_ssize_t)count);
if (ids == NULL) {
goto finally;
}
int64_t *cur = cids;
for (int64_t i=0; i < count; cur++, i++) {
PyObject *id = (PyObject *)newchannelid(&ChannelIDtype, *cur, 0,
&_globals.channels, 0, 0);
if (id == NULL) {
Py_DECREF(ids);
ids = NULL;
break;
}
PyList_SET_ITEM(ids, i, id);
}
finally:
PyMem_Free(cids);
return ids;
}
PyDoc_STRVAR(channel_list_all_doc,
"channel_list_all() -> [cid]\n\
\n\
Return the list of all IDs for active channels.");
static PyObject *
channel_list_interpreters(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"cid", "send", NULL};
int64_t cid; /* Channel ID */
int send = 0; /* Send or receive end? */
int64_t id;
PyObject *ids, *id_obj;
PyInterpreterState *interp;
if (!PyArg_ParseTupleAndKeywords(
args, kwds, "O&$p:channel_list_interpreters",
kwlist, channel_id_converter, &cid, &send)) {
return NULL;
}
ids = PyList_New(0);
if (ids == NULL) {
goto except;
}
interp = PyInterpreterState_Head();
while (interp != NULL) {
id = PyInterpreterState_GetID(interp);
assert(id >= 0);
int res = _channel_is_associated(&_globals.channels, cid, id, send);
if (res < 0) {
goto except;
}
if (res) {
id_obj = _PyInterpreterState_GetIDObject(interp);
if (id_obj == NULL) {
goto except;
}
res = PyList_Insert(ids, 0, id_obj);
Py_DECREF(id_obj);
if (res < 0) {
goto except;
}
}
interp = PyInterpreterState_Next(interp);
}
goto finally;
except:
Py_XDECREF(ids);
ids = NULL;
finally:
return ids;
}
PyDoc_STRVAR(channel_list_interpreters_doc,
"channel_list_interpreters(cid, *, send) -> [id]\n\
\n\
Return the list of all interpreter IDs associated with an end of the channel.\n\
\n\
The 'send' argument should be a boolean indicating whether to use the send or\n\
receive end.");
static PyObject *
channel_send(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"cid", "obj", NULL};
int64_t cid;
PyObject *obj;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&O:channel_send", kwlist,
channel_id_converter, &cid, &obj)) {
return NULL;
}
if (_channel_send(&_globals.channels, cid, obj) != 0) {
return NULL;
}
Py_RETURN_NONE;
}
PyDoc_STRVAR(channel_send_doc,
"channel_send(cid, obj)\n\
\n\
Add the object's data to the channel's queue.");
static PyObject *
channel_recv(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"cid", "default", NULL};
int64_t cid;
PyObject *dflt = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|O:channel_recv", kwlist,
channel_id_converter, &cid, &dflt)) {
return NULL;
}
Py_XINCREF(dflt);
PyObject *obj = _channel_recv(&_globals.channels, cid);
if (obj != NULL) {
Py_XDECREF(dflt);
return obj;
} else if (PyErr_Occurred()) {
Py_XDECREF(dflt);
return NULL;
} else if (dflt != NULL) {
return dflt;
} else {
PyErr_Format(ChannelEmptyError, "channel %" PRId64 " is empty", cid);
return NULL;
}
}
PyDoc_STRVAR(channel_recv_doc,
"channel_recv(cid, [default]) -> obj\n\
\n\
Return a new object from the data at the front of the channel's queue.\n\
\n\
If there is nothing to receive then raise ChannelEmptyError, unless\n\
a default value is provided. In that case return it.");
static PyObject *
channel_close(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"cid", "send", "recv", "force", NULL};
int64_t cid;
int send = 0;
int recv = 0;
int force = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
"O&|$ppp:channel_close", kwlist,
channel_id_converter, &cid, &send, &recv, &force)) {
return NULL;
}
if (_channel_close(&_globals.channels, cid, send-recv, force) != 0) {
return NULL;
}
Py_RETURN_NONE;
}
PyDoc_STRVAR(channel_close_doc,
"channel_close(cid, *, send=None, recv=None, force=False)\n\
\n\
Close the channel for all interpreters.\n\
\n\
If the channel is empty then the keyword args are ignored and both\n\
ends are immediately closed. Otherwise, if 'force' is True then\n\
all queued items are released and both ends are immediately\n\
closed.\n\
\n\
If the channel is not empty *and* 'force' is False then following\n\
happens:\n\
\n\
* recv is True (regardless of send):\n\
- raise ChannelNotEmptyError\n\
* recv is None and send is None:\n\
- raise ChannelNotEmptyError\n\
* send is True and recv is not True:\n\
- fully close the 'send' end\n\
- close the 'recv' end to interpreters not already receiving\n\
- fully close it once empty\n\
\n\
Closing an already closed channel results in a ChannelClosedError.\n\
\n\
Once the channel's ID has no more ref counts in any interpreter\n\
the channel will be destroyed.");
static PyObject *
channel_release(PyObject *self, PyObject *args, PyObject *kwds)
{
// Note that only the current interpreter is affected.
static char *kwlist[] = {"cid", "send", "recv", "force", NULL};
int64_t cid;
int send = 0;
int recv = 0;
int force = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
"O&|$ppp:channel_release", kwlist,
channel_id_converter, &cid, &send, &recv, &force)) {
return NULL;
}
if (send == 0 && recv == 0) {
send = 1;
recv = 1;
}
// XXX Handle force is True.
// XXX Fix implicit release.
if (_channel_drop(&_globals.channels, cid, send, recv) != 0) {
return NULL;
}
Py_RETURN_NONE;
}
PyDoc_STRVAR(channel_release_doc,
"channel_release(cid, *, send=None, recv=None, force=True)\n\
\n\
Close the channel for the current interpreter. 'send' and 'recv'\n\
(bool) may be used to indicate the ends to close. By default both\n\
ends are closed. Closing an already closed end is a noop.");
static PyObject *
channel__channel_id(PyObject *self, PyObject *args, PyObject *kwds)
{
return channelid_new(&ChannelIDtype, args, kwds);
}
static PyMethodDef module_functions[] = {
{"create", (PyCFunction)(void(*)(void))interp_create,
METH_VARARGS | METH_KEYWORDS, create_doc},
{"destroy", (PyCFunction)(void(*)(void))interp_destroy,
METH_VARARGS | METH_KEYWORDS, destroy_doc},
{"list_all", interp_list_all,
METH_NOARGS, list_all_doc},
{"get_current", interp_get_current,
METH_NOARGS, get_current_doc},
{"get_main", interp_get_main,
METH_NOARGS, get_main_doc},
{"is_running", (PyCFunction)(void(*)(void))interp_is_running,
METH_VARARGS | METH_KEYWORDS, is_running_doc},
{"run_string", (PyCFunction)(void(*)(void))interp_run_string,
METH_VARARGS | METH_KEYWORDS, run_string_doc},
{"is_shareable", (PyCFunction)(void(*)(void))object_is_shareable,
METH_VARARGS | METH_KEYWORDS, is_shareable_doc},
{"channel_create", channel_create,
METH_NOARGS, channel_create_doc},
{"channel_destroy", (PyCFunction)(void(*)(void))channel_destroy,
METH_VARARGS | METH_KEYWORDS, channel_destroy_doc},
{"channel_list_all", channel_list_all,
METH_NOARGS, channel_list_all_doc},
{"channel_list_interpreters", (PyCFunction)(void(*)(void))channel_list_interpreters,
METH_VARARGS | METH_KEYWORDS, channel_list_interpreters_doc},
{"channel_send", (PyCFunction)(void(*)(void))channel_send,
METH_VARARGS | METH_KEYWORDS, channel_send_doc},
{"channel_recv", (PyCFunction)(void(*)(void))channel_recv,
METH_VARARGS | METH_KEYWORDS, channel_recv_doc},
{"channel_close", (PyCFunction)(void(*)(void))channel_close,
METH_VARARGS | METH_KEYWORDS, channel_close_doc},
{"channel_release", (PyCFunction)(void(*)(void))channel_release,
METH_VARARGS | METH_KEYWORDS, channel_release_doc},
{"_channel_id", (PyCFunction)(void(*)(void))channel__channel_id,
METH_VARARGS | METH_KEYWORDS, NULL},
{NULL, NULL} /* sentinel */
};
/* initialization function */
PyDoc_STRVAR(module_doc,
"This module provides primitive operations to manage Python interpreters.\n\
The 'interpreters' module provides a more convenient interface.");
static struct PyModuleDef interpretersmodule = {
PyModuleDef_HEAD_INIT,
"_xxsubinterpreters", /* m_name */
module_doc, /* m_doc */
-1, /* m_size */
module_functions, /* m_methods */
NULL, /* m_slots */
NULL, /* m_traverse */
NULL, /* m_clear */
NULL /* m_free */
};
PyMODINIT_FUNC
PyInit__xxsubinterpreters(void)
{
if (_init_globals() != 0) {
return NULL;
}
/* Initialize types */
if (PyType_Ready(&ChannelIDtype) != 0) {
return NULL;
}
/* Create the module */
PyObject *module = PyModule_Create(&interpretersmodule);
if (module == NULL) {
return NULL;
}
/* Add exception types */
PyObject *ns = PyModule_GetDict(module); // borrowed
if (interp_exceptions_init(ns) != 0) {
return NULL;
}
if (channel_exceptions_init(ns) != 0) {
return NULL;
}
/* Add other types */
Py_INCREF(&ChannelIDtype);
if (PyDict_SetItemString(ns, "ChannelID", (PyObject *)&ChannelIDtype) != 0) {
return NULL;
}
Py_INCREF(&_PyInterpreterID_Type);
if (PyDict_SetItemString(ns, "InterpreterID", (PyObject *)&_PyInterpreterID_Type) != 0) {
return NULL;
}
if (_PyCrossInterpreterData_RegisterClass(&ChannelIDtype, _channelid_shared)) {
return NULL;
}
return module;
}