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