diff --git a/Include/object.h b/Include/object.h index 7bb1eacb81f..a0997f2904e 100644 --- a/Include/object.h +++ b/Include/object.h @@ -76,7 +76,7 @@ whose size is determined when the object is allocated. #define PyObject_VAR_HEAD \ PyObject_HEAD \ int ob_size; /* Number of items in variable part */ - + typedef struct _object { PyObject_HEAD } PyObject; @@ -197,7 +197,7 @@ typedef struct { getsegcountproc bf_getsegcount; getcharbufferproc bf_getcharbuffer; } PyBufferProcs; - + typedef void (*freefunc)(void *); typedef void (*destructor)(PyObject *); @@ -222,18 +222,18 @@ typedef struct _typeobject { PyObject_VAR_HEAD char *tp_name; /* For printing, in format "." */ int tp_basicsize, tp_itemsize; /* For allocation */ - + /* Methods to implement standard operations */ - + destructor tp_dealloc; printfunc tp_print; getattrfunc tp_getattr; setattrfunc tp_setattr; cmpfunc tp_compare; reprfunc tp_repr; - + /* Method suites for standard classes */ - + PyNumberMethods *tp_as_number; PySequenceMethods *tp_as_sequence; PyMappingMethods *tp_as_mapping; @@ -248,7 +248,7 @@ typedef struct _typeobject { /* Functions to access object as input/output buffer */ PyBufferProcs *tp_as_buffer; - + /* Flags to define presence of optional/expanded features */ long tp_flags; @@ -257,7 +257,7 @@ typedef struct _typeobject { /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ traverseproc tp_traverse; - + /* delete references to contained objects */ inquiry tp_clear; @@ -654,58 +654,61 @@ it carefully, it may save lots of calls to Py_INCREF() and Py_DECREF() at times. */ -/* - trashcan - CT 2k0130 - non-recursively destroy nested objects - CT 2k0223 - redefinition for better locality and less overhead. +/* Trashcan mechanism, thanks to Christian Tismer. - Objects that want to be recursion safe need to use - the macro's - Py_TRASHCAN_SAFE_BEGIN(name) - and - Py_TRASHCAN_SAFE_END(name) - surrounding their actual deallocation code. +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 faults, and +especially in threads (which typically have less stack space to work with). - It would be nice to do this using the thread state. - Also, we could do an exact stack measure then. - Unfortunately, deallocations also take place when - the thread state is undefined. +A container object that participates in cyclic gc can avoid this by +bracketing the body of its tp_dealloc function with a pair of macros: - CT 2k0422 complete rewrite. - There is no need to allocate new objects. - Everything is done vialob_refcnt and ob_type now. - Adding support for free-threading should be easy, too. +static void +mytype_dealloc(mytype *p) +{ + ... declarations go here ... + + PyObject_GC_UnTrack(p); // must untrack first + Py_TRASHCAN_SAFE_BEGIN(p) + ... The body of the deallocator goes here, including all calls ... + ... to Py_DECREF on contained objects. ... + Py_TRASHCAN_SAFE_END(p) +} + +How it works: The BEGIN macro increments a call-depth counter. So long +as this counter is small, the body of the deallocator is run directly without +further ado. But if the counter gets large, it instead adds p to a list of +objects to be deallocated later, skips the body of the deallocator, and +resumes execution after the END macro. The tp_dealloc routine then returns +without deallocating anything (and so unbounded call-stack depth is avoided). + +When the call stack finishes unwinding again, code generated by the END macro +notices this, and calls another routine to deallocate all the objects that +may have been added to the list of deferred deallocations. In effect, a +chain of N deallocations is broken into N / PyTrash_UNWIND_LEVEL pieces, +with the call stack never exceeding a depth of PyTrash_UNWIND_LEVEL. */ +extern DL_IMPORT(void) _PyTrash_deposit_object(PyObject*); +extern DL_IMPORT(void) _PyTrash_destroy_chain(void); +extern DL_IMPORT(int) _PyTrash_delete_nesting; +extern DL_IMPORT(PyObject *) _PyTrash_delete_later; + #define PyTrash_UNWIND_LEVEL 50 #define Py_TRASHCAN_SAFE_BEGIN(op) \ - { \ - ++_PyTrash_delete_nesting; \ - if (_PyTrash_delete_nesting < PyTrash_UNWIND_LEVEL) { \ - + if (_PyTrash_delete_nesting < PyTrash_UNWIND_LEVEL) { \ + ++_PyTrash_delete_nesting; + /* The body of the deallocator is here. */ #define Py_TRASHCAN_SAFE_END(op) \ - ;} \ - else \ - _PyTrash_deposit_object((PyObject*)op);\ --_PyTrash_delete_nesting; \ if (_PyTrash_delete_later && _PyTrash_delete_nesting <= 0) \ _PyTrash_destroy_chain(); \ } \ - -extern DL_IMPORT(void) _PyTrash_deposit_object(PyObject*); -extern DL_IMPORT(void) _PyTrash_destroy_chain(void); - -extern DL_IMPORT(int) _PyTrash_delete_nesting; -extern DL_IMPORT(PyObject *) _PyTrash_delete_later; - -/* swap the "xx" to check the speed loss */ - -#define xxPy_TRASHCAN_SAFE_BEGIN(op) -#define xxPy_TRASHCAN_SAFE_END(op) ; + else \ + _PyTrash_deposit_object((PyObject*)op); #ifdef __cplusplus } diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 5685101bb6c..e0c763173c6 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -963,6 +963,9 @@ _PyObject_GC_Track(PyObject *op) void PyObject_GC_UnTrack(void *op) { + /* Obscure: the Py_TRASHCAN mechanism requires that we be able to + * call PyObject_GC_UnTrack twice on an object. + */ if (IS_TRACKED(op)) _PyObject_GC_UNTRACK(op); } diff --git a/Objects/object.c b/Objects/object.c index 8d670c9c941..6a77e882aa0 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -192,7 +192,7 @@ PyObject_Print(PyObject *op, FILE *fp, int flags) } /* For debugging convenience. See Misc/gdbinit for some useful gdb hooks */ -void _PyObject_Dump(PyObject* op) +void _PyObject_Dump(PyObject* op) { if (op == NULL) fprintf(stderr, "NULL\n"); @@ -256,7 +256,7 @@ PyObject * PyObject_Str(PyObject *v) { PyObject *res; - + if (v == NULL) return PyString_FromString(""); if (PyString_CheckExact(v)) { @@ -295,7 +295,7 @@ PyObject * PyObject_Unicode(PyObject *v) { PyObject *res; - + if (v == NULL) res = PyString_FromString(""); if (PyUnicode_CheckExact(v)) { @@ -699,9 +699,9 @@ get_inprogress_dict(void) if (tstate_dict == NULL) { PyErr_BadInternalCall(); return NULL; - } + } - inprogress = PyDict_GetItem(tstate_dict, key); + inprogress = PyDict_GetItem(tstate_dict, key); if (inprogress == NULL) { inprogress = PyDict_New(); if (inprogress == NULL) @@ -848,7 +848,7 @@ convert_3way_to_object(int op, int c) Py_INCREF(result); return result; } - + /* We want a rich comparison but don't have one. Try a 3-way cmp instead. Return NULL if error @@ -1063,13 +1063,13 @@ _Py_HashPointer(void *p) /* convert to a Python long and hash that */ PyObject* longobj; long x; - + if ((longobj = PyLong_FromVoidPtr(p)) == NULL) { x = -1; goto finally; } x = PyObject_Hash(longobj); - + finally: Py_XDECREF(longobj); return x; @@ -1195,7 +1195,7 @@ PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value) if (name == NULL) return -1; } - else + else #endif { PyErr_SetString(PyExc_TypeError, @@ -1285,7 +1285,7 @@ PyObject_GenericGetAttr(PyObject *obj, PyObject *name) if (name == NULL) return NULL; } - else + else #endif { PyErr_SetString(PyExc_TypeError, @@ -1361,7 +1361,7 @@ PyObject_GenericSetAttr(PyObject *obj, PyObject *name, PyObject *value) if (name == NULL) return -1; } - else + else #endif { PyErr_SetString(PyExc_TypeError, @@ -1450,7 +1450,7 @@ PyObject_IsTrue(PyObject *v) return (res > 0) ? 1 : res; } -/* equivalent of 'not v' +/* equivalent of 'not v' Return -1 if an error occurred */ int @@ -1758,7 +1758,7 @@ none_repr(PyObject *op) /* ARGUSED */ static void -none_dealloc(PyObject* ignore) +none_dealloc(PyObject* ignore) { /* This should never get called, but we also don't want to SEGV if * we accidently decref None out of existance. @@ -2042,62 +2042,52 @@ Py_ReprLeave(PyObject *obj) } } -/* - trashcan - CT 2k0130 - non-recursively destroy nested objects - - CT 2k0223 - everything is now done in a macro. - - CT 2k0305 - modified to use functions, after Tim Peter's suggestion. - - CT 2k0309 - modified to restore a possible error. - - CT 2k0325 - added better safe than sorry check for threadstate - - CT 2k0422 - complete rewrite. We now build a chain via ob_type - and save the limited number of types in ob_refcnt. - This is perfect since we don't need any memory. - A patch for free-threading would need just a lock. -*/ - -#define Py_TRASHCAN_TUPLE 1 -#define Py_TRASHCAN_LIST 2 -#define Py_TRASHCAN_DICT 3 -#define Py_TRASHCAN_FRAME 4 -#define Py_TRASHCAN_TRACEBACK 5 -/* extend here if other objects want protection */ +/* Trashcan support. */ +/* Current call-stack depth of tp_dealloc calls. */ int _PyTrash_delete_nesting = 0; -PyObject * _PyTrash_delete_later = NULL; +/* List of objects that still need to be cleaned up, singly linked via their + * gc headers' gc_prev pointers. + */ +PyObject *_PyTrash_delete_later = NULL; +/* Add op to the _PyTrash_delete_later list. Called when the current + * call-stack depth gets large. op must be a currently untracked gc'ed + * object, with refcount 0. Py_DECREF must already have been called on it. + */ void _PyTrash_deposit_object(PyObject *op) { - assert (_Py_AS_GC(op)->gc.gc_next == NULL); + 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 *)_PyTrash_delete_later; _PyTrash_delete_later = op; } +/* Dealloccate all the objects in the _PyTrash_delete_later list. Called when + * the call-stack unwinds again. + */ void _PyTrash_destroy_chain(void) { while (_PyTrash_delete_later) { - PyObject *shredder = _PyTrash_delete_later; + PyObject *op = _PyTrash_delete_later; + destructor dealloc = op->ob_type->tp_dealloc; _PyTrash_delete_later = - (PyObject*) _Py_AS_GC(shredder)->gc.gc_prev; - - _Py_NewReference(shredder); + (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); ++_PyTrash_delete_nesting; - Py_DECREF(shredder); + (*dealloc)(op); --_PyTrash_delete_nesting; } }