-
Notifications
You must be signed in to change notification settings - Fork 2
What's the deal with `libuv`?
libuv
(see http://docs.libuv.org/en/v1.x/)
The C library libuv
is used by libstorj
for concurrency;
however, ruby's C API is not thread-safe so this is currently resolved by using the ruby gem libuv
in tandem with the C-libuv
calls in libstorj
Specifically, libstorj
makes calls to the C-libuv
function uv_queue_work
, which queues work to be run in another thread.
In standard C-libuv
usage, you would call uv_run(...)
:
#include <uv.h>
...
// Queue work to be run in another thread
uv_queue_work(...);
...
// Run the event loop
uv_run(...);
After calling a function which queues work using the C uv_queue_work
function, simply calling uv_run
from ruby does not work:
/* example.c */
#include <uv.h>
...
// implemented elsewhere
uv_work_t* my_work_factory(void* handle);
...
handle_wrapper
...
extern "C"
int example_queue_work(void* handle, uv_after_work_cb cb)
{
uv_loop_t* loop = uv_default_loop();
uv_work_t *work = my_work_factory(handle);
return uv_queue_work(loop, work, my_worker, cb);
}
# example.rb
# NOTE: THIS IS AN EXAMPLE OF WHAT DOES NOT WORK
####################################### === ####
require 'ffi'
module Example
extend FFI::Library
# load built shared object from C/C++ using ruby ffi
ffi_lib '/path/to/libexample.so'
# NB: if `libexample` is installed in the usual system location*
# ffi_lib 'example'
# expose C functions as members of the `Example` module
attach_function('queue_work', 'example_queue_work', [:pointer, :int], :int)
attach_function('run', 'uv_run', [:pointer, :int], :int)
end
handle = FFI::Function.new(...) {...}
cb = FFI::Function.new(...) {...}
Example.queue_work handle, cb
Example.run #=> segmentation fault
*ffi_lib
automatically adds the conventional "lib" prefix to args which are not absolute paths
It seems to be the case that calls to uv_default_loop()
made from within C-libstorj point to a different location than the default loop from ruby-livub.
It may be the case that C-libuv is being loaded twice, one linked through C-libstorj and the other through ruby-libuv bindings or something; more digging required.
Instead of calling uv_run
directly, if we let the ruby libuv
gem do it for us, we can call uv_queue_work
in C as much as we want.
Using ruby-libuv
's reactor
block to wrap calls to C functions that call uv_queue_work
, we let ruby-libuv
do the heavy concurrency lifting and C-libuv
integration:
# example.rb
require 'libuv'
require 'ffi'
module Example
extend FFI::Library
# load built shared object from C/C++ using ruby ffi
ffi_lib '/path/to/libexample.so'
# NB: if `libexample` is installed in the usual system location*
# ffi_lib 'example'
# expose C functions as members of the `Example` module
attach_function('queue_work', 'example_queue_work', [:pointer, :int], :int)
end
handle = FFI::Function.new(...) {...}
cb = FFI::Function.new(...) {...}
reactor do |reactor|
reactor.work do
Example.queue_work handle, cb
end
end
(see https://github.com/cotag/libuv)
NB: the understanding presented here comes from reading the docs and source of the various C and ruby libraries involved