Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions Lib/test/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ def f():
self.assertEqual(gc.collect(), 1)

def test_saveall(self):
self.addCleanup(gc.collect)
# Verify that cyclic garbage like lists show up in gc.garbage if the
# SAVEALL option is enabled.

Expand All @@ -352,6 +353,31 @@ def test_saveall(self):
self.assertEqual(len(gc.garbage), 1)
obj = gc.garbage.pop()
self.assertEqual(id(obj), id_L)
obj.clear()

# A no-op generator finalizer must still set the finalized marker.
gen = (item for item in ())
next(gen, None)
self.assertTrue(gc.is_tracked(gen))
self.assertIsNone(gen.gi_frame)
self.assertFalse(gc.is_finalized(gen))
L = [gen]
L.append(L)
id_L = id(L)
del gen

try:
gc.set_debug(gc.DEBUG_SAVEALL)
del L
gc.collect()
saved = next(obj for obj in gc.garbage if id(obj) == id_L)
self.assertTrue(gc.is_finalized(saved[0]))
finally:
gc.set_debug(debug)
for obj in gc.garbage:
if id(obj) == id_L:
obj.clear()
gc.garbage.clear()

def test_del(self):
# __del__ methods can trigger collection, make this to happen
Expand Down Expand Up @@ -658,6 +684,83 @@ def __del__(self):
lazarus = storage.pop()
self.assertTrue(gc.is_finalized(lazarus))

def test_weakref_callback_installs_finalizer(self):
"""A callback can make a finalizer eligible before the object is cleared."""
self.addCleanup(gc.collect)
events = []

class C:
"""A self-cycle whose weakref callback installs its finalizer."""
__slots__ = ("cycle", "__weakref__")

def finalize(obj):
"""Record whether finalization sees the intact cycle."""
events.append(("finalizer", getattr(obj, "cycle", None) is obj))

def callback(reference):
"""Install the finalizer after observing the cleared weakref."""
events.append(("callback", reference() is None))
C.__del__ = finalize

obj = C()
obj.cycle = obj
reference = weakref.ref(obj, callback)
gc.collect()
del obj

self.assertEqual(gc.collect(), 1)
self.assertEqual(events, [("callback", True), ("finalizer", True)])
self.assertIsNone(reference())

@requires_gil_enabled("tests the generation-list collector")
def test_debug_output_resurrects_without_callbacks(self):
"""Debug output can resurrect garbage without pending callbacks."""
was_enabled = gc.isenabled()
gc.disable()
self.addCleanup(gc.enable if was_enabled else gc.disable)
self.addCleanup(gc.collect)
resurrected = []

class C:
"""An object with a cycle and no finalizer."""
__slots__ = ("cycle", "__weakref__")

class Stderr:
"""Retain the target when collection reports it as collectable."""

def write(self, text):
"""Retain the target on collectable debug output.

text: Debug text written by the collector.
Returns: Number of characters accepted.
"""
if text.startswith("gc: collectable") and not resurrected:
target = reference()
if target is not None:
resurrected.append(target)
return len(text)

writer = Stderr()
gc.collect()
obj = C()
obj.cycle = obj
reference = weakref.ref(obj)
del obj

debug = gc.get_debug()
try:
with unittest.mock.patch.object(sys, "stderr", writer):
gc.set_debug(gc.DEBUG_COLLECTABLE)
self.assertEqual(gc.collect(), 0)
self.assertEqual(len(resurrected), 1)
self.assertIs(reference(), resurrected[0])
self.assertIs(resurrected[0].cycle, resurrected[0])
finally:
gc.set_debug(debug)
for obj in resurrected:
obj.cycle = None
resurrected.clear()

def test_bug1055820b(self):
# Corresponds to temp2b.py in the bug report.

Expand Down
8 changes: 7 additions & 1 deletion Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,13 @@ CONFIGURE_LDFLAGS= @LDFLAGS@
# command line to append to these values without stomping the pre-set
# values.
PY_CFLAGS= $(BASECFLAGS) $(OPT) $(CONFIGURE_CFLAGS) $(CFLAGS) $(EXTRA_CFLAGS)
PY_CFLAGS_NODIST=$(CONFIGURE_CFLAGS_NODIST) $(CFLAGS_NODIST) -I$(srcdir)/Include/internal -I$(srcdir)/Include/internal/mimalloc
# Configure validates function alignment once, but make-time CFLAGS can still
# select a size-optimized build after configure has completed.
PY_ALIGN_FUNCTIONS_CFLAGS=@FUNCTION_ALIGNMENT_CFLAGS@
ifneq ($(filter -Os -Oz,$(PY_CFLAGS) $(CONFIGURE_CFLAGS_NODIST) $(CFLAGS_NODIST)),)
PY_ALIGN_FUNCTIONS_CFLAGS=
endif
PY_CFLAGS_NODIST=$(CONFIGURE_CFLAGS_NODIST) $(CFLAGS_NODIST) $(PY_ALIGN_FUNCTIONS_CFLAGS) -I$(srcdir)/Include/internal -I$(srcdir)/Include/internal/mimalloc
# Both CPPFLAGS and LDFLAGS need to contain the shell's value for setup.py to
# be able to build extension modules using the directories specified in the
# environment variables
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Request 64-byte function alignment for performance-oriented Linux and macOS builds. This can reduce performance variation caused by changes to the size of preceding functions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Count collectable objects while clearing weak references, avoiding a separate pass during garbage collection.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
With the GIL enabled, garbage collection skips the finalization scan when no
finalizers or weakref callbacks are pending and debug output for collectable
objects is disabled.
Loading
Loading