colesbury · GitHub

Bug report

When calling finalizers from dealloc, we temporarily increase the refcount with Py_SET_REFCNT and then decrease it again (also with Py_SET_REFCNT). This isn't thread-safe in the free threading build because some other thread may concurrently try to increase the refcount during a racy dictionary or list access.

/* Temporarily resurrect the object. */
Py_SET_REFCNT(self, 1);
PyObject_CallFinalizer(self);
_PyObject_ASSERT_WITH_MSG(self,
Py_REFCNT(self) > 0,
"refcount is too small");
/* Undo the temporary resurrection; can't use DECREF here, it would
* cause a recursive call. */
Py_SET_REFCNT(self, Py_REFCNT(self) - 1);
if (Py_REFCNT(self) == 0) {
return 0; /* this is the normal path out */
}

We have similar issues with some watcher events:

assert(Py_REFCNT(op) == 0);
Py_SET_REFCNT(op, 1);
handle_func_event(PyFunction_EVENT_DESTROY, op, NULL);
if (Py_REFCNT(op) > 1) {
Py_SET_REFCNT(op, Py_REFCNT(op) - 1);
return;
}
Py_SET_REFCNT(op, 0);

This can lead to crashes when we have racy accesses to objects that may be concurrently finalized. For example:

"./python -m test test_free_threading -m test_racing_load_super_attr -v -F Test (un)specialization of LOAD_SUPER_ATTR opcode. ... ./Include/internal/pycore_object.h:593: _Py_NegativeRefcount: Assertion failed: object has negative ref count Enable tracemalloc to get the memory block allocation traceback

Read the original on github.com ↗