Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 52 additions & 34 deletions ext/ffi_c/Function.c
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,52 @@ async_cb_dispatcher_set(struct async_cb_dispatcher *ctx)
async_cb_dispatcher = ctx;
}
#endif

static void
async_cb_dispatcher_initialize(struct async_cb_dispatcher *ctx)
{
ctx->async_cb_list = NULL;

#if !defined(_WIN32)
/* n.b. we _used_ to try and destroy the mutex/cond before initializing here,
* but it's undefined what happens if you try and destory an unitialized cond.
* glibc in particular seems to wait for any concurrent waiters to finish before
* destroying a condvar, trying to destroy a condvar after fork that someone was
* waiting on pre-fork won't work. Just re-init he memory directly. */
pthread_mutex_init(&ctx->async_cb_mutex, NULL);
pthread_cond_init(&ctx->async_cb_cond, NULL);
Comment on lines +225 to +231
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: I wonder if, given the comment, it would be cleaner to use

ctx->async_cb_mutex = PTHREAD_MUTEX_INITIALIZER;
ctx->async_cb_cond = PTHREAD_COND_INITIALIZER;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess they're equivalent, yeah. I can change this if we prefer.

#else
InitializeCriticalSection(&ctx->async_cb_lock);
ctx->async_cb_cond = CreateEvent(NULL, FALSE, FALSE, NULL);
#endif
ctx->thread = rb_thread_create(async_cb_event, ctx);

/* Name thread, for better debugging */
rb_funcall(ctx->thread, rb_intern("name="), 1, rb_str_new2("FFI Callback Dispatcher"));
}

static struct async_cb_dispatcher *
async_cb_dispatcher_ensure_created(void)
{
struct async_cb_dispatcher *ctx = async_cb_dispatcher_get();
if (ctx == NULL) {
ctx = (struct async_cb_dispatcher*)ALLOC(struct async_cb_dispatcher);
async_cb_dispatcher_initialize(ctx);
async_cb_dispatcher_set(ctx);
}
return ctx;
}


static VALUE
async_cb_dispatcher_atfork_child(VALUE self)
{
struct async_cb_dispatcher *ctx = async_cb_dispatcher_get();
if (ctx) {
async_cb_dispatcher_initialize(ctx);
}
return Qnil;
}
#endif

static VALUE
Expand Down Expand Up @@ -391,15 +437,6 @@ 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_dispatcher_set(NULL);
}
#endif

static VALUE
function_init(VALUE self, VALUE rbFunctionInfo, VALUE rbProc)
{
Expand All @@ -426,31 +463,7 @@ function_init(VALUE self, VALUE rbFunctionInfo, VALUE rbProc)
}

#if defined(DEFER_ASYNC_CALLBACK)
{
struct async_cb_dispatcher *ctx = async_cb_dispatcher_get();
if (ctx == NULL) {
ctx = (struct async_cb_dispatcher*)ALLOC(struct async_cb_dispatcher);
ctx->async_cb_list = NULL;

#if !defined(_WIN32)
pthread_mutex_init(&ctx->async_cb_mutex, NULL);
pthread_cond_init(&ctx->async_cb_cond, NULL);
if( pthread_atfork(NULL, NULL, after_fork_callback) ){
rb_warn("FFI: unable to register fork callback");
}
#else
InitializeCriticalSection(&ctx->async_cb_lock);
ctx->async_cb_cond = CreateEvent(NULL, FALSE, FALSE, NULL);
#endif
ctx->thread = rb_thread_create(async_cb_event, ctx);

/* Name thread, for better debugging */
rb_funcall(ctx->thread, rb_intern("name="), 1, rb_str_new2("FFI Callback Dispatcher"));

async_cb_dispatcher_set(ctx);
}
fn->dispatcher = ctx;
}
fn->dispatcher = async_cb_dispatcher_ensure_created();
#endif

fn->closure = rbffi_Closure_Alloc(fn->info->closurePool);
Expand Down Expand Up @@ -1060,4 +1073,9 @@ rbffi_Function_Init(VALUE moduleFFI)
#if defined(DEFER_ASYNC_CALLBACK) && defined(HAVE_RB_EXT_RACTOR_SAFE)
async_cb_dispatcher_key = rb_ractor_local_storage_ptr_newkey(&async_cb_dispatcher_key_type);
#endif
#ifdef DEFER_ASYNC_CALLBACK
/* Ruby code will call this method in a Process._fork patch */
rb_define_singleton_method(moduleFFI, "_async_cb_dispatcher_atfork_child",
async_cb_dispatcher_atfork_child, 0);
#endif
}
59 changes: 59 additions & 0 deletions lib/ffi/ffi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,62 @@
require 'ffi/enum'
require 'ffi/version'
require 'ffi/function'

module FFI
module ModernForkTracking
def _fork
pid = super
if pid == 0
FFI._async_cb_dispatcher_atfork_child
end
pid
end
end

module LegacyForkTracking
module KernelExt
def fork
if block_given?
super do
FFI._async_cb_dispatcher_atfork_child
yield
end
else
pid = super
FFI._async_cb_dispatcher_atfork_child if pid.nil?
pid
end
end
end

module KernelExtPrivate
include KernelExt
private :fork
end

module IOExt
def popen(*args)
return super unless args[0] == '-'

super(*args) do |pipe|
FFI._async_cb_dispatcher_atfork_child if pipe.nil?
yield pipe
end
end
ruby2_keywords :popen if respond_to?(:ruby2_keywords)
end
end

if Process.respond_to?(:_fork)
# The nice Ruby 3.1+ way of doing things
::Process.singleton_class.prepend(ModernForkTracking)
elsif Process.respond_to?(:fork)
# Barf. Old CRuby.
# Most of the inspiration for how to do this was stolen from ActiveSupport.
::Object.prepend(LegacyForkTracking::KernelExtPrivate)
::Object.singleton_class.prepend(LegacyForkTracking::KernelExt)
::Kernel.prepend(LegacyForkTracking::KernelExtPrivate)
::Kernel.singleton_class.prepend(LegacyForkTracking::KernelExt)
::IO.singleton_class.prepend(LegacyForkTracking::IOExt)
end
end
38 changes: 38 additions & 0 deletions spec/ffi/async_callback_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ module LibTest

@blocking = true
attach_function :testAsyncCallback, [ AsyncIntCallback, :int ], :void
@blocking = true
attach_function :testAsyncCallbackDelayedRegister, [ AsyncIntCallback ], :void
@blocking = true
attach_function :testAsyncCallbackDelayedTrigger, [ :int ], :void
end

it ":int (0x7fffffff) argument" do
Expand Down Expand Up @@ -73,4 +77,38 @@ module LibTest
expect(res).to eq([0x7fffffff, true, true])
end

it "works in forks" do
skip "no forking on this ruby" unless Process.respond_to?(:fork)

v = 0
LibTest.testAsyncCallbackDelayedRegister { |i| v = i }
IO.popen('-') do |pipe|
if pipe
begin
# Parent process - read the value and assert it.
# Wait two seconds for the pipe to be readable (since the write of a four-byte int
# is < PIPE_BUF, there should be no possibility of only _some_ of the data being ready)
IO.select([pipe], [], [], 2)
data = pipe.read_nonblock(4)
expect(data).to_not be_nil
child_v = data.unpack1('l')
expect(child_v).to eq(125512)
ensure
# Make sure we don't wait forever for this test if the child process hangs.
Process.kill :KILL, pipe.pid
end
else
begin
# Child process - call the callback and write the result
LibTest.testAsyncCallbackDelayedTrigger(125512)
$stdout.write [v].pack('l')
rescue Exception => e
$stderr.puts e.inspect
ensure
# Make sure control never returns to the test runner
exit! 0
end
end
end
end
end
11 changes: 11 additions & 0 deletions spec/ffi/fixtures/FunctionTest.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,14 @@ void __stdcall testStdcallManyParams(long *a1, char a2, short int a3, int a4, __
struct StructUCDP a6, struct StructUCDP *a7, float a8, double a9) {
}
#endif

static void (*testAsyncCallbackDelayedCb)(int);
void testAsyncCallbackDelayedRegister(void (*fn)(int))
{
testAsyncCallbackDelayedCb = fn;
}

void testAsyncCallbackDelayedTrigger(int value)
{
testAsyncCallback(testAsyncCallbackDelayedCb, value);
}