Skip to content

Commit

Permalink
Merge pull request #888 from larskanis/async-callback-with-fork
Browse files Browse the repository at this point in the history
Fix async callbacks in conjunction with fork()
  • Loading branch information
larskanis committed Mar 5, 2021
2 parents ea13267 + df39cc6 commit 05537c7
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -3,6 +3,7 @@

Fixed:
* Fix MSVC build
* Fix async callbacks in conjunction with fork(). #884

Added:
* Allow to pass callbacks in varargs. #885
Expand Down
18 changes: 18 additions & 0 deletions ext/ffi_c/Function.c
Expand Up @@ -282,6 +282,17 @@ rbffi_Function_ForProc(VALUE rbFunctionInfo, VALUE proc)
return callback;
}

#if !defined(_WIN32) && defined(DEFER_ASYNC_CALLBACK)
static void
after_fork_callback(void)
{
/* Ensure that a new dispatcher thread is started in a forked process */
async_cb_thread = Qnil;
pthread_mutex_init(&async_cb_mutex, NULL);
pthread_cond_init(&async_cb_cond, NULL);
}
#endif

static VALUE
function_init(VALUE self, VALUE rbFunctionInfo, VALUE rbProc)
{
Expand Down Expand Up @@ -309,6 +320,13 @@ function_init(VALUE self, VALUE rbFunctionInfo, VALUE rbProc)

#if defined(DEFER_ASYNC_CALLBACK)
if (async_cb_thread == Qnil) {

#if !defined(_WIN32)
if( pthread_atfork(NULL, NULL, after_fork_callback) ){
rb_warn("FFI: unable to register fork callback");
}
#endif

async_cb_thread = rb_thread_create(async_cb_event, NULL);
/* Name thread, for better debugging */
rb_funcall(async_cb_thread, rb_intern("name="), 1, rb_str_new2("FFI Callback Dispatcher"));
Expand Down
37 changes: 32 additions & 5 deletions spec/ffi/fork_spec.rb
Expand Up @@ -8,32 +8,59 @@
if Process.respond_to?(:fork)
describe "Callback in conjunction with fork()" do

module LibTest
libtest = Module.new do
extend FFI::Library
ffi_lib TestLibrary::PATH

callback :cbVrL, [ ], :long
attach_function :testCallbackVrL, :testClosureVrL, [ :cbVrL ], :long
AsyncIntCallback = callback [ :int ], :void
attach_function :testAsyncCallback, [ AsyncIntCallback, :int ], :void, blocking: true
end

it "works with forked process and GC" do
expect(LibTest.testCallbackVrL { 12345 }).to eq(12345)
expect(libtest.testCallbackVrL { 12345 }).to eq(12345)
fork do
expect(LibTest.testCallbackVrL { 12345 }).to eq(12345)
expect(libtest.testCallbackVrL { 12345 }).to eq(12345)
Process.exit 42
end
expect(LibTest.testCallbackVrL { 12345 }).to eq(12345)
expect(libtest.testCallbackVrL { 12345 }).to eq(12345)
GC.start

expect(Process.wait2[1].exitstatus).to eq(42)
end

it "works with forked process and free()" do
cbf = FFI::Function.new(FFI::Type::LONG, []) { 234 }

fork do
cbf.free
Process.exit 43
end

expect(LibTest.testCallbackVrL(cbf)).to eq(234)
expect(libtest.testCallbackVrL(cbf)).to eq(234)
cbf.free

expect(Process.wait2[1].exitstatus).to eq(43)
end

def run_async_callback(libtest)
recv = nil
libtest.testAsyncCallback(proc { |r| recv = r }, 23)
expect(recv).to eq(23)
end

it "async thread dispatch works after forking" do
run_async_callback(libtest)

fork do
run_async_callback(libtest)
Process.exit 44
end

run_async_callback(libtest)

expect(Process.wait2[1].exitstatus).to eq(44)
end
end
end

0 comments on commit 05537c7

Please sign in to comment.