Re: Closure shadowing



On Thu, 2005-04-28 at 15:44 -0400, Owen Taylor wrote:
> A couple of times during discussion on gtk-devel-list in the last
> few days, I mentioned the idea of attaching closure callbacks to 
> proxy objects rather than GObjects to avoid uncollectable reference
> cycles.
> 
> Since I doubt too many people who aren't familiar with the internals
> of pygtk caught what I was talking about, I thought I'd write up
> the concept in a bit of detail.
> 
> Basic idea
> ==========
> 
> Examine the following bit of Python code:
> 
>   class ActionInfo:
>       def __init__(self, name, label, message):
> 	   self.name = name
> 	   self.label = label
> 	   self.message = message
> 
>       def create_action(self):
>            self.action = gtk.Action (name=self.name, label=self.label)
>            def onActivate(action):
> 	       print self.message
>            action.connect("activate", onActivate)
> 
> [ C# and Java analogues are at the end. All code is untested and not
>   checked against reference documentation ]
> 
> Implemented in the obvious way we get a structure that looks like
> 
>  +-----------+       +------------------+      +------------------+
>  | ActioInfo |  ---> |  PyRadioAction   | ---> |  GtkRadioAction  | 
>  | (Python   |       |  (Python Object) |      |     (GObject)    |
>  |  Object)  |       |                  |      |                  |
>  +-----------+       +------------------+      +------------------+
>       ^                                                 |
>       |                                                 v
>  +-------------+                               +----------------+
>  | OnActivate  | <---------------------------- |    GClosure    |
>  | Python      |                               |                |
>  | callback    |                               |                |
>  +-------------+                               +----------------+
> 
> We have a reference cycle that goes through both python and GObject,
> and is thus uncollectable. If we were dealing with a widget the
> signal would be removed at gtk_widget_destroy() time, but that doesn't
> help us for a GObject like GtkAction.
> 
> The technique that Python uses to fix this cycle is something I'll
> call "closure shadowing".

  "Python uses"?  You are proposing, or are you saying things are like
this already?  I don't think they are.  pygtk's solution is more like
this (jamesh please correct me if I'm wrong):


 +-----------+       +------------------+      +------------------+
 | ActioInfo |  ---> |  PyRadioAction   | ---> |  GtkRadioAction  | 
 | (Python   |       |   (PyGObject)    |      |     (GObject)    |
 |  Object)  |       |                  |      |                  |
 +-----------+       +------------------+      +------------------+
      ^                               |                 |
      |                               v                 v
 +-------------+                  +-------------------------+
 | OnActivate  | <--------------- |        PyGClosure       |
 | Python      |                  |     (is-a GClosure)     |
 | callback    |                  |                         |
 +-------------+                  +-------------------------+

  Python's GC, when looking for cycles to collect, asks (tp_traverse
slot) the PyGObject (PyRadioAction) for all python object references
that it contains.  Then it looks a a list of closures being tracked, and
answers with a list of python objects contained in the tracked closures.
Thus, the GC discovers the cycle PyRadioAction -> OnActivate ->
ActionInfo -> (back to) PyRadioAction, and frees it (but only when
GtkRadioAction refcount == 1).

>  We don't make the GClosure hold a 
> reference to the python callback directly, rather we hang the
> python callback information off of the python proxy object and
> keep a weak reference to that from the GClosure:
> 
> The modified structure looks like:
> 
>  +-----------+       +------------------+      +------------------+
>  | ActioInfo |  ---> |  PyRadioAction   | ---> |  GtkRadioAction  |
>  | (Python   |       |  (Python Object) |      |     (GObject)    |
>  |  Object)  |       |                  |      |                  |
>  +-----------+       +------------------+      +------------------+
>       ^                       | (*)                       |
>       |                       v                           v
>  +-------------+      +-----------------+       +----------------+
>  | OnActivate  |      | Proxy closure   |       |    GClosure    |
>  | Python      | <--- | (Python Object) | <.... |                | 
>  | closure     |      |                 |       |                |
>  +-------------+      +-----------------+       +----------------+
> 
> If the GClosure is freed before the underlying objects (as by
> g_signal_signal_disconnect), then we remove the reference 
> labeled (*) from an invalidate notifier we attach to the GClosure.
> 
> With this modification, the cycle lives completely in Python-land
> and is thus collectable by the Python garbage collector.

  I think it is already collectable, thus this solution, for the python
case, adds complexity with not much gain...

  ...although it would be nice to not rely on cyclic GC; it already
causes transient memory problems, that appear like leaks to newbies,
with GObjects holding large chunks of memory, such as pixbufs.

  One more thing; in pygtk, we need the python wrapper to never be
collected as long as the corresponding GObject is alive.  This is
because the GObject python wrappers has a dictionary for the programmer
to add attributes freely.  Programmers expect this dictionary to be kept
unchanged even when python-land loses track of the GObject.

  For instance, say you create a GObject in python, add some attributes
to it, then store it in a tree model.  With what you propose, at this
point the python gobject wrapper would be collected (thus destroyed).
Later on, the programmer tries to retrieve the gobject stored in the
tree model.  pygtk doesn't find the python wrapper for that gobject and
creates a new one, thus losing the custom attributes.

  IMHO, perhaps instance dictionaries should not be allowed except when
subclassing.  But I'm not sure the remaining pygtk community would feel
the same, and I don't think we're allowed to break API compatibility
like that (the wrath o murray would fall upon us :)

  Anyway, good to see language bindings given high priority for a
change.  Keep up the good work :-)

  Regards.

-- 
Gustavo J. A. M. Carneiro
<gjc inescporto pt> <gustavo users sourceforge net>
The universe is always one step beyond logic.

Attachment: smime.p7s
Description: S/MIME cryptographic signature



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]