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