[Python-Dev] Re: Evil Trashcan and GC interaction

Neil Schemenauer nas@python.ca
Thu, 28 Mar 2002 09:53:38 -0800


Guido:
> Well, it *also* abuses the ob_refcnt field.

My patch fixes that too (by abusing the gc_prev pointer to chain trash
together).

> How about this wild idea (which Tim & I had simultaneously
> yesterday): change the trashcan code to simply leave the
> object in the GC generational list, and let the GC code
> special-case objects with zero refcnt so that they are
> eventually properly disposed?

That could probably work.  What happens when the GC is disabled?

There is insidious bug here.  Andrew helped me walk through it and I
think we figured it out.  First here's the code to trigger it:

    import gc

    class Ouch:
        def __del__(self):
            for x in range(20):
                list([])

    def f():
        gc.set_threshold(5)
        while 1:
            t = () # <-- here
            for i in range(300):
                t = [t, Ouch()]
    f()

The line marked with "here" is where things go wrong.  t used to refer
to a long chain of [t, Ouch()].  The SETLOCAL macro in ceval calls
Py_XDECREF(GETLOCAL(i)).  That starts the deallocation of the list
structure.  Ouch.__del__ gets called can creates some more objects,
triggering a collection.  The f frame's traverse gets called and tries
to follow the pointer for the t local.  It points to memory that was
freed by _PyTrash_destroy_chain.

Hmm, now that I think about it the GC is not needed to trigger the bug:

    import gc
    gc.disable()
    import sys

    class Ouch:
        def __del__(self):
            print f_frame.f_locals['t']

    def f():
        global f_frame
        f_frame = sys._getframe()
        while 1:
            t = ()
            for i in range(300):
                t = [t, Ouch()]

    f()

I haven't figured out the correct solution yet.  I'm just giving a
status update. :-)

  Neil