Skip to content

Commit bf384c2

Browse files
committed
Reworked move_finalizer_reachable() to create two distinct lists:
externally unreachable objects with finalizers, and externally unreachable objects without finalizers reachable from such objects. This allows us to call has_finalizer() at most once per object, and so limit the pain of nasty getattr hooks. This fixes the failing "boom 2" example Jeremy posted (a non-printing variant of which is now part of test_gc), via never triggering the nasty part of its __getattr__ method.
1 parent f6ae7a4 commit bf384c2

2 files changed

Lines changed: 89 additions & 35 deletions

File tree

Lib/test/test_gc.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,21 +253,21 @@ def test_trashcan():
253253
v = {1: v, 2: Ouch()}
254254
gc.disable()
255255

256-
class C:
256+
class Boom:
257257
def __getattr__(self, someattribute):
258258
del self.attr
259259
raise AttributeError
260260

261261
def test_boom():
262-
a = C()
263-
b = C()
262+
a = Boom()
263+
b = Boom()
264264
a.attr = b
265265
b.attr = a
266266

267267
gc.collect()
268268
garbagelen = len(gc.garbage)
269269
del a, b
270-
# a<->b are in a trash cycle now. Collection will invoke C.__getattr__
270+
# a<->b are in a trash cycle now. Collection will invoke Boom.__getattr__
271271
# (to see whether a and b have __del__ methods), and __getattr__ deletes
272272
# the internal "attr" attributes as a side effect. That causes the
273273
# trash cycle to get reclaimed via refcounts falling to 0, thus mutating
@@ -276,6 +276,33 @@ def test_boom():
276276
expect(gc.collect(), 0, "boom")
277277
expect(len(gc.garbage), garbagelen, "boom")
278278

279+
class Boom2:
280+
def __init__(self):
281+
self.x = 0
282+
283+
def __getattr__(self, someattribute):
284+
self.x += 1
285+
if self.x > 1:
286+
del self.attr
287+
raise AttributeError
288+
289+
def test_boom2():
290+
a = Boom2()
291+
b = Boom2()
292+
a.attr = b
293+
b.attr = a
294+
295+
gc.collect()
296+
garbagelen = len(gc.garbage)
297+
del a, b
298+
# Much like test_boom(), except that __getattr__ doesn't break the
299+
# cycle until the second time gc checks for __del__. As of 2.3b1,
300+
# there isn't a second time, so this simply cleans up the trash cycle.
301+
# We expect a, b, a.__dict__ and b.__dict__ (4 objects) to get reclaimed
302+
# this way.
303+
expect(gc.collect(), 4, "boom2")
304+
expect(len(gc.garbage), garbagelen, "boom2")
305+
279306
def test_all():
280307
gc.collect() # Delete 2nd generation garbage
281308
run_test("lists", test_list)
@@ -295,6 +322,7 @@ def test_all():
295322
run_test("saveall", test_saveall)
296323
run_test("trashcan", test_trashcan)
297324
run_test("boom", test_boom)
325+
run_test("boom2", test_boom2)
298326

299327
def test():
300328
if verbose:

Modules/gcmodule.c

Lines changed: 57 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,13 @@ PyGC_Head *_PyGC_generation0 = GEN_HEAD(0);
5151
static int enabled = 1; /* automatic collection enabled? */
5252

5353
/* true if we are currently running the collector */
54-
static int collecting;
54+
static int collecting = 0;
5555

5656
/* list of uncollectable objects */
57-
static PyObject *garbage;
57+
static PyObject *garbage = NULL;
5858

5959
/* Python string to use if unhandled exception occurs */
60-
static PyObject *gc_str;
60+
static PyObject *gc_str = NULL;
6161

6262
/* Python string used to look for __del__ attribute. */
6363
static PyObject *delstr = NULL;
@@ -412,19 +412,20 @@ visit_move(PyObject *op, PyGC_Head *tolist)
412412
}
413413

414414
/* Move objects that are reachable from finalizers, from the unreachable set
415-
* into the finalizers set.
415+
* into the reachable_from_finalizers set.
416416
*/
417417
static void
418-
move_finalizer_reachable(PyGC_Head *finalizers)
418+
move_finalizer_reachable(PyGC_Head *finalizers,
419+
PyGC_Head *reachable_from_finalizers)
419420
{
420421
traverseproc traverse;
421422
PyGC_Head *gc = finalizers->gc.gc_next;
422-
for (; gc != finalizers; gc=gc->gc.gc_next) {
423-
/* careful, finalizers list is growing here */
423+
for (; gc != finalizers; gc = gc->gc.gc_next) {
424+
/* Note that the finalizers list may grow during this. */
424425
traverse = FROM_GC(gc)->ob_type->tp_traverse;
425426
(void) traverse(FROM_GC(gc),
426-
(visitproc)visit_move,
427-
(void *)finalizers);
427+
(visitproc)visit_move,
428+
(void *)reachable_from_finalizers);
428429
}
429430
}
430431

@@ -454,28 +455,32 @@ debug_cycle(char *msg, PyObject *op)
454455
}
455456
}
456457

