Hello developers,

I observed strange behaviour in CPython (tested in 2.7.5 and 3.3.3)
regarding object resurrection.
Yes, resurrection is evil, but it is a valid scenario. If an object is
resurrected via its finalizer __del__, sometimes its unique id value as
returned from id() changes. Additionally the list of weak references
pointing to it as returned by weakref.getweakrefs(...) breaks (i.e. is
suddenly empty, assuming it wasn't before).
These issues only arise if the resurrection occurs during
cyclic garbage collection. If the object is finalized because its refcount
drops to zero, everything is fine. See the attached test-file.

Is this behaviour intended or is it a bug? If so, which variant is the bug
and which is right? I can hardly believe that whether id() is preserved
should depend on whether the garbage was cyclic or not.

This might appear of low relevance to you, since no sane program intentionally
performs resurrection. However I originally became aware of the issue
in Jython (where it not only occurs for cyclic garbage but in every
resurrection-case), c.f. http://bugs.jython.org/issue2224.
I am interested in this because I am implementing native gc support
in JyNI and need to understand these details to do it right.


Thanks in advance!

Stefan
import unittest
import gc
import time
import weakref

class ReferentDummy:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name

class ResurrectionDummy:
    def __del__(self):
        ResurrectionDummy.resurrected = self.toResurrect

class GCDetector():
    gcIndex = 0
    
    def __del__(self):
        GCDetector.gcIndex += 1

maxGCRun = 10

def runGC():
    """
    This is needed for Jython, since theoretically Java gc is not guaranteed to
    run if gc.collect is called; the run is only attempted. This method assures
    that actually a gc run happens.
    """
    currentIndex = GCDetector.gcIndex
    gcCount = 0
    detector = GCDetector()
    detector = None
    while currentIndex == GCDetector.gcIndex and gcCount < maxGCRun:
        gc.collect()
        gcCount += 1
        time.sleep(0.1)

class GCTests(unittest.TestCase):
    def test_id_after_resurrection(self):
        l = ["ab"]
        rd = ResurrectionDummy()
        rd.toResurrect = l
        savedId = id(l)
        l = None
        rd = None
        runGC() #needed for Jython etc, even though no cyclic trash appears
        self.assertEqual(id(ResurrectionDummy.resurrected), savedId)

    def test_id_after_resurrection_cyclic(self):
        #CPython 2.7.5 fails this test
        rd = ResurrectionDummy()
        l = ["ab", rd]
        rd.toResurrect = l
        savedId = id(l)
        l = None
        rd = None
        runGC()
        self.assertEqual(id(ResurrectionDummy.resurrected), savedId)

    def test_weakref_after_resurrection(self):
        l = ReferentDummy("ab")
        rd = ResurrectionDummy()
        rd.toResurrect = l
        wref = weakref.ref(l)
        self.assertIn(wref, weakref.getweakrefs(l))
        l = None
        rd = None
        runGC() #needed for Jython etc, even though no cyclic trash appears
        self.assertIn(wref, weakref.getweakrefs(ResurrectionDummy.resurrected))

    def test_weakref_after_resurrection_cyclic(self):
        #CPython 2.7.5 fails this test
        l = ReferentDummy("ab")
        rd = ResurrectionDummy()
        rd.toResurrect = l
        l.cycleLink = rd
        wref = weakref.ref(l)
        self.assertIn(wref, weakref.getweakrefs(l))
        l = None
        rd = None
        runGC()
        self.assertIn(wref, weakref.getweakrefs(ResurrectionDummy.resurrected))


def test_main():
    unittest.main()

if __name__ == "__main__":
    test_main()
_______________________________________________
Python-Dev mailing list
Python-Dev@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com

Reply via email to