[New-bugs-announce] [issue40312] Weakref callbacks running before finalizers in GC collection

Allan Feldman report at bugs.python.org
Fri Apr 17 15:52:40 EDT 2020


New submission from Allan Feldman <allan.d.feldman at gmail.com>:

Our team is making use of a weakref.WeakValueDictionary() that is accessed through the finalizer of a class. We observed that in Python 3 occasionally values that are directly referenced by an object being finalized were missing from the WeakValueDictionary.


Example:

    import weakref
    cache = weakref.WeakValueDictionary()

    class Foo(object):
        pass


    class Bar(object):
        def __init__(self, foo):
            self.foo = foo
            cache['foo'] = foo

        def __del__(self):
            del cache['foo']

    bar = Bar(Foo())
    del bar


Upon further investigation, we realized that this had to do with the weakref callback within WeakValueDictionary being called (removing the key from the dict) before the finalizer for Foo was called.

Reproduction:

    import gc
    import weakref

    cache = weakref.WeakValueDictionary()


    class Foo(object):
        pass


    class Bar(object):
        def __init__(self, foo):
            # Force a reference cycle to run del only on gc.collect
            self._self = self
            self.foo = foo
            cache["foo"] = foo

        def __del__(self):
            # foo is missing from the cache because the weakref callback has
            # already run. KeyError is raised.
            del cache["foo"]


    bar = Bar(Foo())
    del bar

    gc.collect()


Expected behavior:

The weakref callback should only be called when the object is known to be deleted (after the finalizer runs). Running weakref callbacks before then means that the weakref callback can run on objects being ressurected by the finalizer.

Example:

    import gc
    import weakref


    class ForeverObject(object):
        def __init__(self, circular):
            # Introduce a circular reference so that gc must collect the object
            if circular:
                self._self = self

        def __del__(self):
            global o
            o = self


    def callback(wr):
        print("callback running", wr)


    for circular in (True, False):
        print("------- Circular reference:", circular, "-------")
        o = ForeverObject(circular)
        wr = weakref.ref(o, callback)
        del o
        gc.collect()
        print("--------------")


Note: Python 2.7 appears to have the opposite behavior - weakref callbacks are only invoked when dealloc occurs outside of gc. The Python 2.7 behavior hasn't yet been investigated.


If the expected behavior above is confirmed, I would be happy to submit a patch for this issue!

----------
components: Interpreter Core
messages: 366675
nosy: a-feld
priority: normal
severity: normal
status: open
title: Weakref callbacks running before finalizers in GC collection
type: behavior
versions: Python 2.7, Python 3.5, Python 3.6, Python 3.7, Python 3.8, Python 3.9

_______________________________________
Python tracker <report at bugs.python.org>
<https://bugs.python.org/issue40312>
_______________________________________


More information about the New-bugs-announce mailing list