My packages probably aren’t the best example, as I’m doing some hacky things to push the data into ringbuffers within the callbacks. I’m actually going to be re-architecting that part pretty soon so that it’s less brittle.
Here’s the general idea though. Say you have a C library libjepsen
that lets you register a callback:
void call_me_maybe(int (*callback)(int, void*), void *context)
So it’s expecting a callback of the form:
int handle_callback(int mynumber, void *context)
We’ll assume here that it’s handing you back some kind of data in the mynumber
parameter it thinks you might be interested in, as well as whatever context
pointer you passed in when you registered the callback.
In Julia you’d use this library with something like this:
cond = AsyncCondition()
# this function CANNOT do anything that interacts with the GC
# note we're assuming that our condition handle is being passed in as context
function callback(number, context)
ccall(:uv_async_send, Cint, (Ptr{Void}, ), context)
return 0
end
c_callback = cfunction(callback, Cint, (Cint, Ptr{Void})
# register our callback with the condition handle as the
# context pointer so we can use it from within the callback
ccall((:call_me_maybe, libjepsen),
Void,
(Ptr{Void}, Ptr{Void}),
c_callback,
cond.handle)
while true
wait(cond)
println("this is crazy")
end
Note that if the callback gets called multiple times before the Julia-side scheduler gets around to waking up cond
, the while
loop will only print once. Also note that this doesn’t provide a good mechanism to pass data between thread contexts. I wrote RingBuffers.jl which lets you read and write from the other thread context, but it makes some assumptions about what’s “safe” to do from the other thread and may be a little dicey. It also needs a documentation update - LMK if you want to use it and I’ll help out.