aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Python/crossinterp_data_lookup.h
diff options
context:
space:
mode:
authorEric Snow <ericsnowcurrently@gmail.com>2024-02-13 14:56:49 -0700
committerGitHub <noreply@github.com>2024-02-13 14:56:49 -0700
commit514b1c91b8651e8ab9129a34b7482033d2fd4e5b (patch)
tree11a091856f2b7f3ec65009b5b9de4e424a2a79bc /Python/crossinterp_data_lookup.h
parent206f73dc5f1b4c3c81119808aa7fd9038661cf90 (diff)
downloadcpython-514b1c91b8651e8ab9129a34b7482033d2fd4e5b.tar.gz
cpython-514b1c91b8651e8ab9129a34b7482033d2fd4e5b.zip
gh-76785: Improved Subinterpreters Compatibility with 3.12 (gh-115424)
For the most part, these changes make is substantially easier to backport subinterpreter-related code to 3.12, especially the related modules (e.g. _xxsubinterpreters). The main motivation is to support releasing a PyPI package with the 3.13 capabilities compiled for 3.12. A lot of the changes here involve either hiding details behind macros/functions or splitting up some files.
Diffstat (limited to 'Python/crossinterp_data_lookup.h')
-rw-r--r--Python/crossinterp_data_lookup.h594
1 files changed, 594 insertions, 0 deletions
diff --git a/Python/crossinterp_data_lookup.h b/Python/crossinterp_data_lookup.h
new file mode 100644
index 00000000000..863919ad42f
--- /dev/null
+++ b/Python/crossinterp_data_lookup.h
@@ -0,0 +1,594 @@
+
+static crossinterpdatafunc _lookup_getdata_from_registry(
+ PyInterpreterState *, PyObject *);
+
+static crossinterpdatafunc
+lookup_getdata(PyInterpreterState *interp, PyObject *obj)
+{
+ /* Cross-interpreter objects are looked up by exact match on the class.
+ We can reassess this policy when we move from a global registry to a
+ tp_* slot. */
+ return _lookup_getdata_from_registry(interp, obj);
+}
+
+crossinterpdatafunc
+_PyCrossInterpreterData_Lookup(PyObject *obj)
+{
+ PyInterpreterState *interp = PyInterpreterState_Get();
+ return lookup_getdata(interp, obj);
+}
+
+
+/***********************************************/
+/* a registry of {type -> crossinterpdatafunc} */
+/***********************************************/
+
+/* For now we use a global registry of shareable classes. An
+ alternative would be to add a tp_* slot for a class's
+ crossinterpdatafunc. It would be simpler and more efficient. */
+
+
+/* registry lifecycle */
+
+static void _register_builtins_for_crossinterpreter_data(struct _xidregistry *);
+
+static void
+_xidregistry_init(struct _xidregistry *registry)
+{
+ if (registry->initialized) {
+ return;
+ }
+ registry->initialized = 1;
+
+ if (registry->global) {
+ // Registering the builtins is cheap so we don't bother doing it lazily.
+ assert(registry->head == NULL);
+ _register_builtins_for_crossinterpreter_data(registry);
+ }
+}
+
+static void _xidregistry_clear(struct _xidregistry *);
+
+static void
+_xidregistry_fini(struct _xidregistry *registry)
+{
+ if (!registry->initialized) {
+ return;
+ }
+ registry->initialized = 0;
+
+ _xidregistry_clear(registry);
+}
+
+static inline struct _xidregistry * _get_global_xidregistry(_PyRuntimeState *);
+static inline struct _xidregistry * _get_xidregistry(PyInterpreterState *);
+
+static void
+xid_lookup_init(PyInterpreterState *interp)
+{
+ if (_Py_IsMainInterpreter(interp)) {
+ _xidregistry_init(_get_global_xidregistry(interp->runtime));
+ }
+ _xidregistry_init(_get_xidregistry(interp));
+}
+
+static void
+xid_lookup_fini(PyInterpreterState *interp)
+{
+ _xidregistry_fini(_get_xidregistry(interp));
+ if (_Py_IsMainInterpreter(interp)) {
+ _xidregistry_fini(_get_global_xidregistry(interp->runtime));
+ }
+}
+
+
+/* registry thread safety */
+
+static void
+_xidregistry_lock(struct _xidregistry *registry)
+{
+ if (registry->global) {
+ PyMutex_Lock(&registry->mutex);
+ }
+ // else: Within an interpreter we rely on the GIL instead of a separate lock.
+}
+
+static void
+_xidregistry_unlock(struct _xidregistry *registry)
+{
+ if (registry->global) {
+ PyMutex_Unlock(&registry->mutex);
+ }
+}
+
+
+/* accessing the registry */
+
+static inline struct _xidregistry *
+_get_global_xidregistry(_PyRuntimeState *runtime)
+{
+ return &runtime->xi.registry;
+}
+
+static inline struct _xidregistry *
+_get_xidregistry(PyInterpreterState *interp)
+{
+ return &interp->xi.registry;
+}
+
+static inline struct _xidregistry *
+_get_xidregistry_for_type(PyInterpreterState *interp, PyTypeObject *cls)
+{
+ struct _xidregistry *registry = _get_global_xidregistry(interp->runtime);
+ if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) {
+ registry = _get_xidregistry(interp);
+ }
+ return registry;
+}
+
+static struct _xidregitem * _xidregistry_remove_entry(
+ struct _xidregistry *, struct _xidregitem *);
+
+static struct _xidregitem *
+_xidregistry_find_type(struct _xidregistry *xidregistry, PyTypeObject *cls)
+{
+ struct _xidregitem *cur = xidregistry->head;
+ while (cur != NULL) {
+ if (cur->weakref != NULL) {
+ // cur is/was a heap type.
+ PyObject *registered = _PyWeakref_GET_REF(cur->weakref);
+ if (registered == NULL) {
+ // The weakly ref'ed object was freed.
+ cur = _xidregistry_remove_entry(xidregistry, cur);
+ continue;
+ }
+ assert(PyType_Check(registered));
+ assert(cur->cls == (PyTypeObject *)registered);
+ assert(cur->cls->tp_flags & Py_TPFLAGS_HEAPTYPE);
+ Py_DECREF(registered);
+ }
+ if (cur->cls == cls) {
+ return cur;
+ }
+ cur = cur->next;
+ }
+ return NULL;
+}
+
+static crossinterpdatafunc
+_lookup_getdata_from_registry(PyInterpreterState *interp, PyObject *obj)
+{
+ PyTypeObject *cls = Py_TYPE(obj);
+
+ struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls);
+ _xidregistry_lock(xidregistry);
+
+ struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
+ crossinterpdatafunc func = matched != NULL ? matched->getdata : NULL;
+
+ _xidregistry_unlock(xidregistry);
+ return func;
+}
+
+
+/* updating the registry */
+
+static int
+_xidregistry_add_type(struct _xidregistry *xidregistry,
+ PyTypeObject *cls, crossinterpdatafunc getdata)
+{
+ struct _xidregitem *newhead = PyMem_RawMalloc(sizeof(struct _xidregitem));
+ if (newhead == NULL) {
+ return -1;
+ }
+ *newhead = (struct _xidregitem){
+ // We do not keep a reference, to avoid keeping the class alive.
+ .cls = cls,
+ .refcount = 1,
+ .getdata = getdata,
+ };
+ if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) {
+ // XXX Assign a callback to clear the entry from the registry?
+ newhead->weakref = PyWeakref_NewRef((PyObject *)cls, NULL);
+ if (newhead->weakref == NULL) {
+ PyMem_RawFree(newhead);
+ return -1;
+ }
+ }
+ newhead->next = xidregistry->head;
+ if (newhead->next != NULL) {
+ newhead->next->prev = newhead;
+ }
+ xidregistry->head = newhead;
+ return 0;
+}
+
+static struct _xidregitem *
+_xidregistry_remove_entry(struct _xidregistry *xidregistry,
+ struct _xidregitem *entry)
+{
+ struct _xidregitem *next = entry->next;
+ if (entry->prev != NULL) {
+ assert(entry->prev->next == entry);
+ entry->prev->next = next;
+ }
+ else {
+ assert(xidregistry->head == entry);
+ xidregistry->head = next;
+ }
+ if (next != NULL) {
+ next->prev = entry->prev;
+ }
+ Py_XDECREF(entry->weakref);
+ PyMem_RawFree(entry);
+ return next;
+}
+
+static void
+_xidregistry_clear(struct _xidregistry *xidregistry)
+{
+ struct _xidregitem *cur = xidregistry->head;
+ xidregistry->head = NULL;
+ while (cur != NULL) {
+ struct _xidregitem *next = cur->next;
+ Py_XDECREF(cur->weakref);
+ PyMem_RawFree(cur);
+ cur = next;
+ }
+}
+
+int
+_PyCrossInterpreterData_RegisterClass(PyTypeObject *cls,
+ crossinterpdatafunc getdata)
+{
+ if (!PyType_Check(cls)) {
+ PyErr_Format(PyExc_ValueError, "only classes may be registered");
+ return -1;
+ }
+ if (getdata == NULL) {
+ PyErr_Format(PyExc_ValueError, "missing 'getdata' func");
+ return -1;
+ }
+
+ int res = 0;
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls);
+ _xidregistry_lock(xidregistry);
+
+ struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
+ if (matched != NULL) {
+ assert(matched->getdata == getdata);
+ matched->refcount += 1;
+ goto finally;
+ }
+
+ res = _xidregistry_add_type(xidregistry, cls, getdata);
+
+finally:
+ _xidregistry_unlock(xidregistry);
+ return res;
+}
+
+int
+_PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls)
+{
+ int res = 0;
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls);
+ _xidregistry_lock(xidregistry);
+
+ struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
+ if (matched != NULL) {
+ assert(matched->refcount > 0);
+ matched->refcount -= 1;
+ if (matched->refcount == 0) {
+ (void)_xidregistry_remove_entry(xidregistry, matched);
+ }
+ res = 1;
+ }
+
+ _xidregistry_unlock(xidregistry);
+ return res;
+}
+
+
+/********************************************/
+/* cross-interpreter data for builtin types */
+/********************************************/
+
+// bytes
+
+struct _shared_bytes_data {
+ char *bytes;
+ Py_ssize_t len;
+};
+
+static PyObject *
+_new_bytes_object(_PyCrossInterpreterData *data)
+{
+ struct _shared_bytes_data *shared = (struct _shared_bytes_data *)(data->data);
+ return PyBytes_FromStringAndSize(shared->bytes, shared->len);
+}
+
+static int
+_bytes_shared(PyThreadState *tstate, PyObject *obj,
+ _PyCrossInterpreterData *data)
+{
+ if (_PyCrossInterpreterData_InitWithSize(
+ data, tstate->interp, sizeof(struct _shared_bytes_data), obj,
+ _new_bytes_object
+ ) < 0)
+ {
+ return -1;
+ }
+ struct _shared_bytes_data *shared = (struct _shared_bytes_data *)data->data;
+ if (PyBytes_AsStringAndSize(obj, &shared->bytes, &shared->len) < 0) {
+ _PyCrossInterpreterData_Clear(tstate->interp, data);
+ return -1;
+ }
+ return 0;
+}
+
+// str
+
+struct _shared_str_data {
+ int kind;
+ const void *buffer;
+ Py_ssize_t len;
+};
+
+static PyObject *
+_new_str_object(_PyCrossInterpreterData *data)
+{
+ struct _shared_str_data *shared = (struct _shared_str_data *)(data->data);
+ return PyUnicode_FromKindAndData(shared->kind, shared->buffer, shared->len);
+}
+
+static int
+_str_shared(PyThreadState *tstate, PyObject *obj,
+ _PyCrossInterpreterData *data)
+{
+ if (_PyCrossInterpreterData_InitWithSize(
+ data, tstate->interp, sizeof(struct _shared_str_data), obj,
+ _new_str_object
+ ) < 0)
+ {
+ return -1;
+ }
+ struct _shared_str_data *shared = (struct _shared_str_data *)data->data;
+ shared->kind = PyUnicode_KIND(obj);
+ shared->buffer = PyUnicode_DATA(obj);
+ shared->len = PyUnicode_GET_LENGTH(obj);
+ return 0;
+}
+
+// int
+
+static PyObject *
+_new_long_object(_PyCrossInterpreterData *data)
+{
+ return PyLong_FromSsize_t((Py_ssize_t)(data->data));
+}
+
+static int
+_long_shared(PyThreadState *tstate, PyObject *obj,
+ _PyCrossInterpreterData *data)
+{
+ /* Note that this means the size of shareable ints is bounded by
+ * sys.maxsize. Hence on 32-bit architectures that is half the
+ * size of maximum shareable ints on 64-bit.
+ */
+ Py_ssize_t value = PyLong_AsSsize_t(obj);
+ if (value == -1 && PyErr_Occurred()) {
+ if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
+ PyErr_SetString(PyExc_OverflowError, "try sending as bytes");
+ }
+ return -1;
+ }
+ _PyCrossInterpreterData_Init(data, tstate->interp, (void *)value, NULL,
+ _new_long_object);
+ // data->obj and data->free remain NULL
+ return 0;
+}
+
+// float
+
+static PyObject *
+_new_float_object(_PyCrossInterpreterData *data)
+{
+ double * value_ptr = data->data;
+ return PyFloat_FromDouble(*value_ptr);
+}
+
+static int
+_float_shared(PyThreadState *tstate, PyObject *obj,
+ _PyCrossInterpreterData *data)
+{
+ if (_PyCrossInterpreterData_InitWithSize(
+ data, tstate->interp, sizeof(double), NULL,
+ _new_float_object
+ ) < 0)
+ {
+ return -1;
+ }
+ double *shared = (double *)data->data;
+ *shared = PyFloat_AsDouble(obj);
+ return 0;
+}
+
+// None
+
+static PyObject *
+_new_none_object(_PyCrossInterpreterData *data)
+{
+ // XXX Singleton refcounts are problematic across interpreters...
+ return Py_NewRef(Py_None);
+}
+
+static int
+_none_shared(PyThreadState *tstate, PyObject *obj,
+ _PyCrossInterpreterData *data)
+{
+ _PyCrossInterpreterData_Init(data, tstate->interp, NULL, NULL,
+ _new_none_object);
+ // data->data, data->obj and data->free remain NULL
+ return 0;
+}
+
+// bool
+
+static PyObject *
+_new_bool_object(_PyCrossInterpreterData *data)
+{
+ if (data->data){
+ Py_RETURN_TRUE;
+ }
+ Py_RETURN_FALSE;
+}
+
+static int
+_bool_shared(PyThreadState *tstate, PyObject *obj,
+ _PyCrossInterpreterData *data)
+{
+ _PyCrossInterpreterData_Init(data, tstate->interp,
+ (void *) (Py_IsTrue(obj) ? (uintptr_t) 1 : (uintptr_t) 0), NULL,
+ _new_bool_object);
+ // data->obj and data->free remain NULL
+ return 0;
+}
+
+// tuple
+
+struct _shared_tuple_data {
+ Py_ssize_t len;
+ _PyCrossInterpreterData **data;
+};
+
+static PyObject *
+_new_tuple_object(_PyCrossInterpreterData *data)
+{
+ struct _shared_tuple_data *shared = (struct _shared_tuple_data *)(data->data);
+ PyObject *tuple = PyTuple_New(shared->len);
+ if (tuple == NULL) {
+ return NULL;
+ }
+
+ for (Py_ssize_t i = 0; i < shared->len; i++) {
+ PyObject *item = _PyCrossInterpreterData_NewObject(shared->data[i]);
+ if (item == NULL){
+ Py_DECREF(tuple);
+ return NULL;
+ }
+ PyTuple_SET_ITEM(tuple, i, item);
+ }
+ return tuple;
+}
+
+static void
+_tuple_shared_free(void* data)
+{
+ struct _shared_tuple_data *shared = (struct _shared_tuple_data *)(data);
+#ifndef NDEBUG
+ int64_t interpid = PyInterpreterState_GetID(_PyInterpreterState_GET());
+#endif
+ for (Py_ssize_t i = 0; i < shared->len; i++) {
+ if (shared->data[i] != NULL) {
+ assert(_PyCrossInterpreterData_INTERPID(shared->data[i]) == interpid);
+ _PyCrossInterpreterData_Release(shared->data[i]);
+ PyMem_RawFree(shared->data[i]);
+ shared->data[i] = NULL;
+ }
+ }
+ PyMem_Free(shared->data);
+ PyMem_RawFree(shared);
+}
+
+static int
+_tuple_shared(PyThreadState *tstate, PyObject *obj,
+ _PyCrossInterpreterData *data)
+{
+ Py_ssize_t len = PyTuple_GET_SIZE(obj);
+ if (len < 0) {
+ return -1;
+ }
+ struct _shared_tuple_data *shared = PyMem_RawMalloc(sizeof(struct _shared_tuple_data));
+ if (shared == NULL){
+ PyErr_NoMemory();
+ return -1;
+ }
+
+ shared->len = len;
+ shared->data = (_PyCrossInterpreterData **) PyMem_Calloc(shared->len, sizeof(_PyCrossInterpreterData *));
+ if (shared->data == NULL) {
+ PyErr_NoMemory();
+ return -1;
+ }
+
+ for (Py_ssize_t i = 0; i < shared->len; i++) {
+ _PyCrossInterpreterData *data = _PyCrossInterpreterData_New();
+ if (data == NULL) {
+ goto error; // PyErr_NoMemory already set
+ }
+ PyObject *item = PyTuple_GET_ITEM(obj, i);
+
+ int res = -1;
+ if (!_Py_EnterRecursiveCallTstate(tstate, " while sharing a tuple")) {
+ res = _PyObject_GetCrossInterpreterData(item, data);
+ _Py_LeaveRecursiveCallTstate(tstate);
+ }
+ if (res < 0) {
+ PyMem_RawFree(data);
+ goto error;
+ }
+ shared->data[i] = data;
+ }
+ _PyCrossInterpreterData_Init(
+ data, tstate->interp, shared, obj, _new_tuple_object);
+ data->free = _tuple_shared_free;
+ return 0;
+
+error:
+ _tuple_shared_free(shared);
+ return -1;
+}
+
+// registration
+
+static void
+_register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry)
+{
+ // None
+ if (_xidregistry_add_type(xidregistry, (PyTypeObject *)PyObject_Type(Py_None), _none_shared) != 0) {
+ Py_FatalError("could not register None for cross-interpreter sharing");
+ }
+
+ // int
+ if (_xidregistry_add_type(xidregistry, &PyLong_Type, _long_shared) != 0) {
+ Py_FatalError("could not register int for cross-interpreter sharing");
+ }
+
+ // bytes
+ if (_xidregistry_add_type(xidregistry, &PyBytes_Type, _bytes_shared) != 0) {
+ Py_FatalError("could not register bytes for cross-interpreter sharing");
+ }
+
+ // str
+ if (_xidregistry_add_type(xidregistry, &PyUnicode_Type, _str_shared) != 0) {
+ Py_FatalError("could not register str for cross-interpreter sharing");
+ }
+
+ // bool
+ if (_xidregistry_add_type(xidregistry, &PyBool_Type, _bool_shared) != 0) {
+ Py_FatalError("could not register bool for cross-interpreter sharing");
+ }
+
+ // float
+ if (_xidregistry_add_type(xidregistry, &PyFloat_Type, _float_shared) != 0) {
+ Py_FatalError("could not register float for cross-interpreter sharing");
+ }
+
+ // tuple
+ if (_xidregistry_add_type(xidregistry, &PyTuple_Type, _tuple_shared) != 0) {
+ Py_FatalError("could not register tuple for cross-interpreter sharing");
+ }
+}