[Python-checkins] python/dist/src/Objects typeobject.c,2.202,2.203

gvanrossum@users.sourceforge.net gvanrossum@users.sourceforge.net
Wed, 05 Feb 2003 14:39:50 -0800


Update of /cvsroot/python/python/dist/src/Objects
In directory sc8-pr-cvs1:/tmp/cvs-serv22038

Modified Files:
	typeobject.c 
Log Message:
Fix for SF #668433.  I'm not explaining it here; ample comments are in
the code.


Index: typeobject.c
===================================================================
RCS file: /cvsroot/python/python/dist/src/Objects/typeobject.c,v
retrieving revision 2.202
retrieving revision 2.203
diff -C2 -d -r2.202 -r2.203
*** typeobject.c	7 Jan 2003 13:41:36 -0000	2.202
--- typeobject.c	5 Feb 2003 22:39:45 -0000	2.203
***************
*** 641,646 ****
--- 641,649 ----
  
  	/* UnTrack and re-Track around the trashcan macro, alas */
+ 	/* See explanation at end of funtion for full disclosure */
  	PyObject_GC_UnTrack(self);
+ 	++_PyTrash_delete_nesting;
  	Py_TRASHCAN_SAFE_BEGIN(self);
+ 	--_PyTrash_delete_nesting;
  	_PyObject_GC_TRACK(self); /* We'll untrack for real later */
  
***************
*** 690,694 ****
--- 693,787 ----
  
    endlabel:
+ 	++_PyTrash_delete_nesting;
  	Py_TRASHCAN_SAFE_END(self);
+ 	--_PyTrash_delete_nesting;
+ 
+ 	/* Explanation of the weirdness around the trashcan macros:
+ 
+ 	   Q. What do the trashcan macros do?
+ 
+ 	   A. Read the comment titled "Trashcan mechanism" in object.h.
+ 	      For one, this explains why there must be a call to GC-untrack
+ 	      before the trashcan begin macro.  Without understanding the
+ 	      trashcan code, the answers to the following questions don't make
+ 	      sense.
+ 
+ 	   Q. Why do we GC-untrack before the trashcan and then immediately
+ 	      GC-track again afterward?
+ 
+ 	   A. In the case that the base class is GC-aware, the base class
+ 	      probably GC-untracks the object.  If it does that using the
+ 	      UNTRACK macro, this will crash when the object is already
+ 	      untracked.  Because we don't know what the base class does, the
+ 	      only safe thing is to make sure the object is tracked when we
+ 	      call the base class dealloc.  But...  The trashcan begin macro
+ 	      requires that the object is *untracked* before it is called.  So
+ 	      the dance becomes:
+ 
+ 	         GC untrack
+ 		 trashcan begin
+ 		 GC track
+ 
+ 	   Q. Why the bizarre (net-zero) manipulation of
+ 	      _PyTrash_delete_nesting around the trashcan macros?
+ 
+ 	   A. Some base classes (e.g. list) also use the trashcan mechanism.
+ 	      The following scenario used to be possible:
+ 
+ 	      - suppose the trashcan level is one below the trashcan limit
+ 
+ 	      - subtype_dealloc() is called
+ 
+ 	      - the trashcan limit is not yet reached, so the trashcan level
+ 	        is incremented and the code between trashcan begin and end is
+ 	        executed
+ 
+ 	      - this destroys much of the object's contents, including its
+ 	        slots and __dict__
+ 
+ 	      - basedealloc() is called; this is really list_dealloc(), or
+ 	        some other type which also uses the trashcan macros
+ 
+ 	      - the trashcan limit is now reached, so the object is put on the
+ 	        trashcan's to-be-deleted-later list
+ 
+ 	      - basedealloc() returns
+ 
+ 	      - subtype_dealloc() decrefs the object's type
+ 
+ 	      - subtype_dealloc() returns
+ 
+ 	      - later, the trashcan code starts deleting the objects from its
+ 	        to-be-deleted-later list
+ 
+ 	      - subtype_dealloc() is called *AGAIN* for the same object
+ 
+ 	      - at the very least (if the destroyed slots and __dict__ don't
+ 	        cause problems) the object's type gets decref'ed a second
+ 	        time, which is *BAD*!!!
+ 
+ 	      The remedy is to make sure that if the code between trashcan
+ 	      begin and end in subtype_dealloc() is called, the code between
+ 	      trashcan begin and end in basedealloc() will also be called.
+ 	      This is done by decrementing the level after passing into the
+ 	      trashcan block, and incrementing it just before leaving the
+ 	      block.
+ 
+ 	      But now it's possible that a chain of objects consisting solely
+ 	      of objects whose deallocator is subtype_dealloc() will defeat
+ 	      the trashcan mechanism completely: the decremented level means
+ 	      that the effective level never reaches the limit.  Therefore, we
+ 	      *increment* the level *before* entering the trashcan block, and
+ 	      matchingly decrement it after leaving.  This means the trashcan
+ 	      code will trigger a little early, but that's no big deal.
+ 
+ 	   Q. Are there any live examples of code in need of all this
+ 	      complexity?
+ 
+ 	   A. Yes.  See SF bug 668433 for code that crashed (when Python was
+ 	      compiled in debug mode) before the trashcan level manipulations
+ 	      were added.  For more discussion, see SF patches 581742, 575073
+ 	      and bug 574207.
+ 	*/
  }