457-
/* Handle uncollectable garbage (cycles with finalizers). */
458+
/* Handle uncollectable garbage (cycles with finalizers, and stuff reachable
459+
* only from such cycles).
460+
*/
458461
static void
459-
handle_finalizers(PyGC_Head *finalizers, PyGC_Head *old)
462+
handle_finalizers(PyGC_Head *finalizers, PyGC_Head *old, int hasfinalizer)
460463
{
461464
PyGC_Head *gc;
462465
if (garbage == NULL) {
463466
garbage = PyList_New(0);
467+
if (garbage == NULL)
468+
Py_FatalError("gc couldn't create gc.garbage list");
464469
}
465-
for (gc = finalizers->gc.gc_next; gc != finalizers;
466-
gc = finalizers->gc.gc_next) {
470+
for (gc = finalizers->gc.gc_next;
471+
gc != finalizers;
472+
gc = finalizers->gc.gc_next) {
467473
PyObject *op = FROM_GC(gc);
468-
/* XXX has_finalizer() is not safe here. */
469-
if ((debug & DEBUG_SAVEALL) || has_finalizer(op)) {
474+
475+
assert(IS_REACHABLE(op));
476+
if ((debug & DEBUG_SAVEALL) || hasfinalizer) {
470477
/* If SAVEALL is not set then just append objects with
471478
* finalizers to the list of garbage. All objects in
472479
* the finalizers list are reachable from those
473480
* objects.
474481
*/
475482
PyList_Append(garbage, op);
476483
}
477-
/* object is now reachable again */
478-
assert(IS_REACHABLE(op));
479484
gc_list_remove(gc);
480485
gc_list_append(gc, old);
481486
}
@@ -527,6 +532,7 @@ collect(int generation)
527532
PyGC_Head unreachable;
528533
PyGC_Head collectable;
529534
PyGC_Head finalizers;
535+
PyGC_Head reachable_from_finalizers;
530536
PyGC_Head *gc;
531537

532538
if (delstr == NULL) {
@@ -589,16 +595,27 @@ collect(int generation)
589595
* care not to create such things. For Python, finalizers means
590596
* instance objects with __del__ methods.
591597
*
592-
* Move each object into the collectable set or the finalizers set.
593-
* Because we need to check for __del__ methods on instances of
594-
* classic classes, arbitrary Python code may get executed by
595-
* getattr hooks: that may resurrect or deallocate (via refcount
596-
* falling to 0) unreachable objects, so this is very delicate.
598+
* Move each unreachable object into the collectable set or the
599+
* finalizers set. Because we need to check for __del__ methods on
600+
* instances of classic classes, arbitrary Python code may get
601+
* executed by getattr hooks: that may resurrect or deallocate (via
602+
* refcount falling to 0) unreachable objects, so this is very
603+
* delicate.
597604
*/
598605
gc_list_init(&collectable);
599606
gc_list_init(&finalizers);
600607
move_finalizers(&unreachable, &collectable, &finalizers);
601-
move_finalizer_reachable(&finalizers);
608+
/* finalizers contains the unreachable objects with a finalizer;
609+
* unreachable objects reachable only *from* those are also
610+
* uncollectable; we move those into a separate list
611+
* (reachable_from_finalizers) so we don't have to do the dangerous
612+
* has_finalizer() test again later.
613+
*/
614+
gc_list_init(&reachable_from_finalizers);
615+
move_finalizer_reachable(&finalizers, &reachable_from_finalizers);
616+
/* And move everything only reachable from the reachable stuff. */
617+
move_finalizer_reachable(&reachable_from_finalizers,
618+
&reachable_from_finalizers);
602619

603620
/* Collect statistics on collectable objects found and print
604621
* debugging information. */
@@ -610,18 +627,25 @@ collect(int generation)
610627
}
611628
}
612629
/* Call tp_clear on objects in the collectable set. This will cause
613-
* the reference cycles to be broken. It may also cause some objects in
614-
* finalizers to be freed */
630+
* the reference cycles to be broken. It may also cause some objects
631+
* in finalizers and/or reachable_from_finalizers to be freed */
615632
delete_garbage(&collectable, old);
616633

617634
/* Collect statistics on uncollectable objects found and print
618635
* debugging information. */
619-
for (gc = finalizers.gc.gc_next; gc != &finalizers;
620-
gc = gc->gc.gc_next) {
636+
for (gc = finalizers.gc.gc_next;
637+
gc != &finalizers;
638+
gc = gc->gc.gc_next) {
621639
n++;
622-
if (debug & DEBUG_UNCOLLECTABLE) {
640+
if (debug & DEBUG_UNCOLLECTABLE)
641+
debug_cycle("uncollectable", FROM_GC(gc));
642+
}
643+
for (gc = reachable_from_finalizers.gc.gc_next;
644+
gc != &reachable_from_finalizers;
645+
gc = gc->gc.gc_next) {
646+
n++;
647+
if (debug & DEBUG_UNCOLLECTABLE)
623648
debug_cycle("uncollectable", FROM_GC(gc));
624-
}
625649
}
626650
if (debug & DEBUG_STATS) {
627651
if (m == 0 && n == 0) {
@@ -636,8 +660,10 @@ collect(int generation)
636660

637661
/* Append instances in the uncollectable set to a Python
638662
* reachable list of garbage. The programmer has to deal with
639-
* this if they insist on creating this type of structure. */
640-
handle_finalizers(&finalizers, old);
663+
* this if they insist on creating this type of structure.
664+
*/
665+
handle_finalizers(&finalizers, old, 1);
666+
handle_finalizers(&reachable_from_finalizers, old, 0);
641667

642668
if (PyErr_Occurred()) {
643669
if (gc_str == NULL) {

0 commit comments

Comments
 (0)