@@ -429,14 +429,81 @@ PyAPI_FUNC(void) _Py_NO_RETURN _PyObject_AssertFailed(
429429 const char * function );
430430
431431
432+ /* Trashcan mechanism, thanks to Christian Tismer.
433+
434+ When deallocating a container object, it's possible to trigger an unbounded
435+ chain of deallocations, as each Py_DECREF in turn drops the refcount on "the
436+ next" object in the chain to 0. This can easily lead to stack overflows,
437+ especially in threads (which typically have less stack space to work with).
438+
439+ A container object can avoid this by bracketing the body of its tp_dealloc
440+ function with a pair of macros:
441+
442+ static void
443+ mytype_dealloc(mytype *p)
444+ {
445+ ... declarations go here ...
446+
447+ PyObject_GC_UnTrack(p); // must untrack first
448+ Py_TRASHCAN_BEGIN(p, mytype_dealloc)
449+ ... The body of the deallocator goes here, including all calls ...
450+ ... to Py_DECREF on contained objects. ...
451+ Py_TRASHCAN_END // there should be no code after this
452+ }
453+
454+ CAUTION: Never return from the middle of the body! If the body needs to
455+ "get out early", put a label immediately before the Py_TRASHCAN_END
456+ call, and goto it. Else the call-depth counter (see below) will stay
457+ above 0 forever, and the trashcan will never get emptied.
458+
459+ How it works: The BEGIN macro increments a call-depth counter. So long
460+ as this counter is small, the body of the deallocator is run directly without
461+ further ado. But if the counter gets large, it instead adds p to a list of
462+ objects to be deallocated later, skips the body of the deallocator, and
463+ resumes execution after the END macro. The tp_dealloc routine then returns
464+ without deallocating anything (and so unbounded call-stack depth is avoided).
465+
466+ When the call stack finishes unwinding again, code generated by the END macro
467+ notices this, and calls another routine to deallocate all the objects that
468+ may have been added to the list of deferred deallocations. In effect, a
469+ chain of N deallocations is broken into (N-1)/(Py_TRASHCAN_HEADROOM-1) pieces,
470+ with the call stack never exceeding a depth of Py_TRASHCAN_HEADROOM.
471+
472+ Since the tp_dealloc of a subclass typically calls the tp_dealloc of the base
473+ class, we need to ensure that the trashcan is only triggered on the tp_dealloc
474+ of the actual class being deallocated. Otherwise we might end up with a
475+ partially-deallocated object. To check this, the tp_dealloc function must be
476+ passed as second argument to Py_TRASHCAN_BEGIN().
477+ */
478+
479+
432480PyAPI_FUNC (void ) _PyTrash_thread_deposit_object (PyThreadState * tstate , PyObject * op );
433481PyAPI_FUNC (void ) _PyTrash_thread_destroy_chain (PyThreadState * tstate );
434482
483+
484+ /* Python 3.10 private API, invoked by the Py_TRASHCAN_BEGIN(). */
485+
486+ /* To avoid raising recursion errors during dealloc trigger trashcan before we reach
487+ * recursion limit. To avoid trashing, we don't attempt to empty the trashcan until
488+ * we have headroom above the trigger limit */
489+ #define Py_TRASHCAN_HEADROOM 50
490+
491+ /* Helper function for Py_TRASHCAN_BEGIN */
435492PyAPI_FUNC (int ) _Py_ReachedRecursionLimitWithMargin (PyThreadState * tstate , int margin_count );
436493
437- /* For backwards compatibility with the old trashcan mechanism */
438- #define Py_TRASHCAN_BEGIN (op , dealloc )
439- #define Py_TRASHCAN_END
494+ #define Py_TRASHCAN_BEGIN (op , dealloc ) \
495+ do { \
496+ PyThreadState *tstate = PyThreadState_Get(); \
497+ if (_Py_ReachedRecursionLimitWithMargin(tstate, 2) && Py_TYPE(op)->tp_dealloc == (destructor)dealloc) { \
498+ _PyTrash_thread_deposit_object(tstate, (PyObject *)op); \
499+ break; \
500+ }
501+ /* The body of the deallocator is here. */
502+ #define Py_TRASHCAN_END \
503+ if (tstate->delete_later && !_Py_ReachedRecursionLimitWithMargin(tstate, 4)) { \
504+ _PyTrash_thread_destroy_chain(tstate); \
505+ } \
506+ } while (0);
440507
441508
442509PyAPI_FUNC (void * ) PyObject_GetItemData (PyObject * obj );
0 commit comments