Skip to content

Commit 803526b

Browse files
committed
Trashcan cleanup: Now that cyclic gc is always there, the trashcan
mechanism is no longer evil: it no longer plays dangerous games with the type pointer or refcounts, and objects in extension modules can play along too without needing to edit the core first. Rewrote all the comments to explain this, and (I hope) give clear guidance to extension authors who do want to play along. Documented all the functions. Added more asserts (it may no longer be evil, but it's still dangerous <0.9 wink>). Rearranged the generated code to make it clearer, and to tolerate either the presence or absence of a semicolon after the macros. Rewrote _PyTrash_destroy_chain() to call tp_dealloc directly; it was doing a Py_DECREF again, and that has all sorts of obscure distorting effects in non-release builds (Py_DECREF was already called on the object!). Removed Christian's little "embedded change log" comments -- that's what checkin messages are for, and since it was impossible to correlate the comments with the code that changed, I found them merely distracting.
1 parent 943382c commit 803526b

File tree

3 files changed

+96
-100
lines changed

3 files changed

+96
-100
lines changed

Include/object.h

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ whose size is determined when the object is allocated.
7676
#define PyObject_VAR_HEAD \
7777
PyObject_HEAD \
7878
int ob_size; /* Number of items in variable part */
79-
79+
8080
typedef struct _object {
8181
PyObject_HEAD
8282
} PyObject;
@@ -197,7 +197,7 @@ typedef struct {
197197
getsegcountproc bf_getsegcount;
198198
getcharbufferproc bf_getcharbuffer;
199199
} PyBufferProcs;
200-
200+
201201

202202
typedef void (*freefunc)(void *);
203203
typedef void (*destructor)(PyObject *);
@@ -222,18 +222,18 @@ typedef struct _typeobject {
222222
PyObject_VAR_HEAD
223223
char *tp_name; /* For printing, in format "<module>.<name>" */
224224
int tp_basicsize, tp_itemsize; /* For allocation */
225-
225+
226226
/* Methods to implement standard operations */
227-
227+
228228
destructor tp_dealloc;
229229
printfunc tp_print;
230230
getattrfunc tp_getattr;
231231
setattrfunc tp_setattr;
232232
cmpfunc tp_compare;
233233
reprfunc tp_repr;
234-
234+
235235
/* Method suites for standard classes */
236-
236+
237237
PyNumberMethods *tp_as_number;
238238
PySequenceMethods *tp_as_sequence;
239239
PyMappingMethods *tp_as_mapping;
@@ -248,7 +248,7 @@ typedef struct _typeobject {
248248

249249
/* Functions to access object as input/output buffer */
250250
PyBufferProcs *tp_as_buffer;
251-
251+
252252
/* Flags to define presence of optional/expanded features */
253253
long tp_flags;
254254

@@ -257,7 +257,7 @@ typedef struct _typeobject {
257257
/* Assigned meaning in release 2.0 */
258258
/* call function for all accessible objects */
259259
traverseproc tp_traverse;
260-
260+
261261
/* delete references to contained objects */
262262
inquiry tp_clear;
263263

@@ -654,58 +654,61 @@ it carefully, it may save lots of calls to Py_INCREF() and Py_DECREF() at
654654
times.
655655
*/
656656

657-
/*
658-
trashcan
659-
CT 2k0130
660-
non-recursively destroy nested objects
661-
662-
CT 2k0223
663-
redefinition for better locality and less overhead.
664-
665-
Objects that want to be recursion safe need to use
666-
the macro's
667-
Py_TRASHCAN_SAFE_BEGIN(name)
668-
and
669-
Py_TRASHCAN_SAFE_END(name)
670-
surrounding their actual deallocation code.
671-
672-
It would be nice to do this using the thread state.
673-
Also, we could do an exact stack measure then.
674-
Unfortunately, deallocations also take place when
675-
the thread state is undefined.
676-
677-
CT 2k0422 complete rewrite.
678-
There is no need to allocate new objects.
679-
Everything is done vialob_refcnt and ob_type now.
680-
Adding support for free-threading should be easy, too.
681-
*/
682657

683-
#define PyTrash_UNWIND_LEVEL 50
658+
/* Trashcan mechanism, thanks to Christian Tismer.
684659
685-
#define Py_TRASHCAN_SAFE_BEGIN(op) \
686-
{ \
687-
++_PyTrash_delete_nesting; \
688-
if (_PyTrash_delete_nesting < PyTrash_UNWIND_LEVEL) { \
660+
When deallocating a container object, it's possible to trigger an unbounded
661+
chain of deallocations, as each Py_DECREF in turn drops the refcount on "the
662+
next" object in the chain to 0. This can easily lead to stack faults, and
663+
especially in threads (which typically have less stack space to work with).
689664
690-
#define Py_TRASHCAN_SAFE_END(op) \
691-
;} \
692-
else \
693-
_PyTrash_deposit_object((PyObject*)op);\
694-
--_PyTrash_delete_nesting; \
695-
if (_PyTrash_delete_later && _PyTrash_delete_nesting <= 0) \
696-
_PyTrash_destroy_chain(); \
697-
} \
665+
A container object that participates in cyclic gc can avoid this by
666+
bracketing the body of its tp_dealloc function with a pair of macros:
667+
668+
static void
669+
mytype_dealloc(mytype *p)
670+
{
671+
... declarations go here ...
672+
673+
PyObject_GC_UnTrack(p); // must untrack first
674+
Py_TRASHCAN_SAFE_BEGIN(p)
675+
... The body of the deallocator goes here, including all calls ...
676+
... to Py_DECREF on contained objects. ...
677+
Py_TRASHCAN_SAFE_END(p)
678+
}
679+
680+
How it works: The BEGIN macro increments a call-depth counter. So long
681+
as this counter is small, the body of the deallocator is run directly without
682+
further ado. But if the counter gets large, it instead adds p to a list of
683+
objects to be deallocated later, skips the body of the deallocator, and
684+
resumes execution after the END macro. The tp_dealloc routine then returns
685+
without deallocating anything (and so unbounded call-stack depth is avoided).
686+
687+
When the call stack finishes unwinding again, code generated by the END macro
688+
notices this, and calls another routine to deallocate all the objects that
689+
may have been added to the list of deferred deallocations. In effect, a
690+
chain of N deallocations is broken into N / PyTrash_UNWIND_LEVEL pieces,
691+
with the call stack never exceeding a depth of PyTrash_UNWIND_LEVEL.
692+
*/
698693

699694
extern DL_IMPORT(void) _PyTrash_deposit_object(PyObject*);
700695
extern DL_IMPORT(void) _PyTrash_destroy_chain(void);
701-
702696
extern DL_IMPORT(int) _PyTrash_delete_nesting;
703697
extern DL_IMPORT(PyObject *) _PyTrash_delete_later;
704698

705-
/* swap the "xx" to check the speed loss */
699+
#define PyTrash_UNWIND_LEVEL 50
706700

707-
#define xxPy_TRASHCAN_SAFE_BEGIN(op)
708-
#define xxPy_TRASHCAN_SAFE_END(op) ;
701+
#define Py_TRASHCAN_SAFE_BEGIN(op) \
702+
if (_PyTrash_delete_nesting < PyTrash_UNWIND_LEVEL) { \
703+
++_PyTrash_delete_nesting;
704+
/* The body of the deallocator is here. */
705+
#define Py_TRASHCAN_SAFE_END(op) \
706+
--_PyTrash_delete_nesting; \
707+
if (_PyTrash_delete_later && _PyTrash_delete_nesting <= 0) \
708+
_PyTrash_destroy_chain(); \
709+
} \
710+
else \
711+
_PyTrash_deposit_object((PyObject*)op);
709712

710713
#ifdef __cplusplus
711714
}

Modules/gcmodule.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,9 @@ _PyObject_GC_Track(PyObject *op)
963963
void
964964
PyObject_GC_UnTrack(void *op)
965965
{
966+
/* Obscure: the Py_TRASHCAN mechanism requires that we be able to
967+
* call PyObject_GC_UnTrack twice on an object.
968+
*/
966969
if (IS_TRACKED(op))
967970
_PyObject_GC_UNTRACK(op);
968971
}

Objects/object.c

Lines changed: 40 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ PyObject_Print(PyObject *op, FILE *fp, int flags)
192192
}
193193

194194
/* For debugging convenience. See Misc/gdbinit for some useful gdb hooks */
195-
void _PyObject_Dump(PyObject* op)
195+
void _PyObject_Dump(PyObject* op)
196196
{
197197
if (op == NULL)
198198
fprintf(stderr, "NULL\n");
@@ -256,7 +256,7 @@ PyObject *
256256
PyObject_Str(PyObject *v)
257257
{
258258
PyObject *res;
259-
259+
260260
if (v == NULL)
261261
return PyString_FromString("<NULL>");
262262
if (PyString_CheckExact(v)) {
@@ -295,7 +295,7 @@ PyObject *
295295
PyObject_Unicode(PyObject *v)
296296
{
297297
PyObject *res;
298-
298+
299299
if (v == NULL)
300300
res = PyString_FromString("<NULL>");
301301
if (PyUnicode_CheckExact(v)) {
@@ -699,9 +699,9 @@ get_inprogress_dict(void)
699699
if (tstate_dict == NULL) {
700700
PyErr_BadInternalCall();
701701
return NULL;
702-
}
702+
}
703703

704-
inprogress = PyDict_GetItem(tstate_dict, key);
704+
inprogress = PyDict_GetItem(tstate_dict, key);
705705
if (inprogress == NULL) {
706706
inprogress = PyDict_New();
707707
if (inprogress == NULL)
@@ -848,7 +848,7 @@ convert_3way_to_object(int op, int c)
848848
Py_INCREF(result);
849849
return result;
850850
}
851-
851+
852852
/* We want a rich comparison but don't have one. Try a 3-way cmp instead.
853853
Return
854854
NULL if error
@@ -1063,13 +1063,13 @@ _Py_HashPointer(void *p)
10631063
/* convert to a Python long and hash that */
10641064
PyObject* longobj;
10651065
long x;
1066-
1066+
10671067
if ((longobj = PyLong_FromVoidPtr(p)) == NULL) {
10681068
x = -1;
10691069
goto finally;
10701070
}
10711071
x = PyObject_Hash(longobj);
1072-
1072+
10731073
finally:
10741074
Py_XDECREF(longobj);
10751075
return x;
@@ -1195,7 +1195,7 @@ PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
11951195
if (name == NULL)
11961196
return -1;
11971197
}
1198-
else
1198+
else
11991199
#endif
12001200
{
12011201
PyErr_SetString(PyExc_TypeError,
@@ -1285,7 +1285,7 @@ PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
12851285
if (name == NULL)
12861286
return NULL;
12871287
}
1288-
else
1288+
else
12891289
#endif
12901290
{
12911291
PyErr_SetString(PyExc_TypeError,
@@ -1361,7 +1361,7 @@ PyObject_GenericSetAttr(PyObject *obj, PyObject *name, PyObject *value)
13611361
if (name == NULL)
13621362
return -1;
13631363
}
1364-
else
1364+
else
13651365
#endif
13661366
{
13671367
PyErr_SetString(PyExc_TypeError,
@@ -1450,7 +1450,7 @@ PyObject_IsTrue(PyObject *v)
14501450
return (res > 0) ? 1 : res;
14511451
}
14521452

1453-
/* equivalent of 'not v'
1453+
/* equivalent of 'not v'
14541454
Return -1 if an error occurred */
14551455

14561456
int
@@ -1758,7 +1758,7 @@ none_repr(PyObject *op)
17581758

17591759
/* ARGUSED */
17601760
static void
1761-
none_dealloc(PyObject* ignore)
1761+
none_dealloc(PyObject* ignore)
17621762
{
17631763
/* This should never get called, but we also don't want to SEGV if
17641764
* we accidently decref None out of existance.
@@ -2042,62 +2042,52 @@ Py_ReprLeave(PyObject *obj)
20422042
}
20432043
}
20442044

2045-
/*
2046-
trashcan
2047-
CT 2k0130
2048-
non-recursively destroy nested objects
2049-
2050-
CT 2k0223
2051-
everything is now done in a macro.
2052-
2053-
CT 2k0305
2054-
modified to use functions, after Tim Peter's suggestion.
2055-
2056-
CT 2k0309
2057-
modified to restore a possible error.
2058-
2059-
CT 2k0325
2060-
added better safe than sorry check for threadstate
2061-
2062-
CT 2k0422
2063-
complete rewrite. We now build a chain via ob_type
2064-
and save the limited number of types in ob_refcnt.
2065-
This is perfect since we don't need any memory.
2066-
A patch for free-threading would need just a lock.
2067-
*/
2068-
2069-
#define Py_TRASHCAN_TUPLE 1
2070-
#define Py_TRASHCAN_LIST 2
2071-
#define Py_TRASHCAN_DICT 3
2072-
#define Py_TRASHCAN_FRAME 4
2073-
#define Py_TRASHCAN_TRACEBACK 5
2074-
/* extend here if other objects want protection */
2045+
/* Trashcan support. */
20752046

2047+
/* Current call-stack depth of tp_dealloc calls. */
20762048
int _PyTrash_delete_nesting = 0;
20772049

2078-
PyObject * _PyTrash_delete_later = NULL;
2050+
/* List of objects that still need to be cleaned up, singly linked via their
2051+
* gc headers' gc_prev pointers.
2052+
*/
2053+
PyObject *_PyTrash_delete_later = NULL;
20792054

2055+
/* Add op to the _PyTrash_delete_later list. Called when the current
2056+
* call-stack depth gets large. op must be a currently untracked gc'ed
2057+
* object, with refcount 0. Py_DECREF must already have been called on it.
2058+
*/
20802059
void
20812060
_PyTrash_deposit_object(PyObject *op)
20822061
{
2083-
assert (_Py_AS_GC(op)->gc.gc_next == NULL);
2062+
assert(PyObject_IS_GC(op));
2063+
assert(_Py_AS_GC(op)->gc.gc_refs == _PyGC_REFS_UNTRACKED);
2064+
assert(op->ob_refcnt == 0);
20842065
_Py_AS_GC(op)->gc.gc_prev = (PyGC_Head *)_PyTrash_delete_later;
20852066
_PyTrash_delete_later = op;
20862067
}
20872068

2069+
/* Dealloccate all the objects in the _PyTrash_delete_later list. Called when
2070+
* the call-stack unwinds again.
2071+
*/
20882072
void
20892073
_PyTrash_destroy_chain(void)
20902074
{
20912075
while (_PyTrash_delete_later) {
2092-
PyObject *shredder = _PyTrash_delete_later;
2076+
PyObject *op = _PyTrash_delete_later;
2077+
destructor dealloc = op->ob_type->tp_dealloc;
20932078

20942079
_PyTrash_delete_later =
2095-
(PyObject*) _Py_AS_GC(shredder)->gc.gc_prev;
2096-
2097-
_Py_NewReference(shredder);
2080+
(PyObject*) _Py_AS_GC(op)->gc.gc_prev;
20982081

2082+
/* Call the deallocator directly. This used to try to
2083+
* fool Py_DECREF into calling it indirectly, but
2084+
* Py_DECREF was already called on this object, and in
2085+
* assorted non-release builds calling Py_DECREF again ends
2086+
* up distorting allocation statistics.
2087+
*/
2088+
assert(op->ob_refcnt == 0);
20992089
++_PyTrash_delete_nesting;
2100-
Py_DECREF(shredder);
2090+
(*dealloc)(op);
21012091
--_PyTrash_delete_nesting;
21022092
}
21032093
}

0 commit comments

Comments
 (0)