diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 53895bbced8..3692071280b 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -791,6 +791,7 @@ member,PyVarObject.ob_size,3.2,, function,PyVectorcall_Call,3.12,, function,PyVectorcall_NARGS,3.12,, type,PyWeakReference,3.2,,opaque +function,PyWeakref_FetchObject,3.12,, function,PyWeakref_GetObject,3.2,, function,PyWeakref_NewProxy,3.2,, function,PyWeakref_NewRef,3.2,, diff --git a/Include/cpython/weakrefobject.h b/Include/cpython/weakrefobject.h index a7d34f14117..133cbe2560b 100644 --- a/Include/cpython/weakrefobject.h +++ b/Include/cpython/weakrefobject.h @@ -2,55 +2,59 @@ # error "this header file must not be included directly" #endif -/* PyWeakReference is the base struct for the Python ReferenceType, ProxyType, - * and CallableProxyType. - */ -struct _PyWeakReference { +struct _PyWeakrefBase { PyObject_HEAD + /* If wr_object is weakly referenced, wr_object has a doubly-linked NULL- + * terminated list of weak references to it. These are the list pointers. + * If wr_object goes away, wr_object is set to Py_None, and these pointers + * have no meaning then. + */ + struct _PyWeakrefBase *wr_prev; + struct _PyWeakrefBase *wr_next; +}; + +struct _PyWeakrefControl { + struct _PyWeakrefBase base; + + /* Protectes the weakref linked-list and wr_object from + * concurrent accesses. */ + _PyMutex mutex; + /* The object to which this is a weak reference, or Py_None if none. * Note that this is a stealth reference: wr_object's refcount is * not incremented to reflect this pointer. */ PyObject *wr_object; +}; + +/* PyWeakReference is the base struct for the Python ReferenceType, ProxyType, + * and CallableProxyType. + */ +struct _PyWeakReference { + struct _PyWeakrefBase base; + + /* Pointer to weakref control block */ + struct _PyWeakrefControl *wr_parent; /* A callable to invoke when wr_object dies, or NULL if none. */ PyObject *wr_callback; + vectorcallfunc vectorcall; + /* A cache for wr_object's hash code. As usual for hashes, this is -1 * if the hash code isn't known yet. */ Py_hash_t hash; - - /* If wr_object is weakly referenced, wr_object has a doubly-linked NULL- - * terminated list of weak references to it. These are the list pointers. - * If wr_object goes away, wr_object is set to Py_None, and these pointers - * have no meaning then. - */ - PyWeakReference *wr_prev; - PyWeakReference *wr_next; - vectorcallfunc vectorcall; }; -PyAPI_FUNC(Py_ssize_t) _PyWeakref_GetWeakrefCount(PyWeakReference *head); +typedef struct _PyWeakrefControl PyWeakrefControl; +typedef struct _PyWeakrefBase PyWeakrefBase; + +PyAPI_FUNC(void) _PyWeakref_DetachRef(PyWeakReference *self); + +PyAPI_FUNC(Py_ssize_t) _PyWeakref_GetWeakrefCount(struct _PyWeakrefControl *ctrl); PyAPI_FUNC(void) _PyWeakref_ClearRef(PyWeakReference *self); -static inline PyObject* PyWeakref_GET_OBJECT(PyObject *ref_obj) { - PyWeakReference *ref; - PyObject *obj; - assert(PyWeakref_Check(ref_obj)); - ref = _Py_CAST(PyWeakReference*, ref_obj); - obj = ref->wr_object; - // Explanation for the Py_REFCNT() check: when a weakref's target is part - // of a long chain of deallocations which triggers the trashcan mechanism, - // clearing the weakrefs can be delayed long after the target's refcount - // has dropped to zero. In the meantime, code accessing the weakref will - // be able to "see" the target object even though it is supposed to be - // unreachable. See issue gh-60806. - if (Py_IS_REFERENCED(obj)) { - return obj; - } - return Py_None; -} -#define PyWeakref_GET_OBJECT(ref) PyWeakref_GET_OBJECT(_PyObject_CAST(ref)) +#define PyWeakref_GET_OBJECT(ref) PyWeakref_GetObject(_PyObject_CAST(ref)) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 64adf90e8cd..656be200501 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -380,8 +380,8 @@ extern void _Py_PrintReferenceAddresses(FILE *); * nor should it be used to add, remove, or swap any refs in the list. * That is the sole responsibility of the code in weakrefobject.c. */ -static inline PyObject ** -_PyObject_GET_WEAKREFS_LISTPTR(PyObject *op) +static inline PyWeakrefControl ** +_PyObject_GET_WEAKREFS_CONTROLPTR(PyObject *op) { if (PyType_Check(op) && ((PyTypeObject *)op)->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { @@ -389,12 +389,12 @@ _PyObject_GET_WEAKREFS_LISTPTR(PyObject *op) (PyTypeObject *)op); return _PyStaticType_GET_WEAKREFS_LISTPTR(state); } - // Essentially _PyObject_GET_WEAKREFS_LISTPTR_FROM_OFFSET(): + // Essentially _PyObject_GET_WEAKREFS_CONTROLPTR_FROM_OFFSET(): Py_ssize_t offset = Py_TYPE(op)->tp_weaklistoffset; - return (PyObject **)((char *)op + offset); + return (PyWeakrefControl **)((char *)op + offset); } -/* This is a special case of _PyObject_GET_WEAKREFS_LISTPTR(). +/* This is a special case of _PyObject_GET_WEAKREFS_CONTROLPTR(). * Only the most fundamental lookup path is used. * Consequently, static types should not be used. * @@ -404,15 +404,21 @@ _PyObject_GET_WEAKREFS_LISTPTR(PyObject *op) * are only finalized at the end of runtime finalization. * * If the weaklist for static types is actually needed then use - * _PyObject_GET_WEAKREFS_LISTPTR(). + * _PyObject_GET_WEAKREFS_CONTROLPTR(). */ -static inline PyWeakReference ** -_PyObject_GET_WEAKREFS_LISTPTR_FROM_OFFSET(PyObject *op) +static inline PyWeakrefControl ** +_PyObject_GET_WEAKREFS_CONTROLPTR_FROM_OFFSET(PyObject *op) { assert(!PyType_Check(op) || ((PyTypeObject *)op)->tp_flags & Py_TPFLAGS_HEAPTYPE); Py_ssize_t offset = Py_TYPE(op)->tp_weaklistoffset; - return (PyWeakReference **)((char *)op + offset); + return (PyWeakrefControl **)((char *)op + offset); +} + +static inline PyWeakrefControl * +_PyObject_GET_WEAKREF_CONTROL(PyObject *op) +{ + return _Py_atomic_load_ptr(_PyObject_GET_WEAKREFS_CONTROLPTR(op)); } diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 4d705740a9a..3a95dd54291 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -52,10 +52,10 @@ typedef struct { they will effectively never get triggered. However, there are also some diagnostic uses for the list of weakrefs, so we still keep it. */ - PyObject *tp_weaklist; + PyWeakrefControl *tp_weaklist; } static_builtin_state; -static inline PyObject ** +static inline PyWeakrefControl ** _PyStaticType_GET_WEAKREFS_LISTPTR(static_builtin_state *state) { assert(state != NULL); diff --git a/Include/object.h b/Include/object.h index 397294a33c0..e69613a7bfb 100644 --- a/Include/object.h +++ b/Include/object.h @@ -347,6 +347,9 @@ PyAPI_FUNC(int) PyObject_IsTrue(PyObject *); PyAPI_FUNC(int) PyObject_Not(PyObject *); PyAPI_FUNC(int) PyCallable_Check(PyObject *); PyAPI_FUNC(void) PyObject_ClearWeakRefs(PyObject *); +#ifndef Py_LIMITED_API +PyAPI_FUNC(void) _PyObject_ClearWeakRefsFromDealloc(PyObject *); +#endif /* PyObject_Dir(obj) acts like Python builtins.dir(obj), returning a list of strings. PyObject_Dir(NULL) is like builtins.dir(), diff --git a/Include/weakrefobject.h b/Include/weakrefobject.h index 8e1fa1b9286..d25fa721d81 100644 --- a/Include/weakrefobject.h +++ b/Include/weakrefobject.h @@ -28,7 +28,10 @@ PyAPI_FUNC(PyObject *) PyWeakref_NewRef(PyObject *ob, PyAPI_FUNC(PyObject *) PyWeakref_NewProxy(PyObject *ob, PyObject *callback); PyAPI_FUNC(PyObject *) PyWeakref_GetObject(PyObject *ref); - +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030B0000 +PyAPI_FUNC(PyObject *) PyWeakref_FetchObject(PyObject *ref); +#endif +#define PyWeakref_LockObject PyWeakref_FetchObject #ifndef Py_LIMITED_API # define Py_CPYTHON_WEAKREFOBJECT_H @@ -36,6 +39,7 @@ PyAPI_FUNC(PyObject *) PyWeakref_GetObject(PyObject *ref); # undef Py_CPYTHON_WEAKREFOBJECT_H #endif + #ifdef __cplusplus } #endif diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index dace37c362e..5c966e54b69 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -634,7 +634,7 @@ def test_heaptype_with_weakref(self): inst = _testcapi.HeapCTypeWithWeakref() ref = weakref.ref(inst) self.assertEqual(ref(), inst) - self.assertEqual(inst.weakreflist, ref) + self.assertEqual(type(inst.weakreflist).__name__, "weakref_control") def test_heaptype_with_managed_weakref(self): inst = _testcapi.HeapCTypeWithManagedWeakref() diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 9b7cbe17f27..b3d30c52be9 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -786,6 +786,7 @@ def test_windows_feature_macros(self): "PyUnicode_WriteChar", "PyVectorcall_Call", "PyVectorcall_NARGS", + "PyWeakref_FetchObject", "PyWeakref_GetObject", "PyWeakref_NewProxy", "PyWeakref_NewRef", diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 6b80e1438bb..b03b1b7cecf 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1582,11 +1582,11 @@ class newstyleclass(object): pass # TODO: add check that forces layout of unicodefields # weakref import weakref - check(weakref.ref(int), size('2Pn3P')) + check(weakref.ref(int), size('4Pn')) # weakproxy # XXX # weakcallableproxy - check(weakref.proxy(int), size('2Pn3P')) + check(weakref.proxy(int), size('4Pn')) def check_slots(self, obj, base, extra): expected = sys.getsizeof(base) + struct.calcsize(extra) diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 7c5920797d2..8ee4b4fbae2 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -79,7 +79,7 @@ def callback(self, ref): @contextlib.contextmanager -def collect_in_thread(period=0.0001): +def collect_in_thread(period=0.005): """ Ensure GC collections happen in a different thread, at a high frequency. """ @@ -990,9 +990,9 @@ class MyRef(weakref.ref): self.assertEqual(weakref.getweakrefcount(o), 3) refs = weakref.getweakrefs(o) self.assertEqual(len(refs), 3) - self.assertIs(r2, refs[0]) - self.assertIn(r1, refs[1:]) - self.assertIn(r3, refs[1:]) + self.assertIn(r2, refs) + self.assertIn(r1, refs) + self.assertIn(r3, refs) def test_subclass_refs_dont_conflate_callbacks(self): class MyRef(weakref.ref): diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 8cce75c53d4..2c4e7f8c400 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -1577,6 +1577,8 @@ added = '3.2' [function.PyWeakref_GetObject] added = '3.2' +[function.PyWeakref_FetchObject] + added = '3.12' [function.PyWeakref_NewProxy] added = '3.2' [function.PyWeakref_NewRef] diff --git a/Modules/_abc.c b/Modules/_abc.c index e146d4fd0ca..e411f738b35 100644 --- a/Modules/_abc.c +++ b/Modules/_abc.c @@ -150,11 +150,10 @@ static PyObject * _destroy(PyObject *setweakref, PyObject *objweakref) { PyObject *set; - set = PyWeakref_GET_OBJECT(setweakref); + set = PyWeakref_FetchObject(setweakref); if (set == Py_None) { Py_RETURN_NONE; } - Py_INCREF(set); if (PySet_Discard(set, objweakref) < 0) { Py_DECREF(set); return NULL; diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 4ce6433a2e4..39b9e6f1cea 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -244,7 +244,7 @@ PyDict_SetItemProxy(PyObject *dict, PyObject *key, PyObject *item) return result; } -PyObject * +static PyObject * PyDict_GetItemProxy(PyObject *dict, PyObject *key) { PyObject *result; @@ -252,9 +252,11 @@ PyDict_GetItemProxy(PyObject *dict, PyObject *key) if (item == NULL) return NULL; - if (!PyWeakref_CheckProxy(item)) + if (!PyWeakref_CheckProxy(item)) { + Py_INCREF(item); return item; - result = PyWeakref_GET_OBJECT(item); + } + result = PyWeakref_FetchObject(item); if (result == Py_None) return NULL; return result; @@ -4818,7 +4820,6 @@ PyCArrayType_from_ctype(PyObject *itemtype, Py_ssize_t length) return NULL; result = PyDict_GetItemProxy(cache, key); if (result) { - Py_INCREF(result); Py_DECREF(key); return result; } diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 6be4865b410..ec467ce64a8 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1091,14 +1091,14 @@ static PyObject * _localdummy_destroyed(PyObject *localweakref, PyObject *dummyweakref) { assert(PyWeakref_CheckRef(localweakref)); - PyObject *obj = PyWeakref_GET_OBJECT(localweakref); + PyObject *obj = PyWeakref_FetchObject(localweakref); if (obj == Py_None) { Py_RETURN_NONE; } /* If the thread-local object is still alive and not being cleared, remove the corresponding local dict */ - localobject *self = (localobject *)Py_NewRef(obj); + localobject *self = (localobject *)obj; if (self->dummies != NULL) { PyObject *ldict; ldict = PyDict_GetItemWithError(self->dummies, dummyweakref); @@ -1387,7 +1387,8 @@ release_sentinel(void *wr_raw) /* Tricky: this function is called when the current thread state is being deleted. Therefore, only simple C code can safely execute here. */ - PyObject *obj = PyWeakref_GET_OBJECT(wr); + // FIXME(sgross): this isn't simple C code + PyObject *obj = PyWeakref_FetchObject(wr); lockobject *lock; if (obj != Py_None) { lock = (lockobject *) obj; @@ -1395,6 +1396,7 @@ release_sentinel(void *wr_raw) PyThread_release_lock(lock->lock_lock); lock->locked = 0; } + Py_DECREF(obj); } /* Deallocating a weakref with a NULL callback only calls PyObject_GC_Del(), which can't call any Python code. */ diff --git a/Modules/_weakref.c b/Modules/_weakref.c index 157a852ae9a..fe4772eb578 100644 --- a/Modules/_weakref.c +++ b/Modules/_weakref.c @@ -1,9 +1,13 @@ #include "Python.h" -#include "pycore_object.h" // _PyObject_GET_WEAKREFS_LISTPTR +#include "pycore_object.h" // _PyObject_GET_WEAKREFS_CONTROLPTR -#define GET_WEAKREFS_LISTPTR(o) \ - ((PyWeakReference **) _PyObject_GET_WEAKREFS_LISTPTR(o)) +#define GET_WEAKREFS_CONTROL(o) \ + (_PyObject_GET_WEAKREF_CONTROL(o)) + + +#define GET_WEAKREFS_CONTROLPTR(o) \ + (_PyObject_GET_WEAKREFS_CONTROLPTR(o)) /*[clinic input] module _weakref @@ -26,24 +30,26 @@ static Py_ssize_t _weakref_getweakrefcount_impl(PyObject *module, PyObject *object) /*[clinic end generated code: output=301806d59558ff3e input=cedb69711b6a2507]*/ { - PyWeakReference **list; - if (!_PyType_SUPPORTS_WEAKREFS(Py_TYPE(object))) return 0; - list = GET_WEAKREFS_LISTPTR(object); - return _PyWeakref_GetWeakrefCount(*list); + return _PyWeakref_GetWeakrefCount(GET_WEAKREFS_CONTROL(object)); } static int is_dead_weakref(PyObject *value) { + int is_dead; if (!PyWeakref_Check(value)) { PyErr_SetString(PyExc_TypeError, "not a weakref"); return -1; } - return PyWeakref_GET_OBJECT(value) == Py_None; + + PyObject *obj = PyWeakref_FetchObject(value); + is_dead = (obj == Py_None); + Py_DECREF(obj); + return is_dead; } /*[clinic input] @@ -88,26 +94,31 @@ static PyObject * _weakref_getweakrefs(PyObject *module, PyObject *object) /*[clinic end generated code: output=25c7731d8e011824 input=00c6d0e5d3206693]*/ { - PyObject *result = NULL; - - if (_PyType_SUPPORTS_WEAKREFS(Py_TYPE(object))) { - PyWeakReference **list = GET_WEAKREFS_LISTPTR(object); - Py_ssize_t count = _PyWeakref_GetWeakrefCount(*list); - - result = PyList_New(count); - if (result != NULL) { - PyWeakReference *current = *list; - Py_ssize_t i; - for (i = 0; i < count; ++i) { - PyList_SET_ITEM(result, i, (PyObject *) current); - Py_INCREF(current); - current = current->wr_next; - } - } + PyObject *result = PyList_New(0); + if (result == NULL) { + return NULL; + } + + if (!_PyType_SUPPORTS_WEAKREFS(Py_TYPE(object))) { + return result; + } + + PyWeakrefControl *root = GET_WEAKREFS_CONTROL(object); + if (root == NULL) { + return result; } - else { - result = PyList_New(0); + + _PyMutex_lock(&root->mutex); + PyWeakrefBase *next = root->base.wr_next; + while (next != (PyWeakrefBase *)root) { + if (PyList_Append(result, (PyObject *)next) < 0) { + _PyMutex_unlock(&root->mutex); + Py_DECREF(result); + return NULL; + } + next = next->wr_next; } + _PyMutex_unlock(&root->mutex); return result; } diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index e522970dd90..178dd2508ca 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -1009,8 +1009,6 @@ handle_weakrefs(PyGC_Head *unreachable) * pass completes. */ for (gc = GC_NEXT(unreachable); gc != unreachable; gc = next) { - PyWeakReference **wrlist; - op = FROM_GC(gc); next = GC_NEXT(gc); @@ -1026,7 +1024,7 @@ handle_weakrefs(PyGC_Head *unreachable) * will run and potentially cause a crash. See bpo-38006 for * one example. */ - _PyWeakref_ClearRef((PyWeakReference *)op); + _PyWeakref_DetachRef((PyWeakReference *)op); } if (! _PyType_SUPPORTS_WEAKREFS(Py_TYPE(op))) @@ -1037,19 +1035,15 @@ handle_weakrefs(PyGC_Head *unreachable) * This is never triggered for static types so we can avoid the * (slightly) more costly _PyObject_GET_WEAKREFS_LISTPTR(). */ - wrlist = _PyObject_GET_WEAKREFS_LISTPTR_FROM_OFFSET(op); + PyWeakrefBase *ctrl = (PyWeakrefBase *)_PyObject_GET_WEAKREF_CONTROL(op); - PyWeakReference *wr; - for (wr = *wrlist; wr != NULL; wr = *wrlist) { - PyGC_Head *wrasgc; /* AS_GC(wr) */ + if (!ctrl) + continue; - /* _PyWeakref_ClearRef clears the weakref but leaves - * the callback pointer intact. Obscure: it also - * changes *wrlist. - */ - _PyObject_ASSERT((PyObject *)wr, wr->wr_object == op); - _PyWeakref_ClearRef(wr); - _PyObject_ASSERT((PyObject *)wr, wr->wr_object == Py_None); + PyWeakrefBase *ref; + for (ref = ctrl->wr_next; ref != ctrl; ref = ref->wr_next) { + PyGC_Head *wrasgc; /* AS_GC(wr) */ + PyWeakReference *wr = (PyWeakReference *)ref; if (wr->wr_callback == NULL) { /* no callback */ @@ -1086,7 +1080,7 @@ handle_weakrefs(PyGC_Head *unreachable) */ if (gc_is_unreachable(AS_GC(wr))) { /* it should already have been cleared above */ - assert(wr->wr_object == Py_None); + // assert(wr->wr_object == Py_None); continue; } @@ -1106,6 +1100,11 @@ handle_weakrefs(PyGC_Head *unreachable) gc_list_append(wrasgc, &wrcb_to_call); // FIXME: need to set collecting???? } + + /* Clear the root weakref but does not invoke any callbacks. + * Other weak references reference this object + */ + _PyObject_ClearWeakRefsFromDealloc(op); } /* Invoke the callbacks we decided to honor. It's safe to invoke them @@ -1259,11 +1258,13 @@ delete_garbage(PyThreadState *tstate, GCState *gcstate, { assert(!_PyErr_Occurred(tstate)); +#ifdef Py_DEBUG for (PyGC_Head *gc = GC_NEXT(collectable); gc != collectable; gc = GC_NEXT(gc)) { PyObject *op = FROM_GC(gc); _PyObject_ASSERT_WITH_MSG(op, _Py_GC_REFCNT(op) > 0, "refcount is too small"); } +#endif while (!gc_list_is_empty(collectable)) { PyGC_Head *gc = GC_NEXT(collectable); diff --git a/Objects/object.c b/Objects/object.c index 42ca647c5c7..43afae92ad9 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -17,6 +17,7 @@ #include "pycore_symtable.h" // PySTEntry_Type #include "pycore_unionobject.h" // _PyUnion_Type #include "pycore_interpreteridobject.h" // _PyInterpreterID_Type +#include "mimalloc.h" #ifdef Py_LIMITED_API // Prevent recursive call _Py_IncRef() <=> Py_INCREF() @@ -104,6 +105,7 @@ _PyDebug_PrintTotalRefs(void) { fprintf(stderr, "[%zd refs, %zd blocks]\n", _Py_GetRefTotal(), _Py_GetAllocatedBlocks()); + mi_stats_print(NULL); } #endif /* Py_REF_DEBUG */ @@ -2473,7 +2475,8 @@ _Py_Dealloc(PyObject *op) PyObject ** PyObject_GET_WEAKREFS_LISTPTR(PyObject *op) { - return _PyObject_GET_WEAKREFS_LISTPTR(op); + // FIXME(sgross): should this function be present? + return (PyObject **)_PyObject_GET_WEAKREFS_CONTROLPTR(op); } void diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 9a6c824a3bd..e66ba954fa3 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -465,11 +465,12 @@ PyType_Modified(PyTypeObject *type) Py_ssize_t i = 0; PyObject *ref; while (PyDict_Next(subclasses, &i, NULL, &ref)) { - PyTypeObject *subclass = subclass_from_ref(ref); // borrowed + PyTypeObject *subclass = subclass_from_ref(ref); if (subclass == NULL) { continue; } PyType_Modified(subclass); + Py_DECREF(subclass); } } @@ -1587,15 +1588,7 @@ subtype_dealloc(PyObject *self) finalizers since they might rely on part of the object being finalized that has already been destroyed. */ if (type->tp_weaklistoffset && !base->tp_weaklistoffset) { - /* Modeled after GET_WEAKREFS_LISTPTR(). - - This is never triggered for static types so we can avoid the - (slightly) more costly _PyObject_GET_WEAKREFS_LISTPTR(). */ - PyWeakReference **list = \ - _PyObject_GET_WEAKREFS_LISTPTR_FROM_OFFSET(self); - while (*list) { - _PyWeakref_ClearRef(*list); - } + _PyObject_ClearWeakRefsFromDealloc(self); } } @@ -4439,12 +4432,13 @@ clear_static_tp_subclasses(PyTypeObject *type) Py_ssize_t i = 0; PyObject *key, *ref; // borrowed ref while (PyDict_Next(subclasses, &i, &key, &ref)) { - PyTypeObject *subclass = subclass_from_ref(ref); // borrowed + PyTypeObject *subclass = subclass_from_ref(ref); if (subclass == NULL) { continue; } // All static builtin subtypes should have been finalized already. assert(!(subclass->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN)); + Py_DECREF(subclass); } clear_subclasses(type); @@ -4463,15 +4457,12 @@ _PyStaticType_Dealloc(PyTypeObject *type) Py_CLEAR(type->tp_cache); clear_static_tp_subclasses(type); - // PyObject_ClearWeakRefs() raises an exception if Py_REFCNT() != 0 - if (Py_REFCNT(type) == 0) { - PyObject_ClearWeakRefs((PyObject *)type); - } + /* Remove weakrefs but do not call callbacks */ + _PyStaticType_ClearWeakRefs(type); type->tp_flags &= ~Py_TPFLAGS_READY; if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { - _PyStaticType_ClearWeakRefs(type); static_builtin_state_clear(type); /* We leave _Py_TPFLAGS_STATIC_BUILTIN set on tp_flags. */ } @@ -4560,15 +4551,17 @@ _PyType_GetSubclasses(PyTypeObject *self) Py_ssize_t i = 0; PyObject *ref; // borrowed ref while (PyDict_Next(subclasses, &i, NULL, &ref)) { - PyTypeObject *subclass = subclass_from_ref(ref); // borrowed + PyTypeObject *subclass = subclass_from_ref(ref); if (subclass == NULL) { continue; } if (PyList_Append(list, _PyObject_CAST(subclass)) < 0) { + Py_DECREF(subclass); Py_DECREF(list); return NULL; } + Py_DECREF(subclass); } return list; } @@ -7083,7 +7076,7 @@ static inline PyTypeObject * subclass_from_ref(PyObject *ref) { assert(PyWeakref_CheckRef(ref)); - PyObject *obj = PyWeakref_GET_OBJECT(ref); // borrowed ref + PyObject *obj = PyWeakref_FetchObject(ref); assert(obj != NULL); if (obj == Py_None) { return NULL; @@ -7108,10 +7101,12 @@ get_subclasses_key(PyTypeObject *type, PyTypeObject *base) PyObject *subclasses = lookup_subclasses(base); if (subclasses != NULL) { while (PyDict_Next(subclasses, &i, &key, &ref)) { - PyTypeObject *subclass = subclass_from_ref(ref); // borrowed + PyTypeObject *subclass = subclass_from_ref(ref); if (subclass == type) { + Py_DECREF(subclass); return Py_NewRef(key); } + Py_DECREF(subclass); } } /* It wasn't found. */ @@ -9190,7 +9185,7 @@ recurse_down_subclasses(PyTypeObject *type, PyObject *attr_name, Py_ssize_t i = 0; PyObject *ref; while (PyDict_Next(subclasses, &i, NULL, &ref)) { - PyTypeObject *subclass = subclass_from_ref(ref); // borrowed + PyTypeObject *subclass = subclass_from_ref(ref); if (subclass == NULL) { continue; } @@ -9200,6 +9195,7 @@ recurse_down_subclasses(PyTypeObject *type, PyObject *attr_name, if (dict != NULL && PyDict_Check(dict)) { int r = PyDict_Contains(dict, attr_name); if (r < 0) { + Py_DECREF(subclass); return -1; } if (r > 0) { @@ -9208,8 +9204,10 @@ recurse_down_subclasses(PyTypeObject *type, PyObject *attr_name, } if (update_subclasses(subclass, attr_name, callback, data) < 0) { + Py_DECREF(subclass); return -1; } + Py_DECREF(subclass); } return 0; } diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index bd7720e2753..be7095e26b5 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -1,88 +1,63 @@ #include "Python.h" -#include "pycore_object.h" // _PyObject_GET_WEAKREFS_LISTPTR() +#include "pycore_object.h" // _PyObject_GET_WEAKREFS_CONTROLPTR() #include "structmember.h" // PyMemberDef +#include "pycore_refcnt.h" +#include "pyatomic.h" -#define GET_WEAKREFS_LISTPTR(o) \ - ((PyWeakReference **) _PyObject_GET_WEAKREFS_LISTPTR(o)) +typedef struct _PyWeakrefBase PyWeakrefBase; +extern PyTypeObject _PyWeakref_ControlType; Py_ssize_t -_PyWeakref_GetWeakrefCount(PyWeakReference *head) +_PyWeakref_GetWeakrefCount(PyWeakrefControl *ctrl) { + if (ctrl == NULL) { + return 0; + } + + PyWeakrefBase *head = &ctrl->base; + PyWeakrefBase *ref; Py_ssize_t count = 0; - while (head != NULL) { + ref = head->wr_next; + while (ref != head) { ++count; - head = head->wr_next; + ref = ref->wr_next; } + return count; } static PyObject *weakref_vectorcall(PyWeakReference *self, PyObject *const *args, size_t nargsf, PyObject *kwnames); -static void -init_weakref(PyWeakReference *self, PyObject *ob, PyObject *callback) -{ - self->hash = -1; - self->wr_object = ob; - self->wr_prev = NULL; - self->wr_next = NULL; - self->wr_callback = Py_XNewRef(callback); - self->vectorcall = (vectorcallfunc)weakref_vectorcall; -} - static PyWeakReference * -new_weakref(PyObject *ob, PyObject *callback) +new_weakref(PyTypeObject *type, PyWeakrefControl *root, PyObject *callback) { - PyWeakReference *result; - - result = PyObject_GC_New(PyWeakReference, &_PyWeakref_RefType); - if (result) { - init_weakref(result, ob, callback); - PyObject_GC_Track(result); + PyWeakReference *self = (PyWeakReference *)type->tp_alloc(type, 0); + if (!self) { + return NULL; } - return result; + _PyObject_SetMaybeWeakref((PyObject *)self); + self->hash = -1; + self->base.wr_prev = NULL; + self->base.wr_next = NULL; + self->vectorcall = (vectorcallfunc)weakref_vectorcall; + self->wr_parent = (PyWeakrefControl *)Py_NewRef(root); + self->wr_callback = Py_XNewRef(callback); + return self; } - /* This function clears the passed-in reference and removes it from the * list of weak references for the referent. This is the only code that * removes an item from the doubly-linked list of weak references for an * object; it is also responsible for clearing the callback slot. - */ -static void -clear_weakref(PyWeakReference *self) -{ - PyObject *callback = self->wr_callback; - - if (self->wr_object != Py_None) { - PyWeakReference **list = GET_WEAKREFS_LISTPTR(self->wr_object); - - if (*list == self) - /* If 'self' is the end of the list (and thus self->wr_next == NULL) - then the weakref list itself (and thus the value of *list) will - end up being set to NULL. */ - *list = self->wr_next; - self->wr_object = Py_None; - if (self->wr_prev != NULL) - self->wr_prev->wr_next = self->wr_next; - if (self->wr_next != NULL) - self->wr_next->wr_prev = self->wr_prev; - self->wr_prev = NULL; - self->wr_next = NULL; - } - if (callback != NULL) { - Py_DECREF(callback); - self->wr_callback = NULL; - } -} - -/* Cyclic gc uses this to *just* clear the passed-in reference, leaving + * + * Cyclic gc uses this to *just* detach the passed-in reference, leaving * the callback intact and uncalled. It must be possible to call self's * tp_dealloc() after calling this, so self has to be left in a sane enough * state for that to work. We expect tp_dealloc to decref the callback - * then. The reason for not letting clear_weakref() decref the callback + * then. The reason for not letting this function decref the callback * right now is that if the callback goes away, that may in turn trigger * another callback (if a weak reference to the callback exists) -- running * arbitrary Python code in the middle of gc is a disaster. The convolution @@ -90,25 +65,26 @@ clear_weakref(PyWeakReference *self) * a sane state again. */ void -_PyWeakref_ClearRef(PyWeakReference *self) +_PyWeakref_DetachRef(PyWeakReference *ref) { - PyObject *callback; - - assert(self != NULL); - assert(PyWeakref_Check(self)); - /* Preserve and restore the callback around clear_weakref. */ - callback = self->wr_callback; - self->wr_callback = NULL; - clear_weakref(self); - self->wr_callback = callback; -} + PyWeakrefControl *ctrl = ref->wr_parent; + if (ctrl == NULL) { + return; + } -static void -weakref_dealloc(PyObject *self) -{ - PyObject_GC_UnTrack(self); - clear_weakref((PyWeakReference *) self); - Py_TYPE(self)->tp_free(self); + _PyMutex_lock(&ctrl->mutex); + PyWeakrefBase *base = (PyWeakrefBase *)ref; + PyWeakrefBase *prev = base->wr_prev; + if (prev != NULL) { + PyWeakrefBase *next = base->wr_next; + prev->wr_next = next; + next->wr_prev = prev; + } + base->wr_prev = base->wr_next = NULL; + _PyMutex_unlock(&ctrl->mutex); + + ref->wr_parent = NULL; + Py_DECREF(ctrl); } @@ -123,10 +99,19 @@ gc_traverse(PyWeakReference *self, visitproc visit, void *arg) static int gc_clear(PyWeakReference *self) { - clear_weakref(self); + Py_CLEAR(self->wr_callback); return 0; } +static void +weakref_dealloc(PyObject *self) +{ + PyObject_GC_UnTrack(self); + gc_clear((PyWeakReference *)self); + _PyWeakref_DetachRef((PyWeakReference *)self); + Py_TYPE(self)->tp_free(self); +} + static PyObject * weakref_vectorcall(PyWeakReference *self, PyObject *const *args, @@ -139,7 +124,7 @@ weakref_vectorcall(PyWeakReference *self, PyObject *const *args, if (!_PyArg_CheckPositional("weakref", nargs, 0, 0)) { return NULL; } - return Py_NewRef(PyWeakref_GET_OBJECT(self)); + return PyWeakref_FetchObject((PyObject *)self); } static Py_hash_t @@ -147,12 +132,13 @@ weakref_hash(PyWeakReference *self) { if (self->hash != -1) return self->hash; - PyObject* obj = PyWeakref_GET_OBJECT(self); + + PyObject *obj = PyWeakref_FetchObject((PyObject *)self); if (obj == Py_None) { PyErr_SetString(PyExc_TypeError, "weak object has gone away"); return -1; } - Py_INCREF(obj); + self->hash = PyObject_Hash(obj); Py_DECREF(obj); return self->hash; @@ -163,13 +149,12 @@ static PyObject * weakref_repr(PyWeakReference *self) { PyObject *name, *repr; - PyObject* obj = PyWeakref_GET_OBJECT(self); + PyObject* obj = PyWeakref_FetchObject((PyObject *)self); if (obj == Py_None) { return PyUnicode_FromFormat("", self); } - Py_INCREF(obj); if (_PyObject_LookupAttr(obj, &_Py_ID(__name__), &name) < 0) { Py_DECREF(obj); return NULL; @@ -178,14 +163,14 @@ weakref_repr(PyWeakReference *self) repr = PyUnicode_FromFormat( "", self, - Py_TYPE(PyWeakref_GET_OBJECT(self))->tp_name, + Py_TYPE(obj)->tp_name, obj); } else { repr = PyUnicode_FromFormat( "", self, - Py_TYPE(PyWeakref_GET_OBJECT(self))->tp_name, + Py_TYPE(obj)->tp_name, obj, name); } @@ -206,79 +191,148 @@ weakref_richcompare(PyWeakReference* self, PyWeakReference* other, int op) !PyWeakref_Check(other)) { Py_RETURN_NOTIMPLEMENTED; } - if (PyWeakref_GET_OBJECT(self) == Py_None - || PyWeakref_GET_OBJECT(other) == Py_None) { + PyObject* obj = PyWeakref_FetchObject((PyObject *)self); + PyObject* other_obj = PyWeakref_FetchObject((PyObject *)other); + if (obj == Py_None || other_obj == Py_None) { int res = (self == other); if (op == Py_NE) res = !res; - if (res) - Py_RETURN_TRUE; - else - Py_RETURN_FALSE; + Py_DECREF(obj); + Py_DECREF(other_obj); + return res ? Py_True : Py_False; } - PyObject* obj = PyWeakref_GET_OBJECT(self); - PyObject* other_obj = PyWeakref_GET_OBJECT(other); - Py_INCREF(obj); - Py_INCREF(other_obj); PyObject* res = PyObject_RichCompare(obj, other_obj, op); Py_DECREF(obj); Py_DECREF(other_obj); return res; } -/* Given the head of an object's list of weak references, extract the - * two callback-less refs (ref and proxy). Used to determine if the - * shared references exist and to determine the back link for newly - * inserted references. - */ +/* Insert 'newref' in the list before 'next'. Both must be non-NULL. */ static void -get_basic_refs(PyWeakReference *head, - PyWeakReference **refp, PyWeakReference **proxyp) +insert_before(PyWeakrefBase *newref, PyWeakrefBase *next) { - *refp = NULL; - *proxyp = NULL; - - if (head != NULL && head->wr_callback == NULL) { - /* We need to be careful that the "basic refs" aren't - subclasses of the main types. That complicates this a - little. */ - if (PyWeakref_CheckRefExact(head)) { - *refp = head; - head = head->wr_next; - } - if (head != NULL - && head->wr_callback == NULL - && PyWeakref_CheckProxy(head)) { - *proxyp = head; - /* head = head->wr_next; */ - } - } + newref->wr_next = next; + newref->wr_prev = next->wr_prev; + next->wr_prev->wr_next = newref; + next->wr_prev = newref; } /* Insert 'newref' in the list after 'prev'. Both must be non-NULL. */ static void -insert_after(PyWeakReference *newref, PyWeakReference *prev) +insert_after(PyWeakrefBase *newref, PyWeakrefBase *prev) { newref->wr_prev = prev; newref->wr_next = prev->wr_next; - if (prev->wr_next != NULL) - prev->wr_next->wr_prev = newref; + prev->wr_next->wr_prev = newref; prev->wr_next = newref; } -/* Insert 'newref' at the head of the list; 'list' points to the variable - * that stores the head. - */ -static void -insert_head(PyWeakReference *newref, PyWeakReference **list) +static PyWeakrefControl * +PyWeakref_Control(PyObject *ob) { - PyWeakReference *next = *list; + PyWeakrefControl **wrptr = _PyObject_GET_WEAKREFS_CONTROLPTR(ob); - newref->wr_prev = NULL; - newref->wr_next = next; - if (next != NULL) - next->wr_prev = newref; - *list = newref; + PyWeakrefControl *ctrl = _Py_atomic_load_ptr(wrptr); + if (ctrl != NULL) { + return ctrl; + } + + ctrl = PyObject_New(PyWeakrefControl, &_PyWeakref_ControlType); + if (ctrl == NULL) { + return NULL; + } + memset(&ctrl->mutex, 0, sizeof(ctrl->mutex)); + ctrl->wr_object = ob; + _PyObject_SetMaybeWeakref(ob); + + PyWeakrefBase *base = &ctrl->base; + base->wr_prev = base->wr_next = base; + + if (!_Py_atomic_compare_exchange_ptr(wrptr, NULL, ctrl)) { + /* Another thread already set the ctrl weakref; use it */ + Py_DECREF(ctrl); + ctrl = _Py_atomic_load_ptr(wrptr); + assert(ctrl != NULL); + } + + return ctrl; +} + +static int +try_incref(PyObject *op) +{ + return _Py_TryIncrefFast(op) || _Py_TryIncRefShared(op); +} + +static PyWeakReference * +weakref_matching(PyWeakrefControl *ctrl, PyTypeObject *type) +{ + assert(_PyMutex_is_locked(&ctrl->mutex)); + PyWeakrefBase *wr = ctrl->base.wr_prev; + int i = 0; + while (wr != &ctrl->base && i < 2) { + PyWeakReference *ref = (PyWeakReference *)wr; + if (Py_TYPE(ref) == type && ref->wr_callback == NULL) { + if (try_incref((PyObject *)ref)) { + return ref; + } + } + wr = wr->wr_prev; + i++; + } + return NULL; +} + +static PyObject * +PyWeakref_NewWithType(PyTypeObject *type, PyObject *ob, PyObject *callback) +{ + if (!PyType_SUPPORTS_WEAKREFS(Py_TYPE(ob))) { + PyErr_Format(PyExc_TypeError, + "cannot create weak reference to '%s' object", + Py_TYPE(ob)->tp_name); + return NULL; + } + + if (callback == Py_None) + callback = NULL; + + PyWeakrefControl *root = PyWeakref_Control(ob); + if (root == NULL) { + return NULL; + } + + int can_reuse = (callback == NULL && + (type == &_PyWeakref_RefType || + type == &_PyWeakref_ProxyType || + type == &_PyWeakref_CallableProxyType)); + + if (can_reuse) { + /* We can re-use an existing reference. */ + PyWeakReference *wr; + _PyMutex_lock(&root->mutex); + wr = weakref_matching(root, type); + _PyMutex_unlock(&root->mutex); + + if (wr != NULL) { + return (PyObject *)wr; + } + } + + /* We have to create a new reference. */ + PyWeakReference *self = new_weakref(type, root, callback); + if (self == NULL) { + return NULL; + } + + _PyMutex_lock(&root->mutex); + if (can_reuse) { + insert_before(&self->base, &root->base); + } + else { + insert_after(&self->base, &root->base); + } + _PyMutex_unlock(&root->mutex); + return (PyObject *)self; } static int @@ -291,54 +345,13 @@ parse_weakref_init_args(const char *funcname, PyObject *args, PyObject *kwargs, static PyObject * weakref___new__(PyTypeObject *type, PyObject *args, PyObject *kwargs) { - PyWeakReference *self = NULL; PyObject *ob, *callback = NULL; - if (parse_weakref_init_args("__new__", args, kwargs, &ob, &callback)) { - PyWeakReference *ref, *proxy; - PyWeakReference **list; - - if (!_PyType_SUPPORTS_WEAKREFS(Py_TYPE(ob))) { - PyErr_Format(PyExc_TypeError, - "cannot create weak reference to '%s' object", - Py_TYPE(ob)->tp_name); - return NULL; - } - if (callback == Py_None) - callback = NULL; - list = GET_WEAKREFS_LISTPTR(ob); - get_basic_refs(*list, &ref, &proxy); - if (callback == NULL && type == &_PyWeakref_RefType) { - if (ref != NULL) { - /* We can re-use an existing reference. */ - return Py_NewRef(ref); - } - } - /* We have to create a new reference. */ - /* Note: the tp_alloc() can trigger cyclic GC, so the weakref - list on ob can be mutated. This means that the ref and - proxy pointers we got back earlier may have been collected, - so we need to compute these values again before we use - them. */ - self = (PyWeakReference *) (type->tp_alloc(type, 0)); - if (self != NULL) { - init_weakref(self, ob, callback); - if (callback == NULL && type == &_PyWeakref_RefType) { - insert_head(self, list); - } - else { - PyWeakReference *prev; - - get_basic_refs(*list, &ref, &proxy); - prev = (proxy == NULL) ? ref : proxy; - if (prev == NULL) - insert_head(self, list); - else - insert_after(self, prev); - } - } + if (!parse_weakref_init_args("__new__", args, kwargs, &ob, &callback)) { + return NULL; } - return (PyObject *)self; + + return PyWeakref_NewWithType(type, ob, callback); } static int @@ -355,6 +368,17 @@ weakref___init__(PyObject *self, PyObject *args, PyObject *kwargs) return -1; } +PyTypeObject +_PyWeakref_ControlType = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + .tp_name = "weakref_control", + .tp_dealloc = (destructor)PyObject_Del, + .tp_basicsize = sizeof(PyWeakrefControl), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_alloc = PyType_GenericAlloc, + .tp_free = PyObject_Del +}; + static PyMemberDef weakref_members[] = { {"__callback__", T_OBJECT, offsetof(PyWeakReference, wr_callback), READONLY}, @@ -391,34 +415,31 @@ _PyWeakref_RefType = { }; -static int -proxy_checkref(PyWeakReference *proxy) -{ - if (PyWeakref_GET_OBJECT(proxy) == Py_None) { - PyErr_SetString(PyExc_ReferenceError, - "weakly-referenced object no longer exists"); - return 0; - } - return 1; -} - - -/* If a parameter is a proxy, check that it is still "live" and wrap it, - * replacing the original value with the raw object. Raises ReferenceError - * if the param is a dead proxy. - */ -#define UNWRAP(o) \ - if (PyWeakref_CheckProxy(o)) { \ - if (!proxy_checkref((PyWeakReference *)o)) \ - return NULL; \ - o = PyWeakref_GET_OBJECT(o); \ - } +static PyObject * +dead_proxy_error(void) +{ + PyErr_SetString(PyExc_ReferenceError, + "weakly-referenced object no longer exists"); + return NULL; +} + +#define UNWRAP(o, ...) \ + do { \ + if (!PyWeakref_Check(o)) { \ + Py_INCREF(o); \ + break; \ + } \ + o = PyWeakref_FetchObject(o); \ + if (o == Py_None) { \ + __VA_ARGS__; \ + return dead_proxy_error(); \ + } \ + } while (0) #define WRAP_UNARY(method, generic) \ static PyObject * \ method(PyObject *proxy) { \ UNWRAP(proxy); \ - Py_INCREF(proxy); \ PyObject* res = generic(proxy); \ Py_DECREF(proxy); \ return res; \ @@ -428,9 +449,7 @@ proxy_checkref(PyWeakReference *proxy) static PyObject * \ method(PyObject *x, PyObject *y) { \ UNWRAP(x); \ - UNWRAP(y); \ - Py_INCREF(x); \ - Py_INCREF(y); \ + UNWRAP(y, Py_DECREF(x)); \ PyObject* res = generic(x, y); \ Py_DECREF(x); \ Py_DECREF(y); \ @@ -444,12 +463,10 @@ proxy_checkref(PyWeakReference *proxy) static PyObject * \ method(PyObject *proxy, PyObject *v, PyObject *w) { \ UNWRAP(proxy); \ - UNWRAP(v); \ - if (w != NULL) \ - UNWRAP(w); \ - Py_INCREF(proxy); \ - Py_INCREF(v); \ - Py_XINCREF(w); \ + UNWRAP(v, Py_DECREF(proxy)); \ + if (w != NULL) { \ + UNWRAP(w, Py_DECREF(proxy), Py_DECREF(v)); \ + } \ PyObject* res = generic(proxy, v, w); \ Py_DECREF(proxy); \ Py_DECREF(v); \ @@ -460,8 +477,9 @@ proxy_checkref(PyWeakReference *proxy) #define WRAP_METHOD(method, SPECIAL) \ static PyObject * \ method(PyObject *proxy, PyObject *Py_UNUSED(ignored)) { \ - UNWRAP(proxy); \ - Py_INCREF(proxy); \ + assert(PyWeakref_Check(proxy)); \ + proxy = PyWeakref_FetchObject(proxy); \ + if (proxy == Py_None) return dead_proxy_error(); \ PyObject* res = PyObject_CallMethodNoArgs(proxy, &_Py_ID(SPECIAL)); \ Py_DECREF(proxy); \ return res; \ @@ -477,21 +495,26 @@ WRAP_TERNARY(proxy_call, PyObject_Call) static PyObject * proxy_repr(PyWeakReference *proxy) { - return PyUnicode_FromFormat( + PyObject *obj, *repr; + + obj = PyWeakref_FetchObject((PyObject *)proxy); + repr = PyUnicode_FromFormat( "", proxy, - Py_TYPE(PyWeakref_GET_OBJECT(proxy))->tp_name, - PyWeakref_GET_OBJECT(proxy)); -} + Py_TYPE(obj)->tp_name, + obj); + Py_DECREF(obj); + return repr; +} static int -proxy_setattr(PyWeakReference *proxy, PyObject *name, PyObject *value) +proxy_setattr(PyObject *proxy, PyObject *name, PyObject *value) { - if (!proxy_checkref(proxy)) - return -1; - PyObject *obj = PyWeakref_GET_OBJECT(proxy); - Py_INCREF(obj); + PyObject *obj = PyWeakref_FetchObject(proxy); + if (obj == Py_None) { + return dead_proxy_error(), -1; + } int res = PyObject_SetAttr(obj, name, value); Py_DECREF(obj); return res; @@ -500,9 +523,15 @@ proxy_setattr(PyWeakReference *proxy, PyObject *name, PyObject *value) static PyObject * proxy_richcompare(PyObject *proxy, PyObject *v, int op) { - UNWRAP(proxy); - UNWRAP(v); - return PyObject_RichCompare(proxy, v, op); + proxy = PyWeakref_FetchObject(proxy); + if (proxy == Py_None) { + return dead_proxy_error(); + } + UNWRAP(v, Py_DECREF(proxy)); + PyObject *ret = PyObject_RichCompare(proxy, v, op); + Py_DECREF(proxy); + Py_DECREF(v); + return ret; } /* number slots */ @@ -542,38 +571,26 @@ WRAP_BINARY(proxy_matmul, PyNumber_MatrixMultiply) WRAP_BINARY(proxy_imatmul, PyNumber_InPlaceMatrixMultiply) static int -proxy_bool(PyWeakReference *proxy) +proxy_bool(PyObject *proxy) { - PyObject *o = PyWeakref_GET_OBJECT(proxy); - if (!proxy_checkref(proxy)) { - return -1; + PyObject *obj = PyWeakref_FetchObject(proxy); + if (obj == Py_None) { + return dead_proxy_error(), -1; } - Py_INCREF(o); - int res = PyObject_IsTrue(o); - Py_DECREF(o); + int res = PyObject_IsTrue(obj); + Py_DECREF(obj); return res; } -static void -proxy_dealloc(PyWeakReference *self) -{ - PyObject_GC_UnTrack(self); - if (self->wr_callback != NULL) - PyObject_GC_UnTrack((PyObject *)self); - clear_weakref(self); - PyObject_GC_Del(self); -} - /* sequence slots */ static int -proxy_contains(PyWeakReference *proxy, PyObject *value) +proxy_contains(PyObject *proxy, PyObject *value) { - if (!proxy_checkref(proxy)) - return -1; - - PyObject *obj = PyWeakref_GET_OBJECT(proxy); - Py_INCREF(obj); + PyObject *obj = PyWeakref_FetchObject(proxy); + if (obj == Py_None) { + return dead_proxy_error(), -1; + } int res = PySequence_Contains(obj, value); Py_DECREF(obj); return res; @@ -582,13 +599,12 @@ proxy_contains(PyWeakReference *proxy, PyObject *value) /* mapping slots */ static Py_ssize_t -proxy_length(PyWeakReference *proxy) +proxy_length(PyObject *proxy) { - if (!proxy_checkref(proxy)) - return -1; - - PyObject *obj = PyWeakref_GET_OBJECT(proxy); - Py_INCREF(obj); + PyObject *obj = PyWeakref_FetchObject(proxy); + if (obj == Py_None) { + return dead_proxy_error(), -1; + } Py_ssize_t res = PyObject_Length(obj); Py_DECREF(obj); return res; @@ -597,13 +613,12 @@ proxy_length(PyWeakReference *proxy) WRAP_BINARY(proxy_getitem, PyObject_GetItem) static int -proxy_setitem(PyWeakReference *proxy, PyObject *key, PyObject *value) +proxy_setitem(PyObject *proxy, PyObject *key, PyObject *value) { - if (!proxy_checkref(proxy)) - return -1; - - PyObject *obj = PyWeakref_GET_OBJECT(proxy); - Py_INCREF(obj); + PyObject *obj = PyWeakref_FetchObject(proxy); + if (obj == Py_None) { + return dead_proxy_error(), -1; + } int res; if (value == NULL) { res = PyObject_DelItem(obj, key); @@ -617,31 +632,31 @@ proxy_setitem(PyWeakReference *proxy, PyObject *key, PyObject *value) /* iterator slots */ static PyObject * -proxy_iter(PyWeakReference *proxy) +proxy_iter(PyObject *proxy) { - if (!proxy_checkref(proxy)) - return NULL; - PyObject *obj = PyWeakref_GET_OBJECT(proxy); - Py_INCREF(obj); + PyObject *obj = PyWeakref_FetchObject(proxy); + if (obj == Py_None) { + return dead_proxy_error(); + } PyObject* res = PyObject_GetIter(obj); Py_DECREF(obj); return res; } static PyObject * -proxy_iternext(PyWeakReference *proxy) +proxy_iternext(PyObject *proxy) { - if (!proxy_checkref(proxy)) - return NULL; - - PyObject *obj = PyWeakref_GET_OBJECT(proxy); + PyObject *obj = PyWeakref_FetchObject(proxy); + if (obj == Py_None) { + return dead_proxy_error(); + } if (!PyIter_Check(obj)) { PyErr_Format(PyExc_TypeError, "Weakref proxy referenced a non-iterator '%.200s' object", Py_TYPE(obj)->tp_name); + Py_DECREF(obj); return NULL; } - Py_INCREF(obj); PyObject* res = PyIter_Next(obj); Py_DECREF(obj); return res; @@ -723,7 +738,7 @@ _PyWeakref_ProxyType = { sizeof(PyWeakReference), 0, /* methods */ - (destructor)proxy_dealloc, /* tp_dealloc */ + (destructor)weakref_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ @@ -758,7 +773,7 @@ _PyWeakref_CallableProxyType = { sizeof(PyWeakReference), 0, /* methods */ - (destructor)proxy_dealloc, /* tp_dealloc */ + (destructor)weakref_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ @@ -788,133 +803,54 @@ _PyWeakref_CallableProxyType = { PyObject * PyWeakref_NewRef(PyObject *ob, PyObject *callback) { - PyWeakReference *result = NULL; - PyWeakReference **list; - PyWeakReference *ref, *proxy; - - if (!_PyType_SUPPORTS_WEAKREFS(Py_TYPE(ob))) { - PyErr_Format(PyExc_TypeError, - "cannot create weak reference to '%s' object", - Py_TYPE(ob)->tp_name); - return NULL; - } - list = GET_WEAKREFS_LISTPTR(ob); - get_basic_refs(*list, &ref, &proxy); - if (callback == Py_None) - callback = NULL; - if (callback == NULL) - /* return existing weak reference if it exists */ - result = ref; - if (result != NULL) - Py_INCREF(result); - else { - /* Note: new_weakref() can trigger cyclic GC, so the weakref - list on ob can be mutated. This means that the ref and - proxy pointers we got back earlier may have been collected, - so we need to compute these values again before we use - them. */ - result = new_weakref(ob, callback); - if (result != NULL) { - get_basic_refs(*list, &ref, &proxy); - if (callback == NULL) { - if (ref == NULL) - insert_head(result, list); - else { - /* Someone else added a ref without a callback - during GC. Return that one instead of this one - to avoid violating the invariants of the list - of weakrefs for ob. */ - Py_SETREF(result, (PyWeakReference*)Py_NewRef(ref)); - } - } - else { - PyWeakReference *prev; - - prev = (proxy == NULL) ? ref : proxy; - if (prev == NULL) - insert_head(result, list); - else - insert_after(result, prev); - } - } - } - return (PyObject *) result; + return PyWeakref_NewWithType(&_PyWeakref_RefType, ob, callback); } PyObject * PyWeakref_NewProxy(PyObject *ob, PyObject *callback) { - PyWeakReference *result = NULL; - PyWeakReference **list; - PyWeakReference *ref, *proxy; - - if (!_PyType_SUPPORTS_WEAKREFS(Py_TYPE(ob))) { - PyErr_Format(PyExc_TypeError, - "cannot create weak reference to '%s' object", - Py_TYPE(ob)->tp_name); - return NULL; - } - list = GET_WEAKREFS_LISTPTR(ob); - get_basic_refs(*list, &ref, &proxy); - if (callback == Py_None) - callback = NULL; - if (callback == NULL) - /* attempt to return an existing weak reference if it exists */ - result = proxy; - if (result != NULL) - Py_INCREF(result); - else { - /* Note: new_weakref() can trigger cyclic GC, so the weakref - list on ob can be mutated. This means that the ref and - proxy pointers we got back earlier may have been collected, - so we need to compute these values again before we use - them. */ - result = new_weakref(ob, callback); - if (result != NULL) { - PyWeakReference *prev; - - if (PyCallable_Check(ob)) { - Py_SET_TYPE(result, &_PyWeakref_CallableProxyType); - } - else { - Py_SET_TYPE(result, &_PyWeakref_ProxyType); - } - get_basic_refs(*list, &ref, &proxy); - if (callback == NULL) { - if (proxy != NULL) { - /* Someone else added a proxy without a callback - during GC. Return that one instead of this one - to avoid violating the invariants of the list - of weakrefs for ob. */ - Py_SETREF(result, (PyWeakReference*)Py_NewRef(proxy)); - goto skip_insert; - } - prev = ref; - } - else - prev = (proxy == NULL) ? ref : proxy; - - if (prev == NULL) - insert_head(result, list); - else - insert_after(result, prev); - skip_insert: - ; - } - } - return (PyObject *) result; + PyTypeObject *type; + if (PyCallable_Check(ob)) + type = &_PyWeakref_CallableProxyType; + else + type = &_PyWeakref_ProxyType; + return PyWeakref_NewWithType(type, ob, callback); } PyObject * PyWeakref_GetObject(PyObject *ref) +{ + PyObject *obj = PyWeakref_FetchObject(ref); + Py_XDECREF(obj); + return obj; +} + + +PyObject * +PyWeakref_FetchObject(PyObject *ref) { if (ref == NULL || !PyWeakref_Check(ref)) { PyErr_BadInternalCall(); return NULL; } - return PyWeakref_GET_OBJECT(ref); + + PyWeakReference *wr = (PyWeakReference *)ref; + if (wr->wr_parent == NULL) { + return Py_None; + } + + PyWeakrefControl *ctrl = wr->wr_parent; + _PyMutex_lock(&ctrl->mutex); + PyObject *obj = ctrl->wr_object; + assert((_PyObject_IS_IMMORTAL(obj) || obj->ob_ref_shared & _Py_REF_SHARED_FLAG_MASK) != 0); + if (!try_incref(obj)) { + obj = Py_None; + } + _PyMutex_unlock(&ctrl->mutex); + + return obj; } /* Note that there's an inlined copy-paste of handle_callback() in gcmodule.c's @@ -931,17 +867,63 @@ handle_callback(PyWeakReference *ref, PyObject *callback) Py_DECREF(cbresult); } +static Py_ssize_t +_PyWeakref_DetachRefs(PyWeakrefControl *ctrl, PyWeakReference *list[], Py_ssize_t max) +{ + Py_ssize_t count = 0; + + PyWeakrefBase *head = &ctrl->base; + PyWeakrefBase *current = head->wr_next; + while (current != head && count < max) { + PyWeakrefBase *next = current->wr_next; + + if (try_incref((PyObject *)current)) { + list[count] = (PyWeakReference *)current; + ++count; + } + + current->wr_next = NULL; + current->wr_prev = NULL; + + current = next; + } + + head->wr_next = current; + current->wr_prev = head; + + return count; +} + +void +_PyObject_ClearWeakRefsFromDealloc(PyObject *object) +{ + PyWeakrefControl **wrptr = _PyObject_GET_WEAKREFS_CONTROLPTR(object); + PyWeakrefControl *root = *wrptr; + if (!root) + return; + + *wrptr = NULL; + + _PyMutex_lock(&root->mutex); + root->wr_object = Py_None; + _PyMutex_unlock(&root->mutex); + + Py_DECREF(root); +} + /* This function is called by the tp_dealloc handler to clear weak references. * * This iterates through the weak references for 'object' and calls callbacks * for those references which have one. It returns when all callbacks have * been attempted. + * + * Thread safety note: no other threads may create weak references to this + * object concurrent with this function. However, they may destroy weak + * references concurrently. */ void PyObject_ClearWeakRefs(PyObject *object) { - PyWeakReference **list; - if (object == NULL || !_PyType_SUPPORTS_WEAKREFS(Py_TYPE(object)) || Py_REFCNT(object) != 0) @@ -949,69 +931,54 @@ PyObject_ClearWeakRefs(PyObject *object) PyErr_BadInternalCall(); return; } - list = GET_WEAKREFS_LISTPTR(object); - /* Remove the callback-less basic and proxy references */ - if (*list != NULL && (*list)->wr_callback == NULL) { - clear_weakref(*list); - if (*list != NULL && (*list)->wr_callback == NULL) - clear_weakref(*list); + + PyWeakrefControl **wrptr = _PyObject_GET_WEAKREFS_CONTROLPTR(object); + + PyWeakrefControl *root = *wrptr; + if (!root) + return; + + *wrptr = NULL; + + assert(root->wr_object == object); + + int make_callbacks; + + _PyMutex_lock(&root->mutex); + make_callbacks = (root->wr_object != Py_None); + root->wr_object = Py_None; + int has_refs = root->base.wr_next != &root->base; + _PyMutex_unlock(&root->mutex); + + if (!has_refs) { + Py_DECREF(root); + return; } - if (*list != NULL) { - PyWeakReference *current = *list; - Py_ssize_t count = _PyWeakref_GetWeakrefCount(current); - PyObject *err_type, *err_value, *err_tb; - - PyErr_Fetch(&err_type, &err_value, &err_tb); - if (count == 1) { - PyObject *callback = current->wr_callback; - - current->wr_callback = NULL; - clear_weakref(current); - if (callback != NULL) { - if (Py_REFCNT((PyObject *)current) > 0) { - handle_callback(current, callback); - } - Py_DECREF(callback); - } - } - else { - PyObject *tuple; - Py_ssize_t i = 0; - - tuple = PyTuple_New(count * 2); - if (tuple == NULL) { - _PyErr_ChainExceptions(err_type, err_value, err_tb); - return; - } - for (i = 0; i < count; ++i) { - PyWeakReference *next = current->wr_next; - - if (Py_REFCNT((PyObject *)current) > 0) { - PyTuple_SET_ITEM(tuple, i * 2, Py_NewRef(current)); - PyTuple_SET_ITEM(tuple, i * 2 + 1, current->wr_callback); - } - else { - Py_DECREF(current->wr_callback); - } - current->wr_callback = NULL; - clear_weakref(current); - current = next; - } - for (i = 0; i < count; ++i) { - PyObject *callback = PyTuple_GET_ITEM(tuple, i * 2 + 1); - - /* The tuple may have slots left to NULL */ - if (callback != NULL) { - PyObject *item = PyTuple_GET_ITEM(tuple, i * 2); - handle_callback((PyWeakReference *)item, callback); - } + PyObject *type, *value, *traceback; + PyErr_Fetch(&type, &value, &traceback); + + PyWeakReference *list[16]; + while (has_refs) { + _PyMutex_lock(&root->mutex); + Py_ssize_t count = _PyWeakref_DetachRefs(root, list, 16); + has_refs = root->base.wr_next != &root->base; + _PyMutex_unlock(&root->mutex); + + for (Py_ssize_t i = 0; i < count; i++) { + PyWeakReference *ref = list[i]; + if (ref->wr_callback && make_callbacks) { + handle_callback(ref, ref->wr_callback); } - Py_DECREF(tuple); + Py_CLEAR(ref->wr_callback); + Py_DECREF(ref); } - assert(!PyErr_Occurred()); - PyErr_Restore(err_type, err_value, err_tb); } + + Py_DECREF(root); + + assert(!PyErr_Occurred()); + PyErr_Restore(type, value, traceback); } /* This function is called by _PyStaticType_Dealloc() to clear weak references. @@ -1023,12 +990,14 @@ PyObject_ClearWeakRefs(PyObject *object) void _PyStaticType_ClearWeakRefs(PyTypeObject *type) { - static_builtin_state *state = _PyStaticType_GetState(type); - PyObject **list = _PyStaticType_GET_WEAKREFS_LISTPTR(state); - while (*list != NULL) { - /* Note that clear_weakref() pops the first ref off the type's - weaklist before clearing its wr_object and wr_callback. - That is how we're able to loop over the list. */ - clear_weakref((PyWeakReference *)*list); + PyWeakrefControl **ptr = _PyObject_GET_WEAKREFS_CONTROLPTR((PyObject *)type); + PyWeakrefBase *head = (PyWeakrefBase *)(*ptr); + if (head == NULL) { + return; + } + while (head->wr_next != head) { + PyWeakReference *ref = (PyWeakReference *)head->wr_next; + _PyWeakref_DetachRef(ref); } + Py_CLEAR(*ptr); } diff --git a/PC/python3dll.c b/PC/python3dll.c index fd4b29cf031..7b33935428e 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -736,6 +736,7 @@ EXPORT_FUNC(PyUnicodeTranslateError_SetReason) EXPORT_FUNC(PyUnicodeTranslateError_SetStart) EXPORT_FUNC(PyVectorcall_Call) EXPORT_FUNC(PyVectorcall_NARGS) +EXPORT_FUNC(PyWeakref_FetchObject) EXPORT_FUNC(PyWeakref_GetObject) EXPORT_FUNC(PyWeakref_NewProxy) EXPORT_FUNC(PyWeakref_NewRef)