GNOME Bugzilla – Bug 71435
cyclic garbage collection and signal handlers
Last modified: 2004-12-22 21:47:04 UTC
when you connect a signal handler, a couple of python objects are wrapped up in a GClosure. This closure is then connected as a handler for the signal. Effectively, those object references in the closure are owned by the wrapper PyObject for the GObject. We can easily get cyclic references by passing object pointers as data for the signals: a.connect('foo', b) b.connect('bar', a) Python has the facility to break these loops with its cyclic garbage collection features. It would be nice to get pygtk to hook into this. It would essentially involve keeping a list of the closures associated with the object, so we can traverse them in the tp_traverse slot, and be able to disconnect them in the tp_clear slot. Cleaning up references in the list when signals get removed could be done with a finalise notify in the closure itself.
I've been looking into this signalling stuff, since it would be very nice for Gaphor to have it working. The owner of the signal should own the closure right? IMHO a GList should be added to PyGObject, where we can add closures. pyg_closure_new() should have an extra parameter: the (to be) owner of the closure. This will allow pyg_closure_destroy() to remove itself from the owning PyGObject. Maybe even better: create an extra method pyg_object_add_closure(self, callback, args, swap_data) that can be used as a wrapper around pyg_closure_new(). Here we can add an extra finalize_notifier that removes the closure from the list. PyGObject should still have an extra attribute, though. This will avoid vague dependencies. PyGClosure will have its own traverse method. The same thing has to be done for set_(q)data()...
Created attachment 9706 [details] [review] Makes signals collectable by the Python garbage collector
About the patch: It adds a new field to PyGObject: closures. When gobject_traverse() or pygobjet_clear() or *_dealloc() is called the closure list is also visited. Closures are now made via pygobject_closure_new() which has an extra (first) argument: self, the owner of the closure. I've patched gtk.override and libglade.override too.
Created attachment 9707 [details] [review] Python app demonstrating the gcable-signals patch
Thank you for the patch. I have applied a reworked version of the patch. I added a pygobject_watch_closure() API instead of pygobject_closure_new(). Also, I moved the cleanup handler for PyGClosure into an invalidate handler rather than finalise. When watching a closure, we add an invalidate notifier that unwatches the closure. For the tp_clear() slot of the GObject wrapper, we simply invalidate all the watched closures (which unwatches it for us, and decrefs the PyObjects that the closure references). This seems to be a better way of doing things (after reading some more of the glib code). Your unit test for the signal code passes with my modifications to your patch.