Bug report
Bug description:
PyObject_CallFinalizerFromDealloc() starts with a fatal assertion:
|
int |
|
PyObject_CallFinalizerFromDealloc(PyObject *self) |
|
{ |
|
if (Py_REFCNT(self) != 0) { |
|
_PyObject_ASSERT_FAILED_MSG(self, |
|
"PyObject_CallFinalizerFromDealloc called " |
|
"on object with a non-zero refcount"); |
|
} |
|
|
|
/* Temporarily resurrect the object. */ |
|
_PyObject_ResurrectStart(self); |
|
|
|
PyObject_CallFinalizer(self); |
|
|
|
_PyObject_ASSERT_WITH_MSG(self, |
|
Py_REFCNT(self) > 0, |
|
"refcount is too small"); |
|
|
|
_PyObject_ASSERT(self, |
|
(!_PyType_IS_GC(Py_TYPE(self)) |
|
|| _PyObject_GC_IS_TRACKED(self))); |
|
|
|
/* Undo the temporary resurrection; can't use DECREF here, it would |
|
* cause a recursive call. */ |
|
if (_PyObject_ResurrectEnd(self)) { |
|
/* tp_finalize resurrected it! |
|
gh-130202: Note that the object may still be dead in the free |
|
threaded build in some circumstances, so it's not safe to access |
|
`self` after this point. For example, the last reference to the |
|
resurrected `self` may be held by another thread, which can |
|
concurrently deallocate it. */ |
|
return -1; |
|
} |
|
|
|
/* this is the normal path out, the caller continues with deallocation. */ |
|
return 0; |
|
} |
This was ok before FT-Python, but with freethreading concurrency, it can happen that an object gets resurrected by one thread before it even learns about its own finalisation in another thread. A fatal abort of the runtime seems very unhelpful in this case.
The fact that the function calls _PyObject_ResurrectStart() immediately after the assertion shows that the code is prepared for such a resurrection and could easily deal with it. Instead of the fatal assertion, it should check the refcount and abort the deallocation (by returning -1) if the refcount is higher than 0.
I noticed this issue while trying to port lxml to FT-Python. lxml uses unowned backlinks to Python objects from a C XML tree structure in order to assure a 1:1 mapping between C nodes and Python API proxy objects. The issues of the port are being discussed here:
lxml/lxml#477
CPython versions tested on:
3.14
Operating systems tested on:
No response
Bug report
Bug description:
PyObject_CallFinalizerFromDealloc()starts with a fatal assertion:cpython/Objects/object.c
Lines 591 to 627 in 6fcac09
This was ok before FT-Python, but with freethreading concurrency, it can happen that an object gets resurrected by one thread before it even learns about its own finalisation in another thread. A fatal abort of the runtime seems very unhelpful in this case.
The fact that the function calls
_PyObject_ResurrectStart()immediately after the assertion shows that the code is prepared for such a resurrection and could easily deal with it. Instead of the fatal assertion, it should check the refcount and abort the deallocation (by returning -1) if the refcount is higher than 0.I noticed this issue while trying to port lxml to FT-Python. lxml uses unowned backlinks to Python objects from a C XML tree structure in order to assure a 1:1 mapping between C nodes and Python API proxy objects. The issues of the port are being discussed here:
lxml/lxml#477
CPython versions tested on:
3.14
Operating systems tested on:
No response