Closure shadowing



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

Other languages
===============

In C#, the example would look roughly like:

 public class ActionInfo {
     string name, label, message;
     Action action;

     public ActionInfo (string name, string label, string message) {
 	this.name = name;
 	this.label = label;
 	this.message = message;
     }
 
     public void createAction () {
         action = Action (name, label);
         action.Activate += new EventHandler (onActivate)
     }

     private void onActivate (oObject o) {
         Console.WriteLine (message)
     }
 }

In Java, something like:

 public class ActionInfo {
     String name, label, message;
     Action action;

     public ActionInfo (string name, string label, string message) {
 	this.name = name;
 	this.label = label;
 	this.message = message;
     }
 
     public void createAction () {
         action = Action (name, label);
         action.addListener (new LifeCycleListener () {
             void activate (Action action) {
	         Sys.out.println(message)
              }
         });
     }
 }

Although the exact details vary in both cases a similar loop is created.

Attachment: signature.asc
Description: This is a digitally signed message part



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