mirror of
https://github.com/python/cpython.git
synced 2025-11-25 04:34:37 +00:00
Issue #13992: The trashcan mechanism is now thread-safe. This eliminates
sporadic crashes in multi-thread programs when several long deallocator chains ran concurrently and involved subclasses of built-in container types. Note that the trashcan functions are part of the stable ABI, therefore they have to be kept around for binary compatibility of extensions.
This commit is contained in:
commit
5b4faae307
7 changed files with 140 additions and 9 deletions
|
|
@ -1954,6 +1954,18 @@ _PyTrash_deposit_object(PyObject *op)
|
|||
_PyTrash_delete_later = op;
|
||||
}
|
||||
|
||||
/* The equivalent API, using per-thread state recursion info */
|
||||
void
|
||||
_PyTrash_thread_deposit_object(PyObject *op)
|
||||
{
|
||||
PyThreadState *tstate = PyThreadState_GET();
|
||||
assert(PyObject_IS_GC(op));
|
||||
assert(_Py_AS_GC(op)->gc.gc_refs == _PyGC_REFS_UNTRACKED);
|
||||
assert(op->ob_refcnt == 0);
|
||||
_Py_AS_GC(op)->gc.gc_prev = (PyGC_Head *) tstate->trash_delete_later;
|
||||
tstate->trash_delete_later = op;
|
||||
}
|
||||
|
||||
/* Dealloccate all the objects in the _PyTrash_delete_later list. Called when
|
||||
* the call-stack unwinds again.
|
||||
*/
|
||||
|
|
@ -1980,6 +1992,31 @@ _PyTrash_destroy_chain(void)
|
|||
}
|
||||
}
|
||||
|
||||
/* The equivalent API, using per-thread state recursion info */
|
||||
void
|
||||
_PyTrash_thread_destroy_chain(void)
|
||||
{
|
||||
PyThreadState *tstate = PyThreadState_GET();
|
||||
while (tstate->trash_delete_later) {
|
||||
PyObject *op = tstate->trash_delete_later;
|
||||
destructor dealloc = Py_TYPE(op)->tp_dealloc;
|
||||
|
||||
tstate->trash_delete_later =
|
||||
(PyObject*) _Py_AS_GC(op)->gc.gc_prev;
|
||||
|
||||
/* Call the deallocator directly. This used to try to
|
||||
* fool Py_DECREF into calling it indirectly, but
|
||||
* Py_DECREF was already called on this object, and in
|
||||
* assorted non-release builds calling Py_DECREF again ends
|
||||
* up distorting allocation statistics.
|
||||
*/
|
||||
assert(op->ob_refcnt == 0);
|
||||
++tstate->trash_delete_nesting;
|
||||
(*dealloc)(op);
|
||||
--tstate->trash_delete_nesting;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef Py_TRACE_REFS
|
||||
/* For Py_LIMITED_API, we need an out-of-line version of _Py_Dealloc.
|
||||
Define this here, so we can undefine the macro. */
|
||||
|
|
|
|||
|
|
@ -891,6 +891,7 @@ subtype_dealloc(PyObject *self)
|
|||
{
|
||||
PyTypeObject *type, *base;
|
||||
destructor basedealloc;
|
||||
PyThreadState *tstate = PyThreadState_GET();
|
||||
|
||||
/* Extract the type; we expect it to be a heap type */
|
||||
type = Py_TYPE(self);
|
||||
|
|
@ -940,8 +941,10 @@ subtype_dealloc(PyObject *self)
|
|||
/* See explanation at end of function for full disclosure */
|
||||
PyObject_GC_UnTrack(self);
|
||||
++_PyTrash_delete_nesting;
|
||||
++ tstate->trash_delete_nesting;
|
||||
Py_TRASHCAN_SAFE_BEGIN(self);
|
||||
--_PyTrash_delete_nesting;
|
||||
-- tstate->trash_delete_nesting;
|
||||
/* DO NOT restore GC tracking at this point. weakref callbacks
|
||||
* (if any, and whether directly here or indirectly in something we
|
||||
* call) may trigger GC, and if self is tracked at that point, it
|
||||
|
|
@ -1020,8 +1023,10 @@ subtype_dealloc(PyObject *self)
|
|||
|
||||
endlabel:
|
||||
++_PyTrash_delete_nesting;
|
||||
++ tstate->trash_delete_nesting;
|
||||
Py_TRASHCAN_SAFE_END(self);
|
||||
--_PyTrash_delete_nesting;
|
||||
-- tstate->trash_delete_nesting;
|
||||
|
||||
/* Explanation of the weirdness around the trashcan macros:
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue