Skip to content

Commit 466326d

Browse files
authored
bpo-38379: Don't block collection of unreachable objects when some objects resurrect (GH-16687)
Currently if any finalizer invoked during garbage collection resurrects any object, the gc gives up and aborts the collection. Although finalizers are assured to only run once per object, this behaviour of the gc can lead to an ever-increasing memory situation if new resurrecting objects are allocated in every new gc collection. To avoid this, recompute what objects among the unreachable set need to be resurrected and what objects can be safely collected. In this way, resurrecting objects will not block the collection of other objects in the unreachable set.
1 parent e3babbd commit 466326d

File tree

3 files changed

+208
-76
lines changed

3 files changed

+208
-76
lines changed

Lib/test/test_gc.py

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -822,7 +822,78 @@ def test_get_objects_arguments(self):
822822
self.assertRaises(TypeError, gc.get_objects, "1")
823823
self.assertRaises(TypeError, gc.get_objects, 1.234)
824824

825-
def test_38379(self):
825+
def test_resurrection_only_happens_once_per_object(self):
826+
class A: # simple self-loop
827+
def __init__(self):
828+
self.me = self
829+
830+
class Lazarus(A):
831+
resurrected = 0
832+
resurrected_instances = []
833+
834+
def __del__(self):
835+
Lazarus.resurrected += 1
836+
Lazarus.resurrected_instances.append(self)
837+
838+
gc.collect()
839+
gc.disable()
840+
841+
# We start with 0 resurrections
842+
laz = Lazarus()
843+
self.assertEqual(Lazarus.resurrected, 0)
844+
845+
# Deleting the instance and triggering a collection
846+
# resurrects the object
847+
del laz
848+
gc.collect()
849+
self.assertEqual(Lazarus.resurrected, 1)
850+
self.assertEqual(len(Lazarus.resurrected_instances), 1)
851+
852+
# Clearing the references and forcing a collection
853+
# should not resurrect the object again.
854+
Lazarus.resurrected_instances.clear()
855+
self.assertEqual(Lazarus.resurrected, 1)
856+
gc.collect()
857+
self.assertEqual(Lazarus.resurrected, 1)
858+
859+
gc.enable()
860+
861+
def test_resurrection_is_transitive(self):
862+
class Cargo:
863+
def __init__(self):
864+
self.me = self
865+
866+
class Lazarus:
867+
resurrected_instances = []
868+
869+
def __del__(self):
870+
Lazarus.resurrected_instances.append(self)
871+
872+
gc.collect()
873+
gc.disable()
874+
875+
laz = Lazarus()
876+
cargo = Cargo()
877+
cargo_id = id(cargo)
878+
879+
# Create a cycle between cargo and laz
880+
laz.cargo = cargo
881+
cargo.laz = laz
882+
883+
# Drop the references, force a collection and check that
884+
# everything was resurrected.
885+
del laz, cargo
886+
gc.collect()
887+
self.assertEqual(len(Lazarus.resurrected_instances), 1)
888+
instance = Lazarus.resurrected_instances.pop()
889+
self.assertTrue(hasattr(instance, "cargo"))
890+
self.assertEqual(id(instance.cargo), cargo_id)
891+
892+
gc.collect()
893+
gc.enable()
894+
895+
def test_resurrection_does_not_block_cleanup_of_other_objects(self):
896+
826897
# When a finalizer resurrects objects, stats were reporting them as
827898
# having been collected. This affected both collect()'s return
828899
# value and the dicts returned by get_stats().
@@ -861,34 +932,29 @@ def getstats():
861932
# Nothing is collected - Z() is merely resurrected.
862933
t = gc.collect()
863934
c, nc = getstats()
864-
#self.assertEqual(t, 2) # before
865-
self.assertEqual(t, 0) # after
866-
#self.assertEqual(c - oldc, 2) # before
867-
self.assertEqual(c - oldc, 0) # after
935+
self.assertEqual(t, 0)
936+
self.assertEqual(c - oldc, 0)
868937
self.assertEqual(nc - oldnc, 0)
869938

870-
# Unfortunately, a Z() prevents _anything_ from being collected.
871-
# It should be possible to collect the A instances anyway, but
872-
# that will require non-trivial code changes.
939+
# Z() should not prevent anything else from being collected.
873940
oldc, oldnc = c, nc
874941
for i in range(N):
875942
A()
876943
Z()
877-
# Z() prevents anything from being collected.
878944
t = gc.collect()
879945
c, nc = getstats()
880-
#self.assertEqual(t, 2*N + 2) # before
881-
self.assertEqual(t, 0) # after
882-
#self.assertEqual(c - oldc, 2*N + 2) # before
883-
self.assertEqual(c - oldc, 0) # after
946+
self.assertEqual(t, 2*N)
947+
self.assertEqual(c - oldc, 2*N)
884948
self.assertEqual(nc - oldnc, 0)
885949

886-
# But the A() trash is reclaimed on the next run.
950+
# The A() trash should have been reclaimed already but the
951+
# 2 copies of Z are still in zs (and the associated dicts).
887952
oldc, oldnc = c, nc
953+
zs.clear()
888954
t = gc.collect()
889955
c, nc = getstats()
890-
self.assertEqual(t, 2*N)
891-
self.assertEqual(c - oldc, 2*N)
956+
self.assertEqual(t, 4)
957+
self.assertEqual(c - oldc, 4)
892958
self.assertEqual(nc - oldnc, 0)
893959

894960
gc.enable()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
When the garbage collector makes a collection in which some objects
2+
resurrect (they are reachable from outside the isolated cycles after the
3+
finalizers have been executed), do not block the collection of all objects
4+
that are still unreachable. Patch by Pablo Galindo and Tim Peters.

Modules/gcmodule.c

Lines changed: 122 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,18 @@ gc_list_size(PyGC_Head *list)
301301
return n;
302302
}
303303

304+
/* Walk the list and mark all objects as non-collecting */
305+
static inline void
306+
gc_list_clear_collecting(PyGC_Head *collectable)
307+
{
308+
PyGC_Head *gc;
309+
for (gc = GC_NEXT(collectable); gc != collectable; gc = GC_NEXT(gc)) {
310+
gc_clear_collecting(gc);
311+
}
312+
}
313+
304314
/* Append objects in a GC list to a Python list.
305-
* Return 0 if all OK, < 0 if error (out of memory for list).
315+
* Return 0 if all OK, < 0 if error (out of memory for list)
306316
*/
307317
static int
308318
append_objects(PyObject *py_list, PyGC_Head *gc_list)
@@ -613,6 +623,22 @@ move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers)
613623
}
614624
}
615625

626+
static inline void
627+
clear_unreachable_mask(PyGC_Head *unreachable)
628+
{
629+
/* Check that the list head does not have the unreachable bit set */
630+
assert(((uintptr_t)unreachable & NEXT_MASK_UNREACHABLE) == 0);
631+
632+
PyGC_Head *gc, *next;
633+
unreachable->_gc_next &= ~NEXT_MASK_UNREACHABLE;
634+
for (gc = GC_NEXT(unreachable); gc != unreachable; gc = next) {
635+
_PyObject_ASSERT((PyObject*)FROM_GC(gc), gc->_gc_next & NEXT_MASK_UNREACHABLE);
636+
gc->_gc_next &= ~NEXT_MASK_UNREACHABLE;
637+
next = (PyGC_Head*)gc->_gc_next;
638+
}
639+
validate_list(unreachable, PREV_MASK_COLLECTING);
640+
}
641+
616642
/* A traversal callback for move_legacy_finalizer_reachable. */
617643
static int
618644
visit_move(PyObject *op, PyGC_Head *tolist)
@@ -891,36 +917,6 @@ finalize_garbage(PyGC_Head *collectable)
891917
gc_list_merge(&seen, collectable);
892918
}
893919

894-
/* Walk the collectable list and check that they are really unreachable
895-
from the outside (some objects could have been resurrected by a
896-
finalizer). */
897-
static int
898-
check_garbage(PyGC_Head *collectable)
899-
{
900-
int ret = 0;
901-
PyGC_Head *gc;
902-
for (gc = GC_NEXT(collectable); gc != collectable; gc = GC_NEXT(gc)) {
903-
// Use gc_refs and break gc_prev again.
904-
gc_set_refs(gc, Py_REFCNT(FROM_GC(gc)));
905-
_PyObject_ASSERT(FROM_GC(gc), gc_get_refs(gc) != 0);
906-
}
907-
subtract_refs(collectable);
908-
PyGC_Head *prev = collectable;
909-
for (gc = GC_NEXT(collectable); gc != collectable; gc = GC_NEXT(gc)) {
910-
_PyObject_ASSERT_WITH_MSG(FROM_GC(gc),
911-
gc_get_refs(gc) >= 0,
912-
"refcount is too small");
913-
if (gc_get_refs(gc) != 0) {
914-
ret = -1;
915-
}
916-
// Restore gc_prev here.
917-
_PyGCHead_SET_PREV(gc, prev);
918-
gc_clear_collecting(gc);
919-
prev = gc;
920-
}
921-
return ret;
922-
}
923-
924920
/* Break reference cycles by clearing the containers involved. This is
925921
* tricky business as the lists can be changing and we don't know which
926922
* objects may be freed. It is possible I screwed something up here.
@@ -958,6 +954,7 @@ delete_garbage(struct _gc_runtime_state *state,
958954
}
959955
if (GC_NEXT(collectable) == gc) {
960956
/* object is still alive, move it, it may die later */
957+
gc_clear_collecting(gc);
961958
gc_list_move(gc, old);
962959
}
963960
}
@@ -1003,6 +1000,87 @@ show_stats_each_generations(struct _gc_runtime_state *state)
10031000
buf, gc_list_size(&state->permanent_generation.head));
10041001
}
10051002

1003+
/* Deduce wich objects among "base" are unreachable from outside the list
1004+
and move them to 'unreachable'. The process consist in the following steps:
1005+
1006+
1. Copy all reference counts to a different field (gc_prev is used to hold
1007+
this copy to save memory).
1008+
2. Traverse all objects in "base" and visit all referred objects using
1009+
"tp_traverse" and for every visited object, substract 1 to the reference
1010+
count (the one that we copied in the previous step). After this step, all
1011+
objects that can be reached directly from outside must have strictly positive
1012+
reference count, while all unreachable objects must have a count of exactly 0.
1013+
3. Indentify all unreachable objects (the ones with 0 reference count) and move
1014+
them to the "unreachable" list. This step also needs to move back to "base" all
1015+
objects that were initially marked as unreachable but are referred transitively
1016+
by the reachable objects (the ones with strictly positive reference count).
1017+
1018+
Contracts:
1019+
1020+
* The "base" has to be a valid list with no mask set.
1021+
1022+
* The "unreachable" list must be uninitialized (this function calls
1023+
gc_list_init over 'unreachable').
1024+
1025+
IMPORTANT: This function leaves 'unreachable' with the NEXT_MASK_UNREACHABLE
1026+
flag set but it does not clear it to skip unnecessary iteration. Before the
1027+
flag is cleared (for example, by using 'clear_unreachable_mask' function or
1028+
by a call to 'move_legacy_finalizers'), the 'unreachable' list is not a normal
1029+
list and we can not use most gc_list_* functions for it. */
1030+
static inline void
1031+
deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
1032+
validate_list(base, 0);
1033+
/* Using ob_refcnt and gc_refs, calculate which objects in the
1034+
* container set are reachable from outside the set (i.e., have a
1035+
* refcount greater than 0 when all the references within the
1036+
* set are taken into account).
1037+
*/
1038+
update_refs(base); // gc_prev is used for gc_refs
1039+
subtract_refs(base);
1040+
1041+
/* Leave everything reachable from outside base in base, and move
1042+
* everything else (in base) to unreachable.
1043+
* NOTE: This used to move the reachable objects into a reachable
1044+
* set instead. But most things usually turn out to be reachable,
1045+
* so it's more efficient to move the unreachable things.
1046+
*/
1047+
gc_list_init(unreachable);
1048+
move_unreachable(base, unreachable); // gc_prev is pointer again
1049+
validate_list(base, 0);
1050+
}
1051+
1052+
/* Handle objects that may have resurrected after a call to 'finalize_garbage', moving
1053+
them to 'old_generation' and placing the rest on 'still_unreachable'.
1054+
1055+
Contracts:
1056+
* After this function 'unreachable' must not be used anymore and 'still_unreachable'
1057+
will contain the objects that did not resurrect.
1058+
1059+
* The "still_unreachable" list must be uninitialized (this function calls
1060+
gc_list_init over 'still_unreachable').
1061+
1062+
IMPORTANT: After a call to this function, the 'still_unreachable' set will have the
1063+
PREV_MARK_COLLECTING set, but the objects in this set are going to be removed so
1064+
we can skip the expense of clearing the flag to avoid extra iteration. */
1065+
static inline void
1066+
handle_resurrected_objects(PyGC_Head *unreachable, PyGC_Head* still_unreachable,
1067+
PyGC_Head *old_generation)
1068+
{
1069+
// Remove the PREV_MASK_COLLECTING from unreachable
1070+
// to prepare it for a new call to 'deduce_unreachable'
1071+
gc_list_clear_collecting(unreachable);
1072+
1073+
// After the call to deduce_unreachable, the 'still_unreachable' set will
1074+
// have the PREV_MARK_COLLECTING set, but the objects are going to be
1075+
// removed so we can skip the expense of clearing the flag.
1076+
PyGC_Head* resurrected = unreachable;
1077+
deduce_unreachable(resurrected, still_unreachable);
1078+
clear_unreachable_mask(still_unreachable);
1079+
1080+
// Move the resurrected objects to the old generation for future collection.
1081+
gc_list_merge(resurrected, old_generation);
1082+
}
1083+
10061084
/* This is the main function. Read this to understand how the
10071085
* collection process works. */
10081086
static Py_ssize_t
@@ -1045,26 +1123,9 @@ collect(struct _gc_runtime_state *state, int generation,
10451123
old = GEN_HEAD(state, generation+1);
10461124
else
10471125
old = young;
1048-
1049-
validate_list(young, 0);
10501126
validate_list(old, 0);
1051-
/* Using ob_refcnt and gc_refs, calculate which objects in the
1052-
* container set are reachable from outside the set (i.e., have a
1053-
* refcount greater than 0 when all the references within the
1054-
* set are taken into account).
1055-
*/
1056-
update_refs(young); // gc_prev is used for gc_refs
1057-
subtract_refs(young);
10581127

1059-
/* Leave everything reachable from outside young in young, and move
1060-
* everything else (in young) to unreachable.
1061-
* NOTE: This used to move the reachable objects into a reachable
1062-
* set instead. But most things usually turn out to be reachable,
1063-
* so it's more efficient to move the unreachable things.
1064-
*/
1065-
gc_list_init(&unreachable);
1066-
move_unreachable(young, &unreachable); // gc_prev is pointer again
1067-
validate_list(young, 0);
1128+
deduce_unreachable(young, &unreachable);
10681129

10691130
untrack_tuples(young);
10701131
/* Move reachable objects to next generation. */
@@ -1114,17 +1175,18 @@ collect(struct _gc_runtime_state *state, int generation,
11141175
/* Call tp_finalize on objects which have one. */
11151176
finalize_garbage(&unreachable);
11161177

1117-
if (check_garbage(&unreachable)) { // clear PREV_MASK_COLLECTING here
1118-
gc_list_merge(&unreachable, old);
1119-
}
1120-
else {
1121-
/* Call tp_clear on objects in the unreachable set. This will cause
1122-
* the reference cycles to be broken. It may also cause some objects
1123-
* in finalizers to be freed.
1124-
*/
1125-
m += gc_list_size(&unreachable);
1126-
delete_garbage(state, &unreachable, old);
1127-
}
1178+
/* Handle any objects that may have resurrected after the call
1179+
* to 'finalize_garbage' and continue the collection with the
1180+
* objects that are still unreachable */
1181+
PyGC_Head final_unreachable;
1182+
handle_resurrected_objects(&unreachable, &final_unreachable, old);
1183+
1184+
/* Call tp_clear on objects in the final_unreachable set. This will cause
1185+
* the reference cycles to be broken. It may also cause some objects
1186+
* in finalizers to be freed.
1187+
*/
1188+
m += gc_list_size(&final_unreachable);
1189+
delete_garbage(state, &final_unreachable, old);
11281190

11291191
/* Collect statistics on uncollectable objects found and print
11301192
* debugging information. */

0 commit comments

Comments
 (0)