diff --git a/CHANGELOG.md b/CHANGELOG.md index 847f946f8..237a20b25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Fixed: * Fix MSVC build +* Fix async callbacks in conjunction with fork(). #884 Added: * Allow to pass callbacks in varargs. #885 diff --git a/ext/ffi_c/Function.c b/ext/ffi_c/Function.c index 11d0a6dbc..1a575911c 100644 --- a/ext/ffi_c/Function.c +++ b/ext/ffi_c/Function.c @@ -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) { @@ -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")); diff --git a/spec/ffi/fork_spec.rb b/spec/ffi/fork_spec.rb index 635d93960..2c663ee2a 100644 --- a/spec/ffi/fork_spec.rb +++ b/spec/ffi/fork_spec.rb @@ -8,21 +8,26 @@ 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 @@ -30,10 +35,32 @@ module LibTest 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