Skip to content

Commit 71c96b5

Browse files
[3.12] pythongh-110310: Add a Per-Interpreter XID Registry for Heap Types (pythongh-110311)
We do the following: * add a per-interpreter XID registry (PyInterpreterState.xidregistry) * put heap types there (keep static types in _PyRuntimeState.xidregistry) * clear the registries during interpreter/runtime finalization * avoid duplicate entries in the registry (when _PyCrossInterpreterData_RegisterClass() is called more than once for a type) * use Py_TYPE() instead of PyObject_Type() in _PyCrossInterpreterData_Lookup() The per-interpreter registry helps preserve isolation between interpreters. This is important when heap types are registered, which is something we haven't been doing yet but I will likely do soon. (cherry-picked from commit 80dc39e)
1 parent 1ea4cb1 commit 71c96b5

File tree

3 files changed

+150
-57
lines changed

3 files changed

+150
-57
lines changed

Include/internal/pycore_interp.h

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,32 @@ struct _Py_long_state {
3939
int max_str_digits;
4040
};
4141

42+
43+
/* cross-interpreter data registry */
44+
45+
/* For now we use a global registry of shareable classes. An
46+
alternative would be to add a tp_* slot for a class's
47+
crossinterpdatafunc. It would be simpler and more efficient. */
48+
49+
struct _xidregitem;
50+
51+
struct _xidregitem {
52+
struct _xidregitem *prev;
53+
struct _xidregitem *next;
54+
/* This can be a dangling pointer, but only if weakref is set. */
55+
PyTypeObject *cls;
56+
/* This is NULL for builtin types. */
57+
PyObject *weakref;
58+
size_t refcount;
59+
crossinterpdatafunc getdata;
60+
};
61+
62+
struct _xidregistry {
63+
PyThread_type_lock mutex;
64+
struct _xidregitem *head;
65+
};
66+
67+
4268
/* interpreter state */
4369

4470
/* PyInterpreterState holds the global state for one of the runtime's
@@ -143,6 +169,9 @@ struct _is {
143169
Py_ssize_t co_extra_user_count;
144170
freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS];
145171

172+
// XXX Remove this field once we have a tp_* slot.
173+
struct _xidregistry xidregistry;
174+
146175
#ifdef HAVE_FORK
147176
PyObject *before_forkers;
148177
PyObject *after_forkers_parent;
@@ -215,21 +244,6 @@ _PyInterpreterState_SetFinalizing(PyInterpreterState *interp, PyThreadState *tst
215244
}
216245

217246

218-
/* cross-interpreter data registry */
219-
220-
/* For now we use a global registry of shareable classes. An
221-
alternative would be to add a tp_* slot for a class's
222-
crossinterpdatafunc. It would be simpler and more efficient. */
223-
224-
struct _xidregitem;
225-
226-
struct _xidregitem {
227-
struct _xidregitem *prev;
228-
struct _xidregitem *next;
229-
PyObject *cls; // weakref to a PyTypeObject
230-
crossinterpdatafunc getdata;
231-
};
232-
233247
PyAPI_FUNC(PyInterpreterState*) _PyInterpreterState_LookUpID(int64_t);
234248

235249
PyAPI_FUNC(int) _PyInterpreterState_IDInitref(PyInterpreterState *);

Include/internal/pycore_runtime.h

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,7 @@ typedef struct pyruntimestate {
111111
tools. */
112112

113113
// XXX Remove this field once we have a tp_* slot.
114-
struct _xidregistry {
115-
PyThread_type_lock mutex;
116-
struct _xidregitem *head;
117-
} xidregistry;
114+
struct _xidregistry xidregistry;
118115

119116
struct _pymem_allocators allocators;
120117
struct _obmalloc_global_state obmalloc;

Python/pystate.c

Lines changed: 120 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,8 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
493493
return _PyStatus_OK();
494494
}
495495

496+
static void _xidregistry_clear(struct _xidregistry *);
497+
496498
void
497499
_PyRuntimeState_Fini(_PyRuntimeState *runtime)
498500
{
@@ -501,6 +503,8 @@ _PyRuntimeState_Fini(_PyRuntimeState *runtime)
501503
assert(runtime->object_state.interpreter_leaks == 0);
502504
#endif
503505

506+
_xidregistry_clear(&runtime->xidregistry);
507+
504508
if (gilstate_tss_initialized(runtime)) {
505509
gilstate_tss_fini(runtime);
506510
}
@@ -546,6 +550,11 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime)
546550
for (int i = 0; i < NUMLOCKS; i++) {
547551
reinit_err += _PyThread_at_fork_reinit(lockptrs[i]);
548552
}
553+
/* PyOS_AfterFork_Child(), which calls this function, later calls
554+
_PyInterpreterState_DeleteExceptMain(), so we only need to update
555+
the main interpreter here. */
556+
assert(runtime->interpreters.main != NULL);
557+
runtime->interpreters.main->xidregistry.mutex = runtime->xidregistry.mutex;
549558

550559
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
551560

@@ -695,6 +704,10 @@ init_interpreter(PyInterpreterState *interp,
695704
interp->dtoa = (struct _dtoa_state)_dtoa_state_INIT(interp);
696705
}
697706
interp->f_opcode_trace_set = false;
707+
708+
assert(runtime->xidregistry.mutex != NULL);
709+
interp->xidregistry.mutex = runtime->xidregistry.mutex;
710+
698711
interp->_initialized = 1;
699712
}
700713

@@ -887,6 +900,10 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
887900
Py_CLEAR(interp->builtins);
888901
Py_CLEAR(interp->interpreter_trampoline);
889902

903+
_xidregistry_clear(&interp->xidregistry);
904+
/* The lock is owned by the runtime, so we don't free it here. */
905+
interp->xidregistry.mutex = NULL;
906+
890907
if (tstate->interp == interp) {
891908
/* We are now safe to fix tstate->_status.cleared. */
892909
// XXX Do this (much) earlier?
@@ -2467,23 +2484,27 @@ _PyCrossInterpreterData_Release(_PyCrossInterpreterData *data)
24672484
crossinterpdatafunc. It would be simpler and more efficient. */
24682485

24692486
static int
2470-
_xidregistry_add_type(struct _xidregistry *xidregistry, PyTypeObject *cls,
2471-
crossinterpdatafunc getdata)
2487+
_xidregistry_add_type(struct _xidregistry *xidregistry,
2488+
PyTypeObject *cls, crossinterpdatafunc getdata)
24722489
{
2473-
// Note that we effectively replace already registered classes
2474-
// rather than failing.
24752490
struct _xidregitem *newhead = PyMem_RawMalloc(sizeof(struct _xidregitem));
24762491
if (newhead == NULL) {
24772492
return -1;
24782493
}
2479-
// XXX Assign a callback to clear the entry from the registry?
2480-
newhead->cls = PyWeakref_NewRef((PyObject *)cls, NULL);
2481-
if (newhead->cls == NULL) {
2482-
PyMem_RawFree(newhead);
2483-
return -1;
2494+
*newhead = (struct _xidregitem){
2495+
// We do not keep a reference, to avoid keeping the class alive.
2496+
.cls = cls,
2497+
.refcount = 1,
2498+
.getdata = getdata,
2499+
};
2500+
if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) {
2501+
// XXX Assign a callback to clear the entry from the registry?
2502+
newhead->weakref = PyWeakref_NewRef((PyObject *)cls, NULL);
2503+
if (newhead->weakref == NULL) {
2504+
PyMem_RawFree(newhead);
2505+
return -1;
2506+
}
24842507
}
2485-
newhead->getdata = getdata;
2486-
newhead->prev = NULL;
24872508
newhead->next = xidregistry->head;
24882509
if (newhead->next != NULL) {
24892510
newhead->next->prev = newhead;
@@ -2508,37 +2529,78 @@ _xidregistry_remove_entry(struct _xidregistry *xidregistry,
25082529
if (next != NULL) {
25092530
next->prev = entry->prev;
25102531
}
2511-
Py_DECREF(entry->cls);
2532+
Py_XDECREF(entry->weakref);
25122533
PyMem_RawFree(entry);
25132534
return next;
25142535
}
25152536

2537+
static void
2538+
_xidregistry_clear(struct _xidregistry *xidregistry)
2539+
{
2540+
struct _xidregitem *cur = xidregistry->head;
2541+
xidregistry->head = NULL;
2542+
while (cur != NULL) {
2543+
struct _xidregitem *next = cur->next;
2544+
Py_XDECREF(cur->weakref);
2545+
PyMem_RawFree(cur);
2546+
cur = next;
2547+
}
2548+
}
2549+
25162550
static struct _xidregitem *
25172551
_xidregistry_find_type(struct _xidregistry *xidregistry, PyTypeObject *cls)
25182552
{
25192553
struct _xidregitem *cur = xidregistry->head;
25202554
while (cur != NULL) {
2521-
PyObject *registered = PyWeakref_GetObject(cur->cls);
2522-
if (registered == Py_None) {
2523-
// The weakly ref'ed object was freed.
2524-
cur = _xidregistry_remove_entry(xidregistry, cur);
2525-
}
2526-
else {
2527-
assert(PyType_Check(registered));
2528-
if (registered == (PyObject *)cls) {
2529-
return cur;
2555+
if (cur->weakref != NULL) {
2556+
// cur is/was a heap type.
2557+
PyObject *registered = PyWeakref_GetObject(cur->weakref);
2558+
assert(registered != NULL);
2559+
if (registered == Py_None) {
2560+
// The weakly ref'ed object was freed.
2561+
cur = _xidregistry_remove_entry(xidregistry, cur);
2562+
continue;
25302563
}
2531-
cur = cur->next;
2564+
assert(PyType_Check(registered));
2565+
assert(cur->cls == (PyTypeObject *)registered);
2566+
assert(cur->cls->tp_flags & Py_TPFLAGS_HEAPTYPE);
2567+
Py_DECREF(registered);
25322568
}
2569+
if (cur->cls == cls) {
2570+
return cur;
2571+
}
2572+
cur = cur->next;
25332573
}
25342574
return NULL;
25352575
}
25362576

2577+
static inline struct _xidregistry *
2578+
_get_xidregistry(PyInterpreterState *interp, PyTypeObject *cls)
2579+
{
2580+
struct _xidregistry *xidregistry = &interp->runtime->xidregistry;
2581+
if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) {
2582+
assert(interp->xidregistry.mutex == xidregistry->mutex);
2583+
xidregistry = &interp->xidregistry;
2584+
}
2585+
return xidregistry;
2586+
}
2587+
25372588
static void _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry);
25382589

2590+
static inline void
2591+
_ensure_builtins_xid(PyInterpreterState *interp, struct _xidregistry *xidregistry)
2592+
{
2593+
if (xidregistry != &interp->xidregistry) {
2594+
assert(xidregistry == &interp->runtime->xidregistry);
2595+
if (xidregistry->head == NULL) {
2596+
_register_builtins_for_crossinterpreter_data(xidregistry);
2597+
}
2598+
}
2599+
}
2600+
25392601
int
25402602
_PyCrossInterpreterData_RegisterClass(PyTypeObject *cls,
2541-
crossinterpdatafunc getdata)
2603+
crossinterpdatafunc getdata)
25422604
{
25432605
if (!PyType_Check(cls)) {
25442606
PyErr_Format(PyExc_ValueError, "only classes may be registered");
@@ -2549,12 +2611,23 @@ _PyCrossInterpreterData_RegisterClass(PyTypeObject *cls,
25492611
return -1;
25502612
}
25512613

2552-
struct _xidregistry *xidregistry = &_PyRuntime.xidregistry ;
2614+
int res = 0;
2615+
PyInterpreterState *interp = _PyInterpreterState_GET();
2616+
struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
25532617
PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
2554-
if (xidregistry->head == NULL) {
2555-
_register_builtins_for_crossinterpreter_data(xidregistry);
2618+
2619+
_ensure_builtins_xid(interp, xidregistry);
2620+
2621+
struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
2622+
if (matched != NULL) {
2623+
assert(matched->getdata == getdata);
2624+
matched->refcount += 1;
2625+
goto finally;
25562626
}
2557-
int res = _xidregistry_add_type(xidregistry, cls, getdata);
2627+
2628+
res = _xidregistry_add_type(xidregistry, cls, getdata);
2629+
2630+
finally:
25582631
PyThread_release_lock(xidregistry->mutex);
25592632
return res;
25602633
}
@@ -2563,13 +2636,20 @@ int
25632636
_PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls)
25642637
{
25652638
int res = 0;
2566-
struct _xidregistry *xidregistry = &_PyRuntime.xidregistry ;
2639+
PyInterpreterState *interp = _PyInterpreterState_GET();
2640+
struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
25672641
PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
2642+
25682643
struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
25692644
if (matched != NULL) {
2570-
(void)_xidregistry_remove_entry(xidregistry, matched);
2645+
assert(matched->refcount > 0);
2646+
matched->refcount -= 1;
2647+
if (matched->refcount == 0) {
2648+
(void)_xidregistry_remove_entry(xidregistry, matched);
2649+
}
25712650
res = 1;
25722651
}
2652+
25732653
PyThread_release_lock(xidregistry->mutex);
25742654
return res;
25752655
}
@@ -2582,17 +2662,19 @@ _PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls)
25822662
crossinterpdatafunc
25832663
_PyCrossInterpreterData_Lookup(PyObject *obj)
25842664
{
2585-
struct _xidregistry *xidregistry = &_PyRuntime.xidregistry ;
2586-
PyObject *cls = PyObject_Type(obj);
2665+
PyTypeObject *cls = Py_TYPE(obj);
2666+
2667+
PyInterpreterState *interp = _PyInterpreterState_GET();
2668+
struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
25872669
PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
2588-
if (xidregistry->head == NULL) {
2589-
_register_builtins_for_crossinterpreter_data(xidregistry);
2590-
}
2591-
struct _xidregitem *matched = _xidregistry_find_type(xidregistry,
2592-
(PyTypeObject *)cls);
2593-
Py_DECREF(cls);
2670+
2671+
_ensure_builtins_xid(interp, xidregistry);
2672+
2673+
struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
2674+
crossinterpdatafunc func = matched != NULL ? matched->getdata : NULL;
2675+
25942676
PyThread_release_lock(xidregistry->mutex);
2595-
return matched != NULL ? matched->getdata : NULL;
2677+
return func;
25962678
}
25972679

25982680
/* cross-interpreter data for builtin types */

0 commit comments

Comments
 (0)