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

Guido van Rossum guido@python.org
Thu, 28 Mar 2002 13:46:59 -0500


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

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

I think I haven't seen that patch yet.

> > 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?

The trashcan code is also disabled.  Better not create cycles *or*
deeply nested containers.  They are similar anyway. :-)

> 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.

Yes, Tim & I figured this out just before lunch, too. :-(

> 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. :-)

This is my patch:

Index: ceval.c
===================================================================
RCS file: /cvsroot/python/python/dist/src/Python/ceval.c,v
retrieving revision 2.308
diff -c -r2.308 ceval.c
*** ceval.c	24 Mar 2002 19:25:00 -0000	2.308
--- ceval.c	28 Mar 2002 18:21:09 -0000
***************
*** 554,561 ****
  /* Local variable macros */
  
  #define GETLOCAL(i)	(fastlocals[i])
! #define SETLOCAL(i, value)	do { Py_XDECREF(GETLOCAL(i)); \
! 				     GETLOCAL(i) = value; } while (0)
  
  /* Start of code */
  
--- 554,562 ----
  /* Local variable macros */
  
  #define GETLOCAL(i)	(fastlocals[i])
! #define SETLOCAL(i, value)	do { PyObject *tmp = GETLOCAL(i); \
! 				     GETLOCAL(i) = value; \
!                                      Py_XDECREF(tmp); } while (0)
  
  /* Start of code */
  

--Guido van Rossum (home page: http://www.python.org/~guido/)