Skip to content
This repository has been archived by the owner on Jul 2, 2021. It is now read-only.

What's the deal with `libuv`?

Bryan White edited this page Mar 20, 2018 · 1 revision

The problem

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.

The solution

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

Clone this wiki locally