summaryrefslogtreecommitdiffstats
path: root/Modules/_interpretersmodule.c
diff options
context:
space:
mode:
Diffstat (limited to 'Modules/_interpretersmodule.c')
-rw-r--r--Modules/_interpretersmodule.c1567
1 files changed, 1567 insertions, 0 deletions
diff --git a/Modules/_interpretersmodule.c b/Modules/_interpretersmodule.c
new file mode 100644
index 0000000..8fea569
--- /dev/null
+++ b/Modules/_interpretersmodule.c
@@ -0,0 +1,1567 @@
+/* interpreters module */
+/* low-level access to interpreter primitives */
+
+#ifndef Py_BUILD_CORE_BUILTIN
+# define Py_BUILD_CORE_MODULE 1
+#endif
+
+#include "Python.h"
+#include "pycore_abstract.h" // _PyIndex_Check()
+#include "pycore_crossinterp.h" // struct _xid
+#include "pycore_interp.h" // _PyInterpreterState_IDIncref()
+#include "pycore_initconfig.h" // _PyErr_SetFromPyStatus()
+#include "pycore_long.h" // _PyLong_IsNegative()
+#include "pycore_modsupport.h" // _PyArg_BadArgument()
+#include "pycore_namespace.h" // _PyNamespace_New()
+#include "pycore_pybuffer.h" // _PyBuffer_ReleaseInInterpreterAndRawFree()
+#include "pycore_pyerrors.h" // _Py_excinfo
+#include "pycore_pylifecycle.h" // _PyInterpreterConfig_AsDict()
+#include "pycore_pystate.h" // _PyInterpreterState_SetRunningMain()
+
+#include "marshal.h" // PyMarshal_ReadObjectFromString()
+
+#include "_interpreters_common.h"
+
+
+#define MODULE_NAME _interpreters
+#define MODULE_NAME_STR Py_STRINGIFY(MODULE_NAME)
+#define MODINIT_FUNC_NAME RESOLVE_MODINIT_FUNC_NAME(MODULE_NAME)
+
+
+static PyInterpreterState *
+_get_current_interp(void)
+{
+ // PyInterpreterState_Get() aborts if lookup fails, so don't need
+ // to check the result for NULL.
+ return PyInterpreterState_Get();
+}
+
+#define look_up_interp _PyInterpreterState_LookUpIDObject
+
+
+static PyObject *
+_get_current_module(void)
+{
+ PyObject *name = PyUnicode_FromString(MODULE_NAME_STR);
+ if (name == NULL) {
+ return NULL;
+ }
+ PyObject *mod = PyImport_GetModule(name);
+ Py_DECREF(name);
+ if (mod == NULL) {
+ return NULL;
+ }
+ assert(mod != Py_None);
+ return mod;
+}
+
+
+static int
+is_running_main(PyInterpreterState *interp)
+{
+ if (_PyInterpreterState_IsRunningMain(interp)) {
+ return 1;
+ }
+ // Unlike with the general C-API, we can be confident that someone
+ // using this module for the main interpreter is doing so through
+ // the main program. Thus we can make this extra check. This benefits
+ // applications that embed Python but haven't been updated yet
+ // to call_PyInterpreterState_SetRunningMain().
+ if (_Py_IsMainInterpreter(interp)) {
+ return 1;
+ }
+ return 0;
+}
+
+
+/* Cross-interpreter Buffer Views *******************************************/
+
+// XXX Release when the original interpreter is destroyed.
+
+typedef struct {
+ PyObject_HEAD
+ Py_buffer *view;
+ int64_t interpid;
+} XIBufferViewObject;
+
+static PyObject *
+xibufferview_from_xid(PyTypeObject *cls, _PyCrossInterpreterData *data)
+{
+ assert(_PyCrossInterpreterData_DATA(data) != NULL);
+ assert(_PyCrossInterpreterData_OBJ(data) == NULL);
+ assert(_PyCrossInterpreterData_INTERPID(data) >= 0);
+ XIBufferViewObject *self = PyObject_Malloc(sizeof(XIBufferViewObject));
+ if (self == NULL) {
+ return NULL;
+ }
+ PyObject_Init((PyObject *)self, cls);
+ self->view = (Py_buffer *)_PyCrossInterpreterData_DATA(data);
+ self->interpid = _PyCrossInterpreterData_INTERPID(data);
+ return (PyObject *)self;
+}
+
+static void
+xibufferview_dealloc(XIBufferViewObject *self)
+{
+ PyInterpreterState *interp = _PyInterpreterState_LookUpID(self->interpid);
+ /* If the interpreter is no longer alive then we have problems,
+ since other objects may be using the buffer still. */
+ assert(interp != NULL);
+
+ if (_PyBuffer_ReleaseInInterpreterAndRawFree(interp, self->view) < 0) {
+ // XXX Emit a warning?
+ PyErr_Clear();
+ }
+
+ PyTypeObject *tp = Py_TYPE(self);
+ tp->tp_free(self);
+ /* "Instances of heap-allocated types hold a reference to their type."
+ * See: https://docs.python.org/3.11/howto/isolating-extensions.html#garbage-collection-protocol
+ * See: https://docs.python.org/3.11/c-api/typeobj.html#c.PyTypeObject.tp_traverse
+ */
+ // XXX Why don't we implement Py_TPFLAGS_HAVE_GC, e.g. Py_tp_traverse,
+ // like we do for _abc._abc_data?
+ Py_DECREF(tp);
+}
+
+static int
+xibufferview_getbuf(XIBufferViewObject *self, Py_buffer *view, int flags)
+{
+ /* Only PyMemoryView_FromObject() should ever call this,
+ via _memoryview_from_xid() below. */
+ *view = *self->view;
+ view->obj = (PyObject *)self;
+ // XXX Should we leave it alone?
+ view->internal = NULL;
+ return 0;
+}
+
+static PyType_Slot XIBufferViewType_slots[] = {
+ {Py_tp_dealloc, (destructor)xibufferview_dealloc},
+ {Py_bf_getbuffer, (getbufferproc)xibufferview_getbuf},
+ // We don't bother with Py_bf_releasebuffer since we don't need it.
+ {0, NULL},
+};
+
+static PyType_Spec XIBufferViewType_spec = {
+ .name = MODULE_NAME_STR ".CrossInterpreterBufferView",
+ .basicsize = sizeof(XIBufferViewObject),
+ .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
+ Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE),
+ .slots = XIBufferViewType_slots,
+};
+
+
+static PyTypeObject * _get_current_xibufferview_type(void);
+
+static PyObject *
+_memoryview_from_xid(_PyCrossInterpreterData *data)
+{
+ PyTypeObject *cls = _get_current_xibufferview_type();
+ if (cls == NULL) {
+ return NULL;
+ }
+ PyObject *obj = xibufferview_from_xid(cls, data);
+ if (obj == NULL) {
+ return NULL;
+ }
+ return PyMemoryView_FromObject(obj);
+}
+
+static int
+_memoryview_shared(PyThreadState *tstate, PyObject *obj,
+ _PyCrossInterpreterData *data)
+{
+ Py_buffer *view = PyMem_RawMalloc(sizeof(Py_buffer));
+ if (view == NULL) {
+ return -1;
+ }
+ if (PyObject_GetBuffer(obj, view, PyBUF_FULL_RO) < 0) {
+ PyMem_RawFree(view);
+ return -1;
+ }
+ _PyCrossInterpreterData_Init(data, tstate->interp, view, NULL,
+ _memoryview_from_xid);
+ return 0;
+}
+
+static int
+register_memoryview_xid(PyObject *mod, PyTypeObject **p_state)
+{
+ // XIBufferView
+ assert(*p_state == NULL);
+ PyTypeObject *cls = (PyTypeObject *)PyType_FromModuleAndSpec(
+ mod, &XIBufferViewType_spec, NULL);
+ if (cls == NULL) {
+ return -1;
+ }
+ if (PyModule_AddType(mod, cls) < 0) {
+ Py_DECREF(cls);
+ return -1;
+ }
+ *p_state = cls;
+
+ // Register XID for the builtin memoryview type.
+ if (ensure_xid_class(&PyMemoryView_Type, _memoryview_shared) < 0) {
+ return -1;
+ }
+ // We don't ever bother un-registering memoryview.
+
+ return 0;
+}
+
+
+
+/* module state *************************************************************/
+
+typedef struct {
+ int _notused;
+
+ /* heap types */
+ PyTypeObject *XIBufferViewType;
+} module_state;
+
+static inline module_state *
+get_module_state(PyObject *mod)
+{
+ assert(mod != NULL);
+ module_state *state = PyModule_GetState(mod);
+ assert(state != NULL);
+ return state;
+}
+
+static module_state *
+_get_current_module_state(void)
+{
+ PyObject *mod = _get_current_module();
+ if (mod == NULL) {
+ // XXX import it?
+ PyErr_SetString(PyExc_RuntimeError,
+ MODULE_NAME_STR " module not imported yet");
+ return NULL;
+ }
+ module_state *state = get_module_state(mod);
+ Py_DECREF(mod);
+ return state;
+}
+
+static int
+traverse_module_state(module_state *state, visitproc visit, void *arg)
+{
+ /* heap types */
+ Py_VISIT(state->XIBufferViewType);
+
+ return 0;
+}
+
+static int
+clear_module_state(module_state *state)
+{
+ /* heap types */
+ Py_CLEAR(state->XIBufferViewType);
+
+ return 0;
+}
+
+
+static PyTypeObject *
+_get_current_xibufferview_type(void)
+{
+ module_state *state = _get_current_module_state();
+ if (state == NULL) {
+ return NULL;
+ }
+ return state->XIBufferViewType;
+}
+
+
+/* Python code **************************************************************/
+
+static const char *
+check_code_str(PyUnicodeObject *text)
+{
+ assert(text != NULL);
+ if (PyUnicode_GET_LENGTH(text) == 0) {
+ return "too short";
+ }
+
+ // XXX Verify that it parses?
+
+ return NULL;
+}
+
+static const char *
+check_code_object(PyCodeObject *code)
+{
+ assert(code != NULL);
+ if (code->co_argcount > 0
+ || code->co_posonlyargcount > 0
+ || code->co_kwonlyargcount > 0
+ || code->co_flags & (CO_VARARGS | CO_VARKEYWORDS))
+ {
+ return "arguments not supported";
+ }
+ if (code->co_ncellvars > 0) {
+ return "closures not supported";
+ }
+ // We trust that no code objects under co_consts have unbound cell vars.
+
+ if (_PyCode_HAS_EXECUTORS(code) || _PyCode_HAS_INSTRUMENTATION(code)) {
+ return "only basic functions are supported";
+ }
+ if (code->_co_monitoring != NULL) {
+ return "only basic functions are supported";
+ }
+ if (code->co_extra != NULL) {
+ return "only basic functions are supported";
+ }
+
+ return NULL;
+}
+
+#define RUN_TEXT 1
+#define RUN_CODE 2
+
+static const char *
+get_code_str(PyObject *arg, Py_ssize_t *len_p, PyObject **bytes_p, int *flags_p)
+{
+ const char *codestr = NULL;
+ Py_ssize_t len = -1;
+ PyObject *bytes_obj = NULL;
+ int flags = 0;
+
+ if (PyUnicode_Check(arg)) {
+ assert(PyUnicode_CheckExact(arg)
+ && (check_code_str((PyUnicodeObject *)arg) == NULL));
+ codestr = PyUnicode_AsUTF8AndSize(arg, &len);
+ if (codestr == NULL) {
+ return NULL;
+ }
+ if (strlen(codestr) != (size_t)len) {
+ PyErr_SetString(PyExc_ValueError,
+ "source code string cannot contain null bytes");
+ return NULL;
+ }
+ flags = RUN_TEXT;
+ }
+ else {
+ assert(PyCode_Check(arg)
+ && (check_code_object((PyCodeObject *)arg) == NULL));
+ flags = RUN_CODE;
+
+ // Serialize the code object.
+ bytes_obj = PyMarshal_WriteObjectToString(arg, Py_MARSHAL_VERSION);
+ if (bytes_obj == NULL) {
+ return NULL;
+ }
+ codestr = PyBytes_AS_STRING(bytes_obj);
+ len = PyBytes_GET_SIZE(bytes_obj);
+ }
+
+ *flags_p = flags;
+ *bytes_p = bytes_obj;
+ *len_p = len;
+ return codestr;
+}
+
+
+/* interpreter-specific code ************************************************/
+
+static int
+init_named_config(PyInterpreterConfig *config, const char *name)
+{
+ if (name == NULL
+ || strcmp(name, "") == 0
+ || strcmp(name, "default") == 0)
+ {
+ name = "isolated";
+ }
+
+ if (strcmp(name, "isolated") == 0) {
+ *config = (PyInterpreterConfig)_PyInterpreterConfig_INIT;
+ }
+ else if (strcmp(name, "legacy") == 0) {
+ *config = (PyInterpreterConfig)_PyInterpreterConfig_LEGACY_INIT;
+ }
+ else if (strcmp(name, "empty") == 0) {
+ *config = (PyInterpreterConfig){0};
+ }
+ else {
+ PyErr_Format(PyExc_ValueError,
+ "unsupported config name '%s'", name);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+config_from_object(PyObject *configobj, PyInterpreterConfig *config)
+{
+ if (configobj == NULL || configobj == Py_None) {
+ if (init_named_config(config, NULL) < 0) {
+ return -1;
+ }
+ }
+ else if (PyUnicode_Check(configobj)) {
+ if (init_named_config(config, PyUnicode_AsUTF8(configobj)) < 0) {
+ return -1;
+ }
+ }
+ else {
+ PyObject *dict = PyObject_GetAttrString(configobj, "__dict__");
+ if (dict == NULL) {
+ PyErr_Format(PyExc_TypeError, "bad config %R", configobj);
+ return -1;
+ }
+ int res = _PyInterpreterConfig_InitFromDict(config, dict);
+ Py_DECREF(dict);
+ if (res < 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+static int
+_run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags)
+{
+ PyObject *result = NULL;
+ if (flags & RUN_TEXT) {
+ result = PyRun_StringFlags(codestr, Py_file_input, ns, ns, NULL);
+ }
+ else if (flags & RUN_CODE) {
+ PyObject *code = PyMarshal_ReadObjectFromString(codestr, codestrlen);
+ if (code != NULL) {
+ result = PyEval_EvalCode(code, ns, ns);
+ Py_DECREF(code);
+ }
+ }
+ else {
+ Py_UNREACHABLE();
+ }
+ if (result == NULL) {
+ return -1;
+ }
+ Py_DECREF(result); // We throw away the result.
+ return 0;
+}
+
+static int
+_run_in_interpreter(PyInterpreterState *interp,
+ const char *codestr, Py_ssize_t codestrlen,
+ PyObject *shareables, int flags,
+ PyObject **p_excinfo)
+{
+ assert(!PyErr_Occurred());
+ _PyXI_session session = {0};
+
+ // Prep and switch interpreters.
+ if (_PyXI_Enter(&session, interp, shareables) < 0) {
+ assert(!PyErr_Occurred());
+ PyObject *excinfo = _PyXI_ApplyError(session.error);
+ if (excinfo != NULL) {
+ *p_excinfo = excinfo;
+ }
+ assert(PyErr_Occurred());
+ return -1;
+ }
+
+ // Run the script.
+ int res = _run_script(session.main_ns, codestr, codestrlen, flags);
+
+ // Clean up and switch back.
+ _PyXI_Exit(&session);
+
+ // Propagate any exception out to the caller.
+ assert(!PyErr_Occurred());
+ if (res < 0) {
+ PyObject *excinfo = _PyXI_ApplyCapturedException(&session);
+ if (excinfo != NULL) {
+ *p_excinfo = excinfo;
+ }
+ }
+ else {
+ assert(!_PyXI_HasCapturedException(&session));
+ }
+
+ return res;
+}
+
+
+/* module level code ********************************************************/
+
+static long
+get_whence(PyInterpreterState *interp)
+{
+ return _PyInterpreterState_GetWhence(interp);
+}
+
+
+static PyInterpreterState *
+resolve_interp(PyObject *idobj, int restricted, int reqready, const char *op)
+{
+ PyInterpreterState *interp;
+ if (idobj == NULL) {
+ interp = PyInterpreterState_Get();
+ }
+ else {
+ interp = look_up_interp(idobj);
+ if (interp == NULL) {
+ return NULL;
+ }
+ }
+
+ if (reqready && !_PyInterpreterState_IsReady(interp)) {
+ if (idobj == NULL) {
+ PyErr_Format(PyExc_InterpreterError,
+ "cannot %s current interpreter (not ready)", op);
+ }
+ else {
+ PyErr_Format(PyExc_InterpreterError,
+ "cannot %s interpreter %R (not ready)", op, idobj);
+ }
+ return NULL;
+ }
+
+ if (restricted && get_whence(interp) != _PyInterpreterState_WHENCE_STDLIB) {
+ if (idobj == NULL) {
+ PyErr_Format(PyExc_InterpreterError,
+ "cannot %s unrecognized current interpreter", op);
+ }
+ else {
+ PyErr_Format(PyExc_InterpreterError,
+ "cannot %s unrecognized interpreter %R", op, idobj);
+ }
+ return NULL;
+ }
+
+ return interp;
+}
+
+
+static PyObject *
+get_summary(PyInterpreterState *interp)
+{
+ PyObject *idobj = _PyInterpreterState_GetIDObject(interp);
+ if (idobj == NULL) {
+ return NULL;
+ }
+ PyObject *whenceobj = PyLong_FromLong(
+ get_whence(interp));
+ if (whenceobj == NULL) {
+ Py_DECREF(idobj);
+ return NULL;
+ }
+ PyObject *res = PyTuple_Pack(2, idobj, whenceobj);
+ Py_DECREF(idobj);
+ Py_DECREF(whenceobj);
+ return res;
+}
+
+
+static PyObject *
+interp_new_config(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ const char *name = NULL;
+ if (!PyArg_ParseTuple(args, "|s:" MODULE_NAME_STR ".new_config",
+ &name))
+ {
+ return NULL;
+ }
+ PyObject *overrides = kwds;
+
+ PyInterpreterConfig config;
+ if (init_named_config(&config, name) < 0) {
+ return NULL;
+ }
+
+ if (overrides != NULL && PyDict_GET_SIZE(overrides) > 0) {
+ if (_PyInterpreterConfig_UpdateFromDict(&config, overrides) < 0) {
+ return NULL;
+ }
+ }
+
+ PyObject *dict = _PyInterpreterConfig_AsDict(&config);
+ if (dict == NULL) {
+ return NULL;
+ }
+
+ PyObject *configobj = _PyNamespace_New(dict);
+ Py_DECREF(dict);
+ return configobj;
+}
+
+PyDoc_STRVAR(new_config_doc,
+"new_config(name='isolated', /, **overrides) -> type.SimpleNamespace\n\
+\n\
+Return a representation of a new PyInterpreterConfig.\n\
+\n\
+The name determines the initial values of the config. Supported named\n\
+configs are: default, isolated, legacy, and empty.\n\
+\n\
+Any keyword arguments are set on the corresponding config fields,\n\
+overriding the initial values.");
+
+
+static PyObject *
+interp_create(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = {"config", "reqrefs", NULL};
+ PyObject *configobj = NULL;
+ int reqrefs = 0;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O$p:create", kwlist,
+ &configobj, &reqrefs)) {
+ return NULL;
+ }
+
+ PyInterpreterConfig config;
+ if (config_from_object(configobj, &config) < 0) {
+ return NULL;
+ }
+
+ long whence = _PyInterpreterState_WHENCE_STDLIB;
+ PyInterpreterState *interp = \
+ _PyXI_NewInterpreter(&config, &whence, NULL, NULL);
+ if (interp == NULL) {
+ // XXX Move the chained exception to interpreters.create()?
+ PyObject *exc = PyErr_GetRaisedException();
+ assert(exc != NULL);
+ PyErr_SetString(PyExc_InterpreterError, "interpreter creation failed");
+ _PyErr_ChainExceptions1(exc);
+ return NULL;
+ }
+ assert(_PyInterpreterState_IsReady(interp));
+
+ PyObject *idobj = _PyInterpreterState_GetIDObject(interp);
+ if (idobj == NULL) {
+ _PyXI_EndInterpreter(interp, NULL, NULL);
+ return NULL;
+ }
+
+ if (reqrefs) {
+ // Decref to 0 will destroy the interpreter.
+ _PyInterpreterState_RequireIDRef(interp, 1);
+ }
+
+ return idobj;
+}
+
+
+PyDoc_STRVAR(create_doc,
+"create([config], *, reqrefs=False) -> ID\n\
+\n\
+Create a new interpreter and return a unique generated ID.\n\
+\n\
+The caller is responsible for destroying the interpreter before exiting,\n\
+typically by using _interpreters.destroy(). This can be managed \n\
+automatically by passing \"reqrefs=True\" and then using _incref() and\n\
+_decref()` appropriately.\n\
+\n\
+\"config\" must be a valid interpreter config or the name of a\n\
+predefined config (\"isolated\" or \"legacy\"). The default\n\
+is \"isolated\".");
+
+
+static PyObject *
+interp_destroy(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = {"id", "restrict", NULL};
+ PyObject *id;
+ int restricted = 0;
+ // XXX Use "L" for id?
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "O|$p:destroy", kwlist, &id, &restricted))
+ {
+ return NULL;
+ }
+
+ // Look up the interpreter.
+ int reqready = 0;
+ PyInterpreterState *interp = \
+ resolve_interp(id, restricted, reqready, "destroy");
+ if (interp == NULL) {
+ return NULL;
+ }
+
+ // Ensure we don't try to destroy the current interpreter.
+ PyInterpreterState *current = _get_current_interp();
+ if (current == NULL) {
+ return NULL;
+ }
+ if (interp == current) {
+ PyErr_SetString(PyExc_InterpreterError,
+ "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 (is_running_main(interp)) {
+ PyErr_Format(PyExc_InterpreterError, "interpreter running");
+ return NULL;
+ }
+
+ // Destroy the interpreter.
+ _PyXI_EndInterpreter(interp, NULL, NULL);
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(destroy_doc,
+"destroy(id, *, restrict=False)\n\
+\n\
+Destroy the identified interpreter.\n\
+\n\
+Attempting to destroy the current interpreter raises InterpreterError.\n\
+So does an unrecognized ID.");
+
+
+static PyObject *
+interp_list_all(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = {"require_ready", NULL};
+ int reqready = 0;
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+ "|$p:" MODULE_NAME_STR ".list_all",
+ kwlist, &reqready))
+ {
+ return NULL;
+ }
+
+ PyObject *ids = PyList_New(0);
+ if (ids == NULL) {
+ return NULL;
+ }
+
+ PyInterpreterState *interp = PyInterpreterState_Head();
+ while (interp != NULL) {
+ if (!reqready || _PyInterpreterState_IsReady(interp)) {
+ PyObject *item = get_summary(interp);
+ if (item == NULL) {
+ Py_DECREF(ids);
+ return NULL;
+ }
+
+ // insert at front of list
+ int res = PyList_Insert(ids, 0, item);
+ Py_DECREF(item);
+ if (res < 0) {
+ Py_DECREF(ids);
+ return NULL;
+ }
+ }
+ interp = PyInterpreterState_Next(interp);
+ }
+
+ return ids;
+}
+
+PyDoc_STRVAR(list_all_doc,
+"list_all() -> [(ID, whence)]\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_interp();
+ if (interp == NULL) {
+ return NULL;
+ }
+ assert(_PyInterpreterState_IsReady(interp));
+ return get_summary(interp);
+}
+
+PyDoc_STRVAR(get_current_doc,
+"get_current() -> (ID, whence)\n\
+\n\
+Return the ID of current interpreter.");
+
+
+static PyObject *
+interp_get_main(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ PyInterpreterState *interp = _PyInterpreterState_Main();
+ assert(_PyInterpreterState_IsReady(interp));
+ return get_summary(interp);
+}
+
+PyDoc_STRVAR(get_main_doc,
+"get_main() -> (ID, whence)\n\
+\n\
+Return the ID of main interpreter.");
+
+
+static PyObject *
+interp_set___main___attrs(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = {"id", "updates", "restrict", NULL};
+ PyObject *id, *updates;
+ int restricted = 0;
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+ "OO|$p:" MODULE_NAME_STR ".set___main___attrs",
+ kwlist, &id, &updates, &restricted))
+ {
+ return NULL;
+ }
+
+ // Look up the interpreter.
+ int reqready = 1;
+ PyInterpreterState *interp = \
+ resolve_interp(id, restricted, reqready, "update __main__ for");
+ if (interp == NULL) {
+ return NULL;
+ }
+
+ // Check the updates.
+ if (updates != Py_None) {
+ Py_ssize_t size = PyObject_Size(updates);
+ if (size < 0) {
+ return NULL;
+ }
+ if (size == 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "arg 2 must be a non-empty mapping");
+ return NULL;
+ }
+ }
+
+ _PyXI_session session = {0};
+
+ // Prep and switch interpreters, including apply the updates.
+ if (_PyXI_Enter(&session, interp, updates) < 0) {
+ if (!PyErr_Occurred()) {
+ _PyXI_ApplyCapturedException(&session);
+ assert(PyErr_Occurred());
+ }
+ else {
+ assert(!_PyXI_HasCapturedException(&session));
+ }
+ return NULL;
+ }
+
+ // Clean up and switch back.
+ _PyXI_Exit(&session);
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(set___main___attrs_doc,
+"set___main___attrs(id, ns, *, restrict=False)\n\
+\n\
+Bind the given attributes in the interpreter's __main__ module.");
+
+
+static PyUnicodeObject *
+convert_script_arg(PyObject *arg, const char *fname, const char *displayname,
+ const char *expected)
+{
+ PyUnicodeObject *str = NULL;
+ if (PyUnicode_CheckExact(arg)) {
+ str = (PyUnicodeObject *)Py_NewRef(arg);
+ }
+ else if (PyUnicode_Check(arg)) {
+ // XXX str = PyUnicode_FromObject(arg);
+ str = (PyUnicodeObject *)Py_NewRef(arg);
+ }
+ else {
+ _PyArg_BadArgument(fname, displayname, expected, arg);
+ return NULL;
+ }
+
+ const char *err = check_code_str(str);
+ if (err != NULL) {
+ Py_DECREF(str);
+ PyErr_Format(PyExc_ValueError,
+ "%.200s(): bad script text (%s)", fname, err);
+ return NULL;
+ }
+
+ return str;
+}
+
+static PyCodeObject *
+convert_code_arg(PyObject *arg, const char *fname, const char *displayname,
+ const char *expected)
+{
+ const char *kind = NULL;
+ PyCodeObject *code = NULL;
+ if (PyFunction_Check(arg)) {
+ if (PyFunction_GetClosure(arg) != NULL) {
+ PyErr_Format(PyExc_ValueError,
+ "%.200s(): closures not supported", fname);
+ return NULL;
+ }
+ code = (PyCodeObject *)PyFunction_GetCode(arg);
+ if (code == NULL) {
+ if (PyErr_Occurred()) {
+ // This chains.
+ PyErr_Format(PyExc_ValueError,
+ "%.200s(): bad func", fname);
+ }
+ else {
+ PyErr_Format(PyExc_ValueError,
+ "%.200s(): func.__code__ missing", fname);
+ }
+ return NULL;
+ }
+ Py_INCREF(code);
+ kind = "func";
+ }
+ else if (PyCode_Check(arg)) {
+ code = (PyCodeObject *)Py_NewRef(arg);
+ kind = "code object";
+ }
+ else {
+ _PyArg_BadArgument(fname, displayname, expected, arg);
+ return NULL;
+ }
+
+ const char *err = check_code_object(code);
+ if (err != NULL) {
+ Py_DECREF(code);
+ PyErr_Format(PyExc_ValueError,
+ "%.200s(): bad %s (%s)", fname, kind, err);
+ return NULL;
+ }
+
+ return code;
+}
+
+static int
+_interp_exec(PyObject *self, PyInterpreterState *interp,
+ PyObject *code_arg, PyObject *shared_arg, PyObject **p_excinfo)
+{
+ // Extract code.
+ Py_ssize_t codestrlen = -1;
+ PyObject *bytes_obj = NULL;
+ int flags = 0;
+ const char *codestr = get_code_str(code_arg,
+ &codestrlen, &bytes_obj, &flags);
+ if (codestr == NULL) {
+ return -1;
+ }
+
+ // Run the code in the interpreter.
+ int res = _run_in_interpreter(interp, codestr, codestrlen,
+ shared_arg, flags, p_excinfo);
+ Py_XDECREF(bytes_obj);
+ if (res < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static PyObject *
+interp_exec(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = {"id", "code", "shared", "restrict", NULL};
+ PyObject *id, *code;
+ PyObject *shared = NULL;
+ int restricted = 0;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "OO|O$p:" MODULE_NAME_STR ".exec", kwlist,
+ &id, &code, &shared, &restricted))
+ {
+ return NULL;
+ }
+
+ int reqready = 1;
+ PyInterpreterState *interp = \
+ resolve_interp(id, restricted, reqready, "exec code for");
+ if (interp == NULL) {
+ return NULL;
+ }
+
+ const char *expected = "a string, a function, or a code object";
+ if (PyUnicode_Check(code)) {
+ code = (PyObject *)convert_script_arg(code, MODULE_NAME_STR ".exec",
+ "argument 2", expected);
+ }
+ else {
+ code = (PyObject *)convert_code_arg(code, MODULE_NAME_STR ".exec",
+ "argument 2", expected);
+ }
+ if (code == NULL) {
+ return NULL;
+ }
+
+ PyObject *excinfo = NULL;
+ int res = _interp_exec(self, interp, code, shared, &excinfo);
+ Py_DECREF(code);
+ if (res < 0) {
+ assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
+ return excinfo;
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(exec_doc,
+"exec(id, code, shared=None, *, restrict=False)\n\
+\n\
+Execute the provided code in the identified interpreter.\n\
+This is equivalent to running the builtin exec() under the target\n\
+interpreter, using the __dict__ of its __main__ module as both\n\
+globals and locals.\n\
+\n\
+\"code\" may be a string containing the text of a Python script.\n\
+\n\
+Functions (and code objects) are also supported, with some restrictions.\n\
+The code/function must not take any arguments or be a closure\n\
+(i.e. have cell vars). Methods and other callables are not supported.\n\
+\n\
+If a function is provided, its code object is used and all its state\n\
+is ignored, including its __globals__ dict.");
+
+static PyObject *
+interp_call(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = {"id", "callable", "args", "kwargs",
+ "restrict", NULL};
+ PyObject *id, *callable;
+ PyObject *args_obj = NULL;
+ PyObject *kwargs_obj = NULL;
+ int restricted = 0;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "OO|OO$p:" MODULE_NAME_STR ".call", kwlist,
+ &id, &callable, &args_obj, &kwargs_obj,
+ &restricted))
+ {
+ return NULL;
+ }
+
+ int reqready = 1;
+ PyInterpreterState *interp = \
+ resolve_interp(id, restricted, reqready, "make a call in");
+ if (interp == NULL) {
+ return NULL;
+ }
+
+ if (args_obj != NULL) {
+ PyErr_SetString(PyExc_ValueError, "got unexpected args");
+ return NULL;
+ }
+ if (kwargs_obj != NULL) {
+ PyErr_SetString(PyExc_ValueError, "got unexpected kwargs");
+ return NULL;
+ }
+
+ PyObject *code = (PyObject *)convert_code_arg(callable, MODULE_NAME_STR ".call",
+ "argument 2", "a function");
+ if (code == NULL) {
+ return NULL;
+ }
+
+ PyObject *excinfo = NULL;
+ int res = _interp_exec(self, interp, code, NULL, &excinfo);
+ Py_DECREF(code);
+ if (res < 0) {
+ assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
+ return excinfo;
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(call_doc,
+"call(id, callable, args=None, kwargs=None, *, restrict=False)\n\
+\n\
+Call the provided object in the identified interpreter.\n\
+Pass the given args and kwargs, if possible.\n\
+\n\
+\"callable\" may be a plain function with no free vars that takes\n\
+no arguments.\n\
+\n\
+The function's code object is used and all its state\n\
+is ignored, including its __globals__ dict.");
+
+static PyObject *
+interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = {"id", "script", "shared", "restrict", NULL};
+ PyObject *id, *script;
+ PyObject *shared = NULL;
+ int restricted = 0;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "OU|O$p:" MODULE_NAME_STR ".run_string",
+ kwlist, &id, &script, &shared, &restricted))
+ {
+ return NULL;
+ }
+
+ int reqready = 1;
+ PyInterpreterState *interp = \
+ resolve_interp(id, restricted, reqready, "run a string in");
+ if (interp == NULL) {
+ return NULL;
+ }
+
+ script = (PyObject *)convert_script_arg(script, MODULE_NAME_STR ".exec",
+ "argument 2", "a string");
+ if (script == NULL) {
+ return NULL;
+ }
+
+ PyObject *excinfo = NULL;
+ int res = _interp_exec(self, interp, script, shared, &excinfo);
+ Py_DECREF(script);
+ if (res < 0) {
+ assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
+ return excinfo;
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(run_string_doc,
+"run_string(id, script, shared=None, *, restrict=False)\n\
+\n\
+Execute the provided string in the identified interpreter.\n\
+\n\
+(See " MODULE_NAME_STR ".exec().");
+
+static PyObject *
+interp_run_func(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = {"id", "func", "shared", "restrict", NULL};
+ PyObject *id, *func;
+ PyObject *shared = NULL;
+ int restricted = 0;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "OO|O$p:" MODULE_NAME_STR ".run_func",
+ kwlist, &id, &func, &shared, &restricted))
+ {
+ return NULL;
+ }
+
+ int reqready = 1;
+ PyInterpreterState *interp = \
+ resolve_interp(id, restricted, reqready, "run a function in");
+ if (interp == NULL) {
+ return NULL;
+ }
+
+ PyCodeObject *code = convert_code_arg(func, MODULE_NAME_STR ".exec",
+ "argument 2",
+ "a function or a code object");
+ if (code == NULL) {
+ return NULL;
+ }
+
+ PyObject *excinfo = NULL;
+ int res = _interp_exec(self, interp, (PyObject *)code, shared, &excinfo);
+ Py_DECREF(code);
+ if (res < 0) {
+ assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
+ return excinfo;
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(run_func_doc,
+"run_func(id, func, shared=None, *, restrict=False)\n\
+\n\
+Execute the body of the provided function in the identified interpreter.\n\
+Code objects are also supported. In both cases, closures and args\n\
+are not supported. Methods and other callables are not supported either.\n\
+\n\
+(See " MODULE_NAME_STR ".exec().");
+
+
+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", "restrict", NULL};
+ PyObject *id;
+ int restricted = 0;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "O|$p:is_running", kwlist,
+ &id, &restricted))
+ {
+ return NULL;
+ }
+
+ int reqready = 1;
+ PyInterpreterState *interp = \
+ resolve_interp(id, restricted, reqready, "check if running for");
+ if (interp == NULL) {
+ return NULL;
+ }
+
+ if (is_running_main(interp)) {
+ Py_RETURN_TRUE;
+ }
+ Py_RETURN_FALSE;
+}
+
+PyDoc_STRVAR(is_running_doc,
+"is_running(id, *, restrict=False) -> bool\n\
+\n\
+Return whether or not the identified interpreter is running.");
+
+
+static PyObject *
+interp_get_config(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = {"id", "restrict", NULL};
+ PyObject *idobj = NULL;
+ int restricted = 0;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "O|$p:get_config", kwlist,
+ &idobj, &restricted))
+ {
+ return NULL;
+ }
+ if (idobj == Py_None) {
+ idobj = NULL;
+ }
+
+ int reqready = 0;
+ PyInterpreterState *interp = \
+ resolve_interp(idobj, restricted, reqready, "get the config of");
+ if (interp == NULL) {
+ return NULL;
+ }
+
+ PyInterpreterConfig config;
+ if (_PyInterpreterConfig_InitFromState(&config, interp) < 0) {
+ return NULL;
+ }
+ PyObject *dict = _PyInterpreterConfig_AsDict(&config);
+ if (dict == NULL) {
+ return NULL;
+ }
+
+ PyObject *configobj = _PyNamespace_New(dict);
+ Py_DECREF(dict);
+ return configobj;
+}
+
+PyDoc_STRVAR(get_config_doc,
+"get_config(id, *, restrict=False) -> types.SimpleNamespace\n\
+\n\
+Return a representation of the config used to initialize the interpreter.");
+
+
+static PyObject *
+interp_whence(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = {"id", NULL};
+ PyObject *id;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "O:whence", kwlist, &id))
+ {
+ return NULL;
+ }
+
+ PyInterpreterState *interp = look_up_interp(id);
+ if (interp == NULL) {
+ return NULL;
+ }
+
+ long whence = get_whence(interp);
+ return PyLong_FromLong(whence);
+}
+
+PyDoc_STRVAR(whence_doc,
+"whence(id) -> int\n\
+\n\
+Return an identifier for where the interpreter was created.");
+
+
+static PyObject *
+interp_incref(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = {"id", "implieslink", "restrict", NULL};
+ PyObject *id;
+ int implieslink = 0;
+ int restricted = 0;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "O|$pp:incref", kwlist,
+ &id, &implieslink, &restricted))
+ {
+ return NULL;
+ }
+
+ int reqready = 1;
+ PyInterpreterState *interp = \
+ resolve_interp(id, restricted, reqready, "incref");
+ if (interp == NULL) {
+ return NULL;
+ }
+
+ if (implieslink) {
+ // Decref to 0 will destroy the interpreter.
+ _PyInterpreterState_RequireIDRef(interp, 1);
+ }
+ _PyInterpreterState_IDIncref(interp);
+
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *
+interp_decref(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = {"id", "restrict", NULL};
+ PyObject *id;
+ int restricted = 0;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "O|$p:decref", kwlist, &id, &restricted))
+ {
+ return NULL;
+ }
+
+ int reqready = 1;
+ PyInterpreterState *interp = \
+ resolve_interp(id, restricted, reqready, "decref");
+ if (interp == NULL) {
+ return NULL;
+ }
+
+ _PyInterpreterState_IDDecref(interp);
+
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *
+capture_exception(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = {"exc", NULL};
+ PyObject *exc_arg = NULL;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "|O:capture_exception", kwlist,
+ &exc_arg))
+ {
+ return NULL;
+ }
+
+ PyObject *exc = exc_arg;
+ if (exc == NULL || exc == Py_None) {
+ exc = PyErr_GetRaisedException();
+ if (exc == NULL) {
+ Py_RETURN_NONE;
+ }
+ }
+ else if (!PyExceptionInstance_Check(exc)) {
+ PyErr_Format(PyExc_TypeError, "expected exception, got %R", exc);
+ return NULL;
+ }
+ PyObject *captured = NULL;
+
+ _PyXI_excinfo info = {0};
+ if (_PyXI_InitExcInfo(&info, exc) < 0) {
+ goto finally;
+ }
+ captured = _PyXI_ExcInfoAsObject(&info);
+ if (captured == NULL) {
+ goto finally;
+ }
+
+ PyObject *formatted = _PyXI_FormatExcInfo(&info);
+ if (formatted == NULL) {
+ Py_CLEAR(captured);
+ goto finally;
+ }
+ int res = PyObject_SetAttrString(captured, "formatted", formatted);
+ Py_DECREF(formatted);
+ if (res < 0) {
+ Py_CLEAR(captured);
+ goto finally;
+ }
+
+finally:
+ _PyXI_ClearExcInfo(&info);
+ if (exc != exc_arg) {
+ if (PyErr_Occurred()) {
+ PyErr_SetRaisedException(exc);
+ }
+ else {
+ _PyErr_ChainExceptions1(exc);
+ }
+ }
+ return captured;
+}
+
+PyDoc_STRVAR(capture_exception_doc,
+"capture_exception(exc=None) -> types.SimpleNamespace\n\
+\n\
+Return a snapshot of an exception. If \"exc\" is None\n\
+then the current exception, if any, is used (but not cleared).\n\
+\n\
+The returned snapshot is the same as what _interpreters.exec() returns.");
+
+
+static PyMethodDef module_functions[] = {
+ {"new_config", _PyCFunction_CAST(interp_new_config),
+ METH_VARARGS | METH_KEYWORDS, new_config_doc},
+
+ {"create", _PyCFunction_CAST(interp_create),
+ METH_VARARGS | METH_KEYWORDS, create_doc},
+ {"destroy", _PyCFunction_CAST(interp_destroy),
+ METH_VARARGS | METH_KEYWORDS, destroy_doc},
+ {"list_all", _PyCFunction_CAST(interp_list_all),
+ METH_VARARGS | METH_KEYWORDS, 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_CAST(interp_is_running),
+ METH_VARARGS | METH_KEYWORDS, is_running_doc},
+ {"get_config", _PyCFunction_CAST(interp_get_config),
+ METH_VARARGS | METH_KEYWORDS, get_config_doc},
+ {"whence", _PyCFunction_CAST(interp_whence),
+ METH_VARARGS | METH_KEYWORDS, whence_doc},
+ {"exec", _PyCFunction_CAST(interp_exec),
+ METH_VARARGS | METH_KEYWORDS, exec_doc},
+ {"call", _PyCFunction_CAST(interp_call),
+ METH_VARARGS | METH_KEYWORDS, call_doc},
+ {"run_string", _PyCFunction_CAST(interp_run_string),
+ METH_VARARGS | METH_KEYWORDS, run_string_doc},
+ {"run_func", _PyCFunction_CAST(interp_run_func),
+ METH_VARARGS | METH_KEYWORDS, run_func_doc},
+
+ {"set___main___attrs", _PyCFunction_CAST(interp_set___main___attrs),
+ METH_VARARGS | METH_KEYWORDS, set___main___attrs_doc},
+
+ {"incref", _PyCFunction_CAST(interp_incref),
+ METH_VARARGS | METH_KEYWORDS, NULL},
+ {"decref", _PyCFunction_CAST(interp_decref),
+ METH_VARARGS | METH_KEYWORDS, NULL},
+
+ {"is_shareable", _PyCFunction_CAST(object_is_shareable),
+ METH_VARARGS | METH_KEYWORDS, is_shareable_doc},
+
+ {"capture_exception", _PyCFunction_CAST(capture_exception),
+ METH_VARARGS | METH_KEYWORDS, capture_exception_doc},
+
+ {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 int
+module_exec(PyObject *mod)
+{
+ PyInterpreterState *interp = PyInterpreterState_Get();
+ module_state *state = get_module_state(mod);
+
+#define ADD_WHENCE(NAME) \
+ if (PyModule_AddIntConstant(mod, "WHENCE_" #NAME, \
+ _PyInterpreterState_WHENCE_##NAME) < 0) \
+ { \
+ goto error; \
+ }
+ ADD_WHENCE(UNKNOWN)
+ ADD_WHENCE(RUNTIME)
+ ADD_WHENCE(LEGACY_CAPI)
+ ADD_WHENCE(CAPI)
+ ADD_WHENCE(XI)
+ ADD_WHENCE(STDLIB)
+#undef ADD_WHENCE
+
+ // exceptions
+ if (PyModule_AddType(mod, (PyTypeObject *)PyExc_InterpreterError) < 0) {
+ goto error;
+ }
+ if (PyModule_AddType(mod, (PyTypeObject *)PyExc_InterpreterNotFoundError) < 0) {
+ goto error;
+ }
+ PyObject *PyExc_NotShareableError = \
+ _PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError;
+ if (PyModule_AddType(mod, (PyTypeObject *)PyExc_NotShareableError) < 0) {
+ goto error;
+ }
+
+ if (register_memoryview_xid(mod, &state->XIBufferViewType) < 0) {
+ goto error;
+ }
+
+ return 0;
+
+error:
+ return -1;
+}
+
+static struct PyModuleDef_Slot module_slots[] = {
+ {Py_mod_exec, module_exec},
+ {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
+ {0, NULL},
+};
+
+static int
+module_traverse(PyObject *mod, visitproc visit, void *arg)
+{
+ module_state *state = get_module_state(mod);
+ assert(state != NULL);
+ traverse_module_state(state, visit, arg);
+ return 0;
+}
+
+static int
+module_clear(PyObject *mod)
+{
+ module_state *state = get_module_state(mod);
+ assert(state != NULL);
+ clear_module_state(state);
+ return 0;
+}
+
+static void
+module_free(void *mod)
+{
+ module_state *state = get_module_state(mod);
+ assert(state != NULL);
+ clear_module_state(state);
+}
+
+static struct PyModuleDef moduledef = {
+ .m_base = PyModuleDef_HEAD_INIT,
+ .m_name = MODULE_NAME_STR,
+ .m_doc = module_doc,
+ .m_size = sizeof(module_state),
+ .m_methods = module_functions,
+ .m_slots = module_slots,
+ .m_traverse = module_traverse,
+ .m_clear = module_clear,
+ .m_free = (freefunc)module_free,
+};
+
+PyMODINIT_FUNC
+MODINIT_FUNC_NAME(void)
+{
+ return PyModuleDef_Init(&moduledef);
+}