-
-
Notifications
You must be signed in to change notification settings - Fork 31.7k
GH-124715: Move trashcan mechanism into Py_Dealloc
#132280
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a7c0082
2cb0a82
2d079a1
6ec46fa
235490e
c805603
8aa20f2
3514aa1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,3 @@ | ||||||||||||||
Prevents against stack overflows when calling Py_DECREF. Third-party | ||||||||||||||
extension objects no longer need to use the "trashcan" mechanism, as | ||||||||||||||
protection is now built into the ``Py_DECREF`` macro. | ||||||||||||||
Comment on lines
+1
to
+3
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -287,7 +287,9 @@ static void | |
capsule_dealloc(PyObject *op) | ||
{ | ||
PyCapsule *capsule = _PyCapsule_CAST(op); | ||
PyObject_GC_UnTrack(op); | ||
if (_PyObject_GC_IS_TRACKED(op)) { | ||
PyObject_GC_UnTrack(op); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this change needed? PyObject_GC_UnTrack() already checks if the object is tracked or not, no? |
||
if (capsule->destructor) { | ||
capsule->destructor(op); | ||
} | ||
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2908,13 +2908,15 @@ Py_ReprLeave(PyObject *obj) | |||||||||
void | ||||||||||
_PyTrash_thread_deposit_object(PyThreadState *tstate, PyObject *op) | ||||||||||
{ | ||||||||||
_PyObject_ASSERT(op, _PyObject_IS_GC(op)); | ||||||||||
_PyObject_ASSERT(op, !_PyObject_GC_IS_TRACKED(op)); | ||||||||||
_PyObject_ASSERT(op, Py_REFCNT(op) == 0); | ||||||||||
#ifdef Py_GIL_DISABLED | ||||||||||
op->ob_tid = (uintptr_t)tstate->delete_later; | ||||||||||
#else | ||||||||||
_PyGCHead_SET_PREV(_Py_AS_GC(op), (PyGC_Head*)tstate->delete_later); | ||||||||||
/* Store the pointer in the refcnt field. | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
* As this object may still be tracked by the GC, | ||||||||||
* it is important that we never store 0 (NULL). */ | ||||||||||
uintptr_t refcnt = (uintptr_t)tstate->delete_later; | ||||||||||
*((uintptr_t*)op) = refcnt+1; | ||||||||||
#endif | ||||||||||
tstate->delete_later = op; | ||||||||||
} | ||||||||||
|
@@ -2933,7 +2935,9 @@ _PyTrash_thread_destroy_chain(PyThreadState *tstate) | |||||||||
op->ob_tid = 0; | ||||||||||
_Py_atomic_store_ssize_relaxed(&op->ob_ref_shared, _Py_REF_MERGED); | ||||||||||
#else | ||||||||||
tstate->delete_later = (PyObject*) _PyGCHead_PREV(_Py_AS_GC(op)); | ||||||||||
uintptr_t refcnt = *((uintptr_t*)op); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||||||
tstate->delete_later = (PyObject *)(refcnt - 1); | ||||||||||
op->ob_refcnt = 0; | ||||||||||
#endif | ||||||||||
|
||||||||||
/* Call the deallocator directly. This used to try to | ||||||||||
|
@@ -2998,13 +3002,25 @@ _PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg, | |||||||||
} | ||||||||||
|
||||||||||
|
||||||||||
/* | ||||||||||
When deallocating a container object, it's possible to trigger an unbounded | ||||||||||
chain of deallocations, as each Py_DECREF in turn drops the refcount on "the | ||||||||||
next" object in the chain to 0. This can easily lead to stack overflows. | ||||||||||
To avoid that, if the C stack is nearing its limit, instead of calling | ||||||||||
dealloc on the object, it is added to a queue to be freed later when the | ||||||||||
stack is shallower */ | ||||||||||
void | ||||||||||
_Py_Dealloc(PyObject *op) | ||||||||||
{ | ||||||||||
PyTypeObject *type = Py_TYPE(op); | ||||||||||
destructor dealloc = type->tp_dealloc; | ||||||||||
#ifdef Py_DEBUG | ||||||||||
PyThreadState *tstate = _PyThreadState_GET(); | ||||||||||
intptr_t margin = _Py_RecursionLimit_GetMargin(tstate); | ||||||||||
if (margin < 2) { | ||||||||||
_PyTrash_thread_deposit_object(tstate, (PyObject *)op); | ||||||||||
return; | ||||||||||
} | ||||||||||
#ifdef Py_DEBUG | ||||||||||
#if !defined(Py_GIL_DISABLED) && !defined(Py_STACKREF_DEBUG) | ||||||||||
/* This assertion doesn't hold for the free-threading build, as | ||||||||||
* PyStackRef_CLOSE_SPECIALIZED is not implemented */ | ||||||||||
|
@@ -3046,6 +3062,9 @@ _Py_Dealloc(PyObject *op) | |||||||||
Py_XDECREF(old_exc); | ||||||||||
Py_DECREF(type); | ||||||||||
#endif | ||||||||||
if (tstate->delete_later && margin >= 4) { | ||||||||||
_PyTrash_thread_destroy_chain(tstate); | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
|
||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may rename this constant to
PYOS_LOG2_STACK_MARGIN
. It took me a while to understand that it was thelog(n)/log(2)
function :-) Or just add a a comment explaining what "LOG" stands for here.