diff --git a/Doc/data/python3.12.abi b/Doc/data/python3.12.abi
index b3a07fdf9bd19b..ab5190f6966c02 100644
--- a/Doc/data/python3.12.abi
+++ b/Doc/data/python3.12.abi
@@ -1710,7 +1710,7 @@
-
+
@@ -2544,7 +2544,7 @@
-
+
@@ -7566,19 +7566,19 @@
-
+
-
+
-
+
-
+
@@ -10912,10 +10912,10 @@
-
+
-
+
@@ -13496,10 +13496,10 @@
-
+
-
+
@@ -13528,7 +13528,7 @@
-
+
@@ -13543,7 +13543,7 @@
-
+
@@ -13564,13 +13564,13 @@
-
+
-
+
-
+
@@ -13606,7 +13606,7 @@
-
+
@@ -13707,7 +13707,7 @@
-
+
@@ -14307,7 +14307,7 @@
-
+
@@ -14367,7 +14367,7 @@
-
+
@@ -14421,7 +14421,7 @@
-
+
@@ -14580,7 +14580,7 @@
-
+
@@ -14664,7 +14664,7 @@
-
+
@@ -15147,7 +15147,7 @@
-
+
@@ -15282,7 +15282,7 @@
-
+
@@ -15366,7 +15366,7 @@
-
+
@@ -15438,7 +15438,7 @@
-
+
@@ -15744,7 +15744,7 @@
-
+
@@ -15881,7 +15881,7 @@
-
+
@@ -16070,253 +16070,270 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -16324,68 +16341,68 @@
-
+
-
+
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
@@ -16394,17 +16411,17 @@
-
+
-
+
-
+
@@ -16428,34 +16445,34 @@
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
-
+
+
-
+
@@ -16464,7 +16481,7 @@
-
+
@@ -16472,42 +16489,42 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -16515,22 +16532,22 @@
-
-
+
+
-
+
-
+
-
+
@@ -16541,34 +16558,34 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -16576,7 +16593,7 @@
-
+
@@ -16584,9 +16601,9 @@
-
+
-
+
@@ -16595,8 +16612,8 @@
-
-
+
+
@@ -16616,109 +16633,109 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -16732,20 +16749,12 @@
-
-
-
-
-
-
-
-
-
+
-
+
-
+
@@ -16880,7 +16889,7 @@
-
+
@@ -16930,7 +16939,7 @@
-
+
@@ -16989,7 +16998,7 @@
-
+
@@ -17000,7 +17009,7 @@
-
+
@@ -17020,8 +17029,8 @@
-
-
+
+
@@ -17056,7 +17065,7 @@
-
+
@@ -17064,7 +17073,7 @@
-
+
@@ -17113,7 +17122,7 @@
-
+
@@ -17353,8 +17362,8 @@
-
-
+
+
@@ -17378,7 +17387,7 @@
-
+
@@ -17392,7 +17401,7 @@
-
+
@@ -17405,10 +17414,10 @@
-
-
-
-
+
+
+
+
@@ -17430,10 +17439,10 @@
+
+
-
-
-
+
@@ -19605,7 +19614,7 @@
-
+
@@ -20634,7 +20643,7 @@
-
+
@@ -23516,7 +23525,7 @@
-
+
@@ -24765,7 +24774,7 @@
-
+
@@ -24954,10 +24963,10 @@
-
+
-
+
@@ -25158,12 +25167,12 @@
-
-
+
+
-
+
@@ -25516,158 +25525,158 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
@@ -26273,7 +26282,7 @@
-
+
@@ -26325,11 +26334,11 @@
-
+
-
+
diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h
index b5d947cbb6ca9d..37cc88ed081b72 100644
--- a/Include/internal/pycore_interp.h
+++ b/Include/internal/pycore_interp.h
@@ -39,6 +39,32 @@ struct _Py_long_state {
int max_str_digits;
};
+
+/* cross-interpreter data registry */
+
+/* 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. */
+
+struct _xidregitem;
+
+struct _xidregitem {
+ struct _xidregitem *prev;
+ struct _xidregitem *next;
+ /* This can be a dangling pointer, but only if weakref is set. */
+ PyTypeObject *cls;
+ /* This is NULL for builtin types. */
+ PyObject *weakref;
+ size_t refcount;
+ crossinterpdatafunc getdata;
+};
+
+struct _xidregistry {
+ PyThread_type_lock mutex;
+ struct _xidregitem *head;
+};
+
+
/* interpreter state */
/* PyInterpreterState holds the global state for one of the runtime's
@@ -194,6 +220,8 @@ struct _is {
struct _Py_interp_cached_objects cached_objects;
struct _Py_interp_static_objects static_objects;
+ // XXX Remove this field once we have a tp_* slot.
+ struct _xidregistry xidregistry;
/* The thread currently executing in the __main__ module, if any. */
PyThreadState *threads_main;
/* The ID of the OS thread in which we are finalizing.
@@ -235,21 +263,6 @@ _PyInterpreterState_SetFinalizing(PyInterpreterState *interp, PyThreadState *tst
}
-/* cross-interpreter data registry */
-
-/* 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. */
-
-struct _xidregitem;
-
-struct _xidregitem {
- struct _xidregitem *prev;
- struct _xidregitem *next;
- PyObject *cls; // weakref to a PyTypeObject
- crossinterpdatafunc getdata;
-};
-
PyAPI_FUNC(PyInterpreterState*) _PyInterpreterState_LookUpID(int64_t);
PyAPI_FUNC(int) _PyInterpreterState_IDInitref(PyInterpreterState *);
diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h
index bc972783fc94f1..99c4b0760bfb94 100644
--- a/Include/internal/pycore_runtime.h
+++ b/Include/internal/pycore_runtime.h
@@ -111,10 +111,7 @@ typedef struct pyruntimestate {
tools. */
// XXX Remove this field once we have a tp_* slot.
- struct _xidregistry {
- PyThread_type_lock mutex;
- struct _xidregitem *head;
- } xidregistry;
+ struct _xidregistry xidregistry;
struct _pymem_allocators allocators;
struct _obmalloc_global_state obmalloc;
diff --git a/Python/pystate.c b/Python/pystate.c
index 534e77fe2cbf55..1337516aa59cbc 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -493,6 +493,8 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
return _PyStatus_OK();
}
+static void _xidregistry_clear(struct _xidregistry *);
+
void
_PyRuntimeState_Fini(_PyRuntimeState *runtime)
{
@@ -501,6 +503,8 @@ _PyRuntimeState_Fini(_PyRuntimeState *runtime)
assert(runtime->object_state.interpreter_leaks == 0);
#endif
+ _xidregistry_clear(&runtime->xidregistry);
+
if (gilstate_tss_initialized(runtime)) {
gilstate_tss_fini(runtime);
}
@@ -550,6 +554,11 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime)
for (int i = 0; i < NUMLOCKS; i++) {
reinit_err += _PyThread_at_fork_reinit(lockptrs[i]);
}
+ /* PyOS_AfterFork_Child(), which calls this function, later calls
+ _PyInterpreterState_DeleteExceptMain(), so we only need to update
+ the main interpreter here. */
+ assert(runtime->interpreters.main != NULL);
+ runtime->interpreters.main->xidregistry.mutex = runtime->xidregistry.mutex;
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
@@ -699,6 +708,10 @@ init_interpreter(PyInterpreterState *interp,
interp->dtoa = (struct _dtoa_state)_dtoa_state_INIT(interp);
}
interp->f_opcode_trace_set = false;
+
+ assert(runtime->xidregistry.mutex != NULL);
+ interp->xidregistry.mutex = runtime->xidregistry.mutex;
+
interp->_initialized = 1;
}
@@ -891,6 +904,10 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
Py_CLEAR(interp->builtins);
Py_CLEAR(interp->interpreter_trampoline);
+ _xidregistry_clear(&interp->xidregistry);
+ /* The lock is owned by the runtime, so we don't free it here. */
+ interp->xidregistry.mutex = NULL;
+
if (tstate->interp == interp) {
/* We are now safe to fix tstate->_status.cleared. */
// XXX Do this (much) earlier?
@@ -2529,23 +2546,27 @@ _PyCrossInterpreterData_ReleaseAndRawFree(_PyCrossInterpreterData *data)
crossinterpdatafunc. It would be simpler and more efficient. */
static int
-_xidregistry_add_type(struct _xidregistry *xidregistry, PyTypeObject *cls,
- crossinterpdatafunc getdata)
+_xidregistry_add_type(struct _xidregistry *xidregistry,
+ PyTypeObject *cls, crossinterpdatafunc getdata)
{
- // Note that we effectively replace already registered classes
- // rather than failing.
struct _xidregitem *newhead = PyMem_RawMalloc(sizeof(struct _xidregitem));
if (newhead == NULL) {
return -1;
}
- // XXX Assign a callback to clear the entry from the registry?
- newhead->cls = PyWeakref_NewRef((PyObject *)cls, NULL);
- if (newhead->cls == NULL) {
- PyMem_RawFree(newhead);
- 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->getdata = getdata;
- newhead->prev = NULL;
newhead->next = xidregistry->head;
if (newhead->next != NULL) {
newhead->next->prev = newhead;
@@ -2570,37 +2591,78 @@ _xidregistry_remove_entry(struct _xidregistry *xidregistry,
if (next != NULL) {
next->prev = entry->prev;
}
- Py_DECREF(entry->cls);
+ 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;
+ }
+}
+
static struct _xidregitem *
_xidregistry_find_type(struct _xidregistry *xidregistry, PyTypeObject *cls)
{
struct _xidregitem *cur = xidregistry->head;
while (cur != NULL) {
- PyObject *registered = PyWeakref_GetObject(cur->cls);
- if (registered == Py_None) {
- // The weakly ref'ed object was freed.
- cur = _xidregistry_remove_entry(xidregistry, cur);
- }
- else {
- assert(PyType_Check(registered));
- if (registered == (PyObject *)cls) {
- return cur;
+ if (cur->weakref != NULL) {
+ // cur is/was a heap type.
+ PyObject *registered = PyWeakref_GetObject(cur->weakref);
+ assert(registered != NULL);
+ if (registered == Py_None) {
+ // The weakly ref'ed object was freed.
+ cur = _xidregistry_remove_entry(xidregistry, cur);
+ continue;
}
- cur = cur->next;
+ 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 inline struct _xidregistry *
+_get_xidregistry(PyInterpreterState *interp, PyTypeObject *cls)
+{
+ struct _xidregistry *xidregistry = &interp->runtime->xidregistry;
+ if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) {
+ assert(interp->xidregistry.mutex == xidregistry->mutex);
+ xidregistry = &interp->xidregistry;
+ }
+ return xidregistry;
+}
+
static void _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry);
+static inline void
+_ensure_builtins_xid(PyInterpreterState *interp, struct _xidregistry *xidregistry)
+{
+ if (xidregistry != &interp->xidregistry) {
+ assert(xidregistry == &interp->runtime->xidregistry);
+ if (xidregistry->head == NULL) {
+ _register_builtins_for_crossinterpreter_data(xidregistry);
+ }
+ }
+}
+
int
_PyCrossInterpreterData_RegisterClass(PyTypeObject *cls,
- crossinterpdatafunc getdata)
+ crossinterpdatafunc getdata)
{
if (!PyType_Check(cls)) {
PyErr_Format(PyExc_ValueError, "only classes may be registered");
@@ -2611,12 +2673,23 @@ _PyCrossInterpreterData_RegisterClass(PyTypeObject *cls,
return -1;
}
- struct _xidregistry *xidregistry = &_PyRuntime.xidregistry ;
+ int res = 0;
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
- if (xidregistry->head == NULL) {
- _register_builtins_for_crossinterpreter_data(xidregistry);
+
+ _ensure_builtins_xid(interp, xidregistry);
+
+ struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
+ if (matched != NULL) {
+ assert(matched->getdata == getdata);
+ matched->refcount += 1;
+ goto finally;
}
- int res = _xidregistry_add_type(xidregistry, cls, getdata);
+
+ res = _xidregistry_add_type(xidregistry, cls, getdata);
+
+finally:
PyThread_release_lock(xidregistry->mutex);
return res;
}
@@ -2625,13 +2698,20 @@ int
_PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls)
{
int res = 0;
- struct _xidregistry *xidregistry = &_PyRuntime.xidregistry ;
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
+
struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
if (matched != NULL) {
- (void)_xidregistry_remove_entry(xidregistry, matched);
+ assert(matched->refcount > 0);
+ matched->refcount -= 1;
+ if (matched->refcount == 0) {
+ (void)_xidregistry_remove_entry(xidregistry, matched);
+ }
res = 1;
}
+
PyThread_release_lock(xidregistry->mutex);
return res;
}
@@ -2644,17 +2724,19 @@ _PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls)
crossinterpdatafunc
_PyCrossInterpreterData_Lookup(PyObject *obj)
{
- struct _xidregistry *xidregistry = &_PyRuntime.xidregistry ;
- PyObject *cls = PyObject_Type(obj);
+ PyTypeObject *cls = Py_TYPE(obj);
+
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
- if (xidregistry->head == NULL) {
- _register_builtins_for_crossinterpreter_data(xidregistry);
- }
- struct _xidregitem *matched = _xidregistry_find_type(xidregistry,
- (PyTypeObject *)cls);
- Py_DECREF(cls);
+
+ _ensure_builtins_xid(interp, xidregistry);
+
+ struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
+ crossinterpdatafunc func = matched != NULL ? matched->getdata : NULL;
+
PyThread_release_lock(xidregistry->mutex);
- return matched != NULL ? matched->getdata : NULL;
+ return func;
}
/* cross-interpreter data for builtin types */