diff options
-rw-r--r-- | Modules/gcmodule.c | 56 |
1 files changed, 43 insertions, 13 deletions
diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index a26adae..b3a688c 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -1,5 +1,5 @@ /* - + Reference Cycle Garbage Collection ================================== @@ -72,11 +72,19 @@ static int collecting; DEBUG_SAVEALL static int debug; -/* Special gc_refs value */ +/* When a collection begins, gc_refs is set to ob_refcnt for, and only for, + * the objects in the generation being collected, called the "young" + * generation at that point. As collection proceeds, when it's determined + * that one of these can't be collected (e.g., because it's reachable from + * outside, or has a __del__ method), the object is moved out of young, and + * gc_refs is set to a negative value. The latter is so we can distinguish + * collection candidates from non-candidates just by looking at the object. + */ +/* Special gc_refs value, although any negative value means "moved". */ #define GC_MOVED -123 -/* True if an object has been moved to the older generation */ -#define IS_MOVED(o) ((AS_GC(o))->gc.gc_refs == GC_MOVED) +/* True iff an object is still a candidate for collection. */ +#define STILL_A_CANDIDATE(o) ((AS_GC(o))->gc.gc_refs >= 0) /* list of uncollectable objects */ static PyObject *garbage; @@ -116,7 +124,7 @@ gc_list_remove(PyGC_Head *node) node->gc.gc_next = NULL; /* object is not currently tracked */ } -static void +static void gc_list_move(PyGC_Head *from, PyGC_Head *to) { if (gc_list_is_empty(from)) { @@ -161,7 +169,10 @@ gc_list_size(PyGC_Head *list) -/* Set all gc_refs = ob_refcnt */ +/* Set all gc_refs = ob_refcnt. After this, STILL_A_CANDIDATE(o) is true + * for all objects in containers, and false for all tracked gc objects not + * in containers (although see the comment in visit_decref). + */ static void update_refs(PyGC_Head *containers) { @@ -174,9 +185,21 @@ update_refs(PyGC_Head *containers) static int visit_decref(PyObject *op, void *data) { + /* There's no point to decrementing gc_refs unless + * STILL_A_CANDIDATE(op) is true. It would take extra cycles to + * check that, though. If STILL_A_CANDIDATE(op) is false, + * decrementing gc_refs almost always makes it "even more negative", + * so doesn't change that STILL_A_CANDIDATE is false, and no harm is + * done. However, it's possible that, after many collections, this + * could underflow gc_refs in a long-lived old object. In that case, + * visit_move() may move the old object back to the generation + * getting collected. That would be a waste of time, but wouldn't + * cause an error. + */ if (op && PyObject_IS_GC(op)) { - if (IS_TRACKED(op)) + if (IS_TRACKED(op)) { AS_GC(op)->gc.gc_refs--; + } } return 0; } @@ -195,7 +218,7 @@ subtract_refs(PyGC_Head *containers) } } -/* Append objects with gc_refs > 0 to roots list */ +/* Move objects with gc_refs > 0 to roots list. They can't be collected. */ static void move_roots(PyGC_Head *containers, PyGC_Head *roots) { @@ -216,7 +239,7 @@ static int visit_move(PyObject *op, PyGC_Head *tolist) { if (PyObject_IS_GC(op)) { - if (IS_TRACKED(op) && !IS_MOVED(op)) { + if (IS_TRACKED(op) && STILL_A_CANDIDATE(op)) { PyGC_Head *gc = AS_GC(op); gc_list_remove(gc); gc_list_append(gc, tolist); @@ -226,7 +249,9 @@ visit_move(PyObject *op, PyGC_Head *tolist) return 0; } -/* Move objects referenced from reachable to reachable set. */ +/* Move candidates referenced from reachable to reachable set (they're no + * longer candidates). + */ static void move_root_reachable(PyGC_Head *reachable) { @@ -242,7 +267,7 @@ move_root_reachable(PyGC_Head *reachable) } } -/* return true of object has a finalization method */ +/* return true if object has a finalization method */ static int has_finalizer(PyObject *op) { @@ -269,6 +294,7 @@ move_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers) if (has_finalizer(op)) { gc_list_remove(gc); gc_list_append(gc, finalizers); + gc->gc.gc_refs = GC_MOVED; } } } @@ -282,7 +308,7 @@ move_finalizer_reachable(PyGC_Head *finalizers) for (; gc != finalizers; gc=gc->gc.gc_next) { /* careful, finalizers list is growing here */ traverse = FROM_GC(gc)->ob_type->tp_traverse; - (void) traverse(FROM_GC(gc), + (void) traverse(FROM_GC(gc), (visitproc)visit_move, (void *)finalizers); } @@ -332,7 +358,8 @@ handle_finalizers(PyGC_Head *finalizers, PyGC_Head *old) * objects. */ PyList_Append(garbage, op); } - /* object is now reachable again */ + /* object is now reachable again */ + assert(!STILL_A_CANDIDATE(op)); gc_list_remove(gc); gc_list_append(gc, old); } @@ -349,6 +376,8 @@ delete_garbage(PyGC_Head *unreachable, PyGC_Head *old) while (!gc_list_is_empty(unreachable)) { PyGC_Head *gc = unreachable->gc.gc_next; PyObject *op = FROM_GC(gc); + + assert(STILL_A_CANDIDATE(op)); if (debug & DEBUG_SAVEALL) { PyList_Append(garbage, op); } @@ -363,6 +392,7 @@ delete_garbage(PyGC_Head *unreachable, PyGC_Head *old) /* object is still alive, move it, it may die later */ gc_list_remove(gc); gc_list_append(gc, old); + gc->gc.gc_refs = GC_MOVED; } } } |