Skip to content

Signal callbacks prevents GC #55

Open
nilnor opened this Issue May 16, 2013 · 3 comments

2 participants

@nilnor
nilnor commented May 16, 2013

I bumped into the problem that setting callbacks for an lgi object appears to prevent any references from within the callback to be garbage collected.

My use case is this:

I have an object o containing a lgi object. For the lgi object I provide a signal handler; a closure that references said object o. This unfortunately seems to prevent any garbage collection of o. This is a problem since o and its siblings are dynamically created objects that could be created in any number during the applications lifetime, and that also have other resource cleanup tasks associated with GC. Any thoughts regarding this?

Below you'll find a self contained stripped down example of the problem:

local lgi = require('lgi')

local function gen()
  -- local a = { ref = {} } -- this works fine
  local a = { ref = lgi.Gtk.Entry() } -- but lgi objects does not
  a.callback = function() return a end
  a.ref.on_focus_in_event = a.callback
  return a
end

local cache = setmetatable({}, {__mode = 'v'})
cache[1] = gen()
collectgarbage()
assert(cache[1] == nil, "reference was not collected")
@pavouk
Owner
pavouk commented Jun 27, 2013

Many thanks for the example. I analyzed it and thought about what to do with it for a very long time, but unfortunately I was not able to come up with any solution for it. Basically, the problem is hybrid ownership cycle, part of the objects in the cycle being Lua objects and part of them GObject. Even smaller example demonstrating the effect is something like this:

local lgi = require 'lgi'
local o = lgi.Gtk.Entry()
o.on_focus_in_event = function() return o end

o is actually Lua userdata object, keeping gobject-style reference to GtkEntry C GObject. GtkEntry has connected signal, which means that it has one reference to GClosure, which in turn contains luaL_ref to Lua closure, which references o object. We have ownership cycle which cannot be reclaimed by Lua GC, because it does not see the cycle because of gobject parts in it.

Workaround for this would be connecting such closures using connect method, remembering connection id somewhere and disconnecting the signal before leaving the object. Ugly, I know, but I really don't have any other suggestion.

Any suggestions how to solve this more elegantly are welcome.

@nilnor
nilnor commented Nov 12, 2013

Just to give a long overdue update on this from my side: I've thought about it myself given your explanation, but have failed to come up with an elegant silver bullet solution myself. I've also postponed the problem in my own application by caching the problematic instances I had, which bought me some time. I'll revisit this at a later point and see what I'll come up with, and how it could be used within LGI. My thoughts up until this point have loosely circled around some kind of central dispatch being used for all callbacks, with weak tables being used for keeping track of the callbacks themselves. Which seems to be something similar to what you thought about.

Regardless of how it's solved, I believe it should be solved in a general way within LGI, as it in my opinion makes for the equivalent of a silent memory leak.

@nilnor nilnor added a commit to howl-editor/howl that referenced this issue Jan 17, 2014
@nilnor nilnor Disable the dynamic background color override for now
Causes hard to diagnose seg faults. I believe this has something to do
with lgi and the problem with lifecycle management
(pavouk/lgi#55).

Revisit when we have the FFI glib bindings in place.
40958c8
@nilnor
nilnor commented May 12, 2014

A final update from my side: for my application I had to switch to FFI bindings anyway for Gtk et al (to use coroutines along with FFI calls triggering callbacks). I implemented a central dispatch for callbacks, offering the ability to explicitly unref callbacks which keeps only weak references in the central dispatch. Something similar could be done for LGI, but it requires jumping through a couple of hoops so it's not an easy fix by any stretch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.