From b8a9dba307845778dea5825c7e5992a62181158f Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Nov 2022 19:22:46 -0700 Subject: [PATCH 1/3] Use a weakref in _PyCrossInterpreterData_RegisterClass(). --- Include/internal/pycore_interp.h | 2 +- Python/pystate.c | 47 +++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 532b28499080f2..98600ab495e547 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -246,7 +246,7 @@ extern void _PyInterpreterState_Clear(PyThreadState *tstate); struct _xidregitem; struct _xidregitem { - PyTypeObject *cls; + PyObject *cls; // weakref to a PyTypeObject crossinterpdatafunc getdata; struct _xidregitem *next; }; diff --git a/Python/pystate.c b/Python/pystate.c index 19fd9a6ae4497b..ee753e9f430dc0 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1944,15 +1944,32 @@ _register_xidata(struct _xidregistry *xidregistry, PyTypeObject *cls, // Note that we effectively replace already registered classes // rather than failing. struct _xidregitem *newhead = PyMem_RawMalloc(sizeof(struct _xidregitem)); - if (newhead == NULL) + if (newhead == NULL) { return -1; - newhead->cls = cls; + } + // 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->getdata = getdata; newhead->next = xidregistry->head; xidregistry->head = newhead; return 0; } +static int +_match_registered_type(struct _xidregitem *item, PyObject *cls) +{ + PyObject *registered = PyWeakref_GetObject(item->cls); + if (registered == Py_None) { + return -1; + } + assert(PyType_Check(registered)); + return (registered == cls); +} + static void _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry); int @@ -1968,9 +1985,6 @@ _PyCrossInterpreterData_RegisterClass(PyTypeObject *cls, return -1; } - // Make sure the class isn't ever deallocated. - Py_INCREF((PyObject *)cls); - struct _xidregistry *xidregistry = &_PyRuntime.xidregistry ; PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK); if (xidregistry->head == NULL) { @@ -1992,16 +2006,35 @@ _PyCrossInterpreterData_Lookup(PyObject *obj) PyObject *cls = PyObject_Type(obj); crossinterpdatafunc getdata = NULL; PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK); + struct _xidregitem *prev = NULL; struct _xidregitem *cur = xidregistry->head; if (cur == NULL) { _register_builtins_for_crossinterpreter_data(xidregistry); cur = xidregistry->head; } - for(; cur != NULL; cur = cur->next) { - if (cur->cls == (PyTypeObject *)cls) { + while (cur != NULL) { + int res = _match_registered_type(cur, cls); + if (res < 0) { + // The weakly ref'ed object was freed. + struct _xidregitem *expired = cur; + cur = expired->next; + Py_DECREF(expired->cls); + PyMem_RawFree(expired); + if (prev == NULL) { + xidregistry->head = cur; + } + else { + prev->next = cur; + } + } + else if (res) { getdata = cur->getdata; break; } + else { + prev = cur; + cur = cur->next; + } } Py_DECREF(cls); PyThread_release_lock(xidregistry->mutex); From 20c3a6a9bb2668876bc345ba55f61d454ab9ae9e Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 29 Nov 2022 18:15:37 -0700 Subject: [PATCH 2/3] Add _PyCrossInterpreterData_UnregisterClass(). --- Include/cpython/pystate.h | 1 + Include/internal/pycore_interp.h | 3 +- Python/pystate.c | 111 +++++++++++++++++++------------ 3 files changed, 71 insertions(+), 44 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index c51542bcc895cb..7468a1c4f26f45 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -403,4 +403,5 @@ PyAPI_FUNC(int) _PyObject_CheckCrossInterpreterData(PyObject *); typedef int (*crossinterpdatafunc)(PyObject *, _PyCrossInterpreterData *); PyAPI_FUNC(int) _PyCrossInterpreterData_RegisterClass(PyTypeObject *, crossinterpdatafunc); +PyAPI_FUNC(int) _PyCrossInterpreterData_UnregisterClass(PyTypeObject *); PyAPI_FUNC(crossinterpdatafunc) _PyCrossInterpreterData_Lookup(PyObject *); diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 98600ab495e547..857ffd06f1c980 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -246,9 +246,10 @@ extern void _PyInterpreterState_Clear(PyThreadState *tstate); struct _xidregitem; struct _xidregitem { + struct _xidregitem *prev; + struct _xidregitem *next; PyObject *cls; // weakref to a PyTypeObject crossinterpdatafunc getdata; - struct _xidregitem *next; }; PyAPI_FUNC(PyInterpreterState*) _PyInterpreterState_LookUpID(int64_t); diff --git a/Python/pystate.c b/Python/pystate.c index ee753e9f430dc0..ba1477985be71c 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1938,7 +1938,7 @@ _PyCrossInterpreterData_NewObject(_PyCrossInterpreterData *data) crossinterpdatafunc. It would be simpler and more efficient. */ static int -_register_xidata(struct _xidregistry *xidregistry, PyTypeObject *cls, +_xidregistry_add_type(struct _xidregistry *xidregistry, PyTypeObject *cls, crossinterpdatafunc getdata) { // Note that we effectively replace already registered classes @@ -1954,20 +1954,55 @@ _register_xidata(struct _xidregistry *xidregistry, PyTypeObject *cls, return -1; } newhead->getdata = getdata; + newhead->prev = NULL; newhead->next = xidregistry->head; + if (newhead->next != NULL) { + newhead->next->prev = newhead; + } xidregistry->head = newhead; return 0; } -static int -_match_registered_type(struct _xidregitem *item, PyObject *cls) +static struct _xidregitem * +_xidregistry_remove_entry(struct _xidregistry *xidregistry, + struct _xidregitem *entry) { - PyObject *registered = PyWeakref_GetObject(item->cls); - if (registered == Py_None) { - return -1; + 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_DECREF(entry->cls); + PyMem_RawFree(entry); + return 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; + } + cur = cur->next; + } } - assert(PyType_Check(registered)); - return (registered == cls); + return NULL; } static void _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry); @@ -1990,11 +2025,27 @@ _PyCrossInterpreterData_RegisterClass(PyTypeObject *cls, if (xidregistry->head == NULL) { _register_builtins_for_crossinterpreter_data(xidregistry); } - int res = _register_xidata(xidregistry, cls, getdata); + int res = _xidregistry_add_type(xidregistry, cls, getdata); + PyThread_release_lock(xidregistry->mutex); + return res; +} + +int +_PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls) +{ + int res = 0; + struct _xidregistry *xidregistry = &_PyRuntime.xidregistry ; + PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK); + struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls); + if (matched != NULL) { + (void)_xidregistry_remove_entry(xidregistry, matched); + res = 1; + } PyThread_release_lock(xidregistry->mutex); return res; } + /* 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. */ @@ -2004,41 +2055,15 @@ _PyCrossInterpreterData_Lookup(PyObject *obj) { struct _xidregistry *xidregistry = &_PyRuntime.xidregistry ; PyObject *cls = PyObject_Type(obj); - crossinterpdatafunc getdata = NULL; PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK); - struct _xidregitem *prev = NULL; - struct _xidregitem *cur = xidregistry->head; - if (cur == NULL) { + if (xidregistry->head == NULL) { _register_builtins_for_crossinterpreter_data(xidregistry); - cur = xidregistry->head; - } - while (cur != NULL) { - int res = _match_registered_type(cur, cls); - if (res < 0) { - // The weakly ref'ed object was freed. - struct _xidregitem *expired = cur; - cur = expired->next; - Py_DECREF(expired->cls); - PyMem_RawFree(expired); - if (prev == NULL) { - xidregistry->head = cur; - } - else { - prev->next = cur; - } - } - else if (res) { - getdata = cur->getdata; - break; - } - else { - prev = cur; - cur = cur->next; - } } + struct _xidregitem *matched = _xidregistry_find_type(xidregistry, + (PyTypeObject *)cls); Py_DECREF(cls); PyThread_release_lock(xidregistry->mutex); - return getdata; + return matched != NULL ? matched->getdata : NULL; } /* cross-interpreter data for builtin types */ @@ -2144,22 +2169,22 @@ static void _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry) { // None - if (_register_xidata(xidregistry, (PyTypeObject *)PyObject_Type(Py_None), _none_shared) != 0) { + 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 (_register_xidata(xidregistry, &PyLong_Type, _long_shared) != 0) { + if (_xidregistry_add_type(xidregistry, &PyLong_Type, _long_shared) != 0) { Py_FatalError("could not register int for cross-interpreter sharing"); } // bytes - if (_register_xidata(xidregistry, &PyBytes_Type, _bytes_shared) != 0) { + if (_xidregistry_add_type(xidregistry, &PyBytes_Type, _bytes_shared) != 0) { Py_FatalError("could not register bytes for cross-interpreter sharing"); } // str - if (_register_xidata(xidregistry, &PyUnicode_Type, _str_shared) != 0) { + if (_xidregistry_add_type(xidregistry, &PyUnicode_Type, _str_shared) != 0) { Py_FatalError("could not register str for cross-interpreter sharing"); } } From ce4e158790a3b5885664bc25e368c46fcf915211 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 30 Nov 2022 20:25:36 -0700 Subject: [PATCH 3/3] Clear cross-interpreter data after releasing it. --- Python/pystate.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Python/pystate.c b/Python/pystate.c index ba1477985be71c..38225516d78a8e 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1873,8 +1873,9 @@ _release_xidata(void *arg) _PyCrossInterpreterData *data = (_PyCrossInterpreterData *)arg; if (data->free != NULL) { data->free(data->data); + data->data = NULL; } - Py_XDECREF(data->obj); + Py_CLEAR(data->obj); } static void @@ -1894,6 +1895,8 @@ _call_in_interpreter(struct _gilstate_runtime_state *gilstate, save_tstate = _PyThreadState_Swap(gilstate, tstate); } + // XXX Once the GIL is per-interpreter, this should be called with the + // calling interpreter's GIL released and the target interpreter's held. func(arg); // Switch back.