Re: Making FFI callbacks call free() automatically when it's collected

  • From: "Robert G. Jakabosky" <bobby@xxxxxxxxxxxxxxx>
  • To: luajit@xxxxxxxxxxxxx
  • Date: Thu, 19 Jul 2012 17:40:14 -0700

On Thursday 19, Johnson Lin wrote:
> 2012/7/19 Mike Pall <mike-1207@xxxxxxxxxx>:
> > Johnson Lin wrote:
> >> Since anonymous FFI callbacks are anchored permanently, and explicitly
> >> call cb:free() every time is pretty troublesome too, is it considered
> >> good to do this?
> >> 
> >> local cb = ffi.gc(ffi.cast("CALLBACK", function() end), function(self)
> >> self:free() end)
> >> 
> >> cb()
> >> cb = nil
> >> collectgarbage("collect")
> >> 
> >> I did print some messages in the callback body and the finalizer body
> >> to verify it's working.
> > 
> > This works. But the time when the GC gets around to collect the
> > callback slot is not predictable. Since the slots are a scarce
> > resource, you may run out of them.
> > 
> > You need know the exact point in time when the callback is no
> > longer used, anyway. IMHO it's better to create a convenience
> > wrapper:
> > 
> > local function wrap_iter(func)
> > 
> >   local cb = ffi.cast("CALLBACK", func)
> >   iter(cb)
> >   cb:free()
> > 
> > end
> > 
> > wrap_iter(function() end)
> > 
> > --Mike
> 
> Hello, thanks for the tip,
> 
> However since my use case is actually like classic UI script where I
> register callbacks from Lua to C, I guess I'll have to do cb:free()
> manually in the UI object's finalizer..
> 
> I was hoping that by using a weak-key obj -> callbacks mapping table,
> all the callbacks related to that UI object will get destructed when
> the UI object itself get collected. But since there's a lot different
> kinds of UI events there, I'd have to write quite a few additional
> lines in the UI object's finalizer. Guess I was too lazy..

If the C callback has a related userdata value (void *) then you can create 
one FFI callback instance for each C callback prototype and pass a callback id 
as the userdata value.  The id would be used to lookup the Lua callback 
function in a table.  Since the FFI callback slots are a limited resource this 
would help decrease the number of slots needed.

local button_callbacks = {}
local function wrapped_button_cb(event, ud)
  local id = tonumber(ffi.cast("uintptr_t", ud))
  local cb = button_callbacks[id]
  if cb then return cb(event, id) end
end
local button_cb = ffi.new("ButtonCB", wrapped_button_cb)

local function wrap_button_cb(func)
  local id = #button_callbacks+1
  local ud = ffi.cast("uintptr_t", id)
  button_callbacks[id] = func
  return button_cb, ud
end


For UI callbacks it might be possible to do the callback lookup via a widget 
id or the widget's cdata value.

-- 
Robert G. Jakabosky

Other related posts: