Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] Allow callbacks to be registered for GVL related events #119

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
Move GVL instrumentation tests in their own files
  • Loading branch information
byroot committed Jan 26, 2022
commit cd2222b48a102c643f5b0634462a663749c6482c
50 changes: 0 additions & 50 deletions ext/-test-/gvl/call_without_gvl/call_without_gvl.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include "ruby/ruby.h"
#include "ruby/thread.h"
#include "ruby/thread_native.h"

static void*
native_sleep_callback(void *data)
@@ -69,60 +68,11 @@ thread_ubf_async_safe(VALUE thread, VALUE notify_fd)
return Qnil;
}

void
ex_callback(uint32_t e, struct gvl_hook_event_args args) {
fprintf(stderr, "calling callback\n");
}

static gvl_hook_t * single_hook = NULL;

static VALUE
thread_register_gvl_callback(VALUE thread) {
single_hook = rb_gvl_event_new(*ex_callback, RUBY_INTERNAL_EVENT_GVL_ACQUIRE_ENTER);

return Qnil;
}

static VALUE
thread_unregister_gvl_callback(VALUE thread) {
if (single_hook) {
rb_gvl_event_delete(single_hook);
single_hook = NULL;
}

return Qnil;
}

static VALUE
thread_call_gvl_callback(VALUE thread) {
rb_gvl_execute_hooks(RUBY_INTERNAL_EVENT_GVL_ACQUIRE_ENTER, 1);
return Qnil;
}

static VALUE
thread_register_and_unregister_gvl_callback(VALUE thread) {
gvl_hook_t * hooks[5];
for (int i = 0; i < 5; i++) {
hooks[i] = rb_gvl_event_new(*ex_callback, RUBY_INTERNAL_EVENT_GVL_ACQUIRE_ENTER);
}

if (!rb_gvl_event_delete(hooks[4])) return Qfalse;
if (!rb_gvl_event_delete(hooks[0])) return Qfalse;
if (!rb_gvl_event_delete(hooks[3])) return Qfalse;
if (!rb_gvl_event_delete(hooks[2])) return Qfalse;
if (!rb_gvl_event_delete(hooks[1])) return Qfalse;
return Qtrue;
}

void
Init_call_without_gvl(void)
{
VALUE mBug = rb_define_module("Bug");
VALUE klass = rb_define_module_under(mBug, "Thread");
rb_define_singleton_method(klass, "runnable_sleep", thread_runnable_sleep, 1);
rb_define_singleton_method(klass, "ubf_async_safe", thread_ubf_async_safe, 1);
rb_define_singleton_method(klass, "register_callback", thread_register_gvl_callback, 0);
rb_define_singleton_method(klass, "unregister_callback", thread_unregister_gvl_callback, 0);
rb_define_singleton_method(klass, "register_and_unregister_callbacks", thread_register_and_unregister_gvl_callback, 0);
rb_define_singleton_method(klass, "call_callbacks", thread_call_gvl_callback, 0);
}
161 changes: 161 additions & 0 deletions ext/-test-/gvl/instrumentation/instrumentation/depend
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# AUTOGENERATED DEPENDENCIES START
call_without_gvl.o: $(RUBY_EXTCONF_H)
call_without_gvl.o: $(arch_hdrdir)/ruby/config.h
call_without_gvl.o: $(hdrdir)/ruby/assert.h
call_without_gvl.o: $(hdrdir)/ruby/backward.h
call_without_gvl.o: $(hdrdir)/ruby/backward/2/assume.h
call_without_gvl.o: $(hdrdir)/ruby/backward/2/attributes.h
call_without_gvl.o: $(hdrdir)/ruby/backward/2/bool.h
call_without_gvl.o: $(hdrdir)/ruby/backward/2/inttypes.h
call_without_gvl.o: $(hdrdir)/ruby/backward/2/limits.h
call_without_gvl.o: $(hdrdir)/ruby/backward/2/long_long.h
call_without_gvl.o: $(hdrdir)/ruby/backward/2/stdalign.h
call_without_gvl.o: $(hdrdir)/ruby/backward/2/stdarg.h
call_without_gvl.o: $(hdrdir)/ruby/defines.h
call_without_gvl.o: $(hdrdir)/ruby/intern.h
call_without_gvl.o: $(hdrdir)/ruby/internal/anyargs.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic/char.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic/double.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic/int.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic/long.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic/short.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h
call_without_gvl.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h
call_without_gvl.o: $(hdrdir)/ruby/internal/assume.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/alloc_size.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/artificial.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/cold.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/const.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/constexpr.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/deprecated.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/error.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/flag_enum.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/forceinline.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/format.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/noalias.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/nodiscard.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/noexcept.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/noinline.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/nonnull.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/noreturn.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/pure.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/restrict.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/warning.h
call_without_gvl.o: $(hdrdir)/ruby/internal/attr/weakref.h
call_without_gvl.o: $(hdrdir)/ruby/internal/cast.h
call_without_gvl.o: $(hdrdir)/ruby/internal/compiler_is.h
call_without_gvl.o: $(hdrdir)/ruby/internal/compiler_is/apple.h
call_without_gvl.o: $(hdrdir)/ruby/internal/compiler_is/clang.h
call_without_gvl.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h
call_without_gvl.o: $(hdrdir)/ruby/internal/compiler_is/intel.h
call_without_gvl.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h
call_without_gvl.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h
call_without_gvl.o: $(hdrdir)/ruby/internal/compiler_since.h
call_without_gvl.o: $(hdrdir)/ruby/internal/config.h
call_without_gvl.o: $(hdrdir)/ruby/internal/constant_p.h
call_without_gvl.o: $(hdrdir)/ruby/internal/core.h
call_without_gvl.o: $(hdrdir)/ruby/internal/core/rarray.h
call_without_gvl.o: $(hdrdir)/ruby/internal/core/rbasic.h
call_without_gvl.o: $(hdrdir)/ruby/internal/core/rbignum.h
call_without_gvl.o: $(hdrdir)/ruby/internal/core/rclass.h
call_without_gvl.o: $(hdrdir)/ruby/internal/core/rdata.h
call_without_gvl.o: $(hdrdir)/ruby/internal/core/rfile.h
call_without_gvl.o: $(hdrdir)/ruby/internal/core/rhash.h
call_without_gvl.o: $(hdrdir)/ruby/internal/core/robject.h
call_without_gvl.o: $(hdrdir)/ruby/internal/core/rregexp.h
call_without_gvl.o: $(hdrdir)/ruby/internal/core/rstring.h
call_without_gvl.o: $(hdrdir)/ruby/internal/core/rstruct.h
call_without_gvl.o: $(hdrdir)/ruby/internal/core/rtypeddata.h
call_without_gvl.o: $(hdrdir)/ruby/internal/ctype.h
call_without_gvl.o: $(hdrdir)/ruby/internal/dllexport.h
call_without_gvl.o: $(hdrdir)/ruby/internal/dosish.h
call_without_gvl.o: $(hdrdir)/ruby/internal/error.h
call_without_gvl.o: $(hdrdir)/ruby/internal/eval.h
call_without_gvl.o: $(hdrdir)/ruby/internal/event.h
call_without_gvl.o: $(hdrdir)/ruby/internal/fl_type.h
call_without_gvl.o: $(hdrdir)/ruby/internal/gc.h
call_without_gvl.o: $(hdrdir)/ruby/internal/glob.h
call_without_gvl.o: $(hdrdir)/ruby/internal/globals.h
call_without_gvl.o: $(hdrdir)/ruby/internal/has/attribute.h
call_without_gvl.o: $(hdrdir)/ruby/internal/has/builtin.h
call_without_gvl.o: $(hdrdir)/ruby/internal/has/c_attribute.h
call_without_gvl.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h
call_without_gvl.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h
call_without_gvl.o: $(hdrdir)/ruby/internal/has/extension.h
call_without_gvl.o: $(hdrdir)/ruby/internal/has/feature.h
call_without_gvl.o: $(hdrdir)/ruby/internal/has/warning.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/array.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/bignum.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/class.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/compar.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/complex.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/cont.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/dir.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/enum.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/enumerator.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/error.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/eval.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/file.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/gc.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/hash.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/io.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/load.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/marshal.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/numeric.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/object.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/parse.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/proc.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/process.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/random.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/range.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/rational.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/re.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/ruby.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/select.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/select/largesize.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/signal.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/sprintf.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/string.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/struct.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/thread.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/time.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/variable.h
call_without_gvl.o: $(hdrdir)/ruby/internal/intern/vm.h
call_without_gvl.o: $(hdrdir)/ruby/internal/interpreter.h
call_without_gvl.o: $(hdrdir)/ruby/internal/iterator.h
call_without_gvl.o: $(hdrdir)/ruby/internal/memory.h
call_without_gvl.o: $(hdrdir)/ruby/internal/method.h
call_without_gvl.o: $(hdrdir)/ruby/internal/module.h
call_without_gvl.o: $(hdrdir)/ruby/internal/newobj.h
call_without_gvl.o: $(hdrdir)/ruby/internal/rgengc.h
call_without_gvl.o: $(hdrdir)/ruby/internal/scan_args.h
call_without_gvl.o: $(hdrdir)/ruby/internal/special_consts.h
call_without_gvl.o: $(hdrdir)/ruby/internal/static_assert.h
call_without_gvl.o: $(hdrdir)/ruby/internal/stdalign.h
call_without_gvl.o: $(hdrdir)/ruby/internal/stdbool.h
call_without_gvl.o: $(hdrdir)/ruby/internal/symbol.h
call_without_gvl.o: $(hdrdir)/ruby/internal/value.h
call_without_gvl.o: $(hdrdir)/ruby/internal/value_type.h
call_without_gvl.o: $(hdrdir)/ruby/internal/variable.h
call_without_gvl.o: $(hdrdir)/ruby/internal/warning_push.h
call_without_gvl.o: $(hdrdir)/ruby/internal/xmalloc.h
call_without_gvl.o: $(hdrdir)/ruby/missing.h
call_without_gvl.o: $(hdrdir)/ruby/ruby.h
call_without_gvl.o: $(hdrdir)/ruby/st.h
call_without_gvl.o: $(hdrdir)/ruby/subst.h
call_without_gvl.o: $(hdrdir)/ruby/thread.h
call_without_gvl.o: call_without_gvl.c
# AUTOGENERATED DEPENDENCIES END
2 changes: 2 additions & 0 deletions ext/-test-/gvl/instrumentation/instrumentation/extconf.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# frozen_string_literal: false
create_makefile("-test-/gvl/instrumentation")
95 changes: 95 additions & 0 deletions ext/-test-/gvl/instrumentation/instrumentation/instrumentation.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#include "ruby/ruby.h"
#include "ruby/atomic.h"
#include "ruby/thread.h"
#include "ruby/thread_native.h"

static rb_atomic_t acquire_enter_count = 0;
static rb_atomic_t acquire_exit_count = 0;
static rb_atomic_t release_count = 0;

void
ex_callback(rb_event_flag_t event, gvl_hook_event_args_t args)
{
switch(event) {
case RUBY_INTERNAL_EVENT_GVL_ACQUIRE_ENTER:
RUBY_ATOMIC_INC(acquire_enter_count);
break;
case RUBY_INTERNAL_EVENT_GVL_ACQUIRE_EXIT:
RUBY_ATOMIC_INC(acquire_exit_count);
break;
case RUBY_INTERNAL_EVENT_GVL_RELEASE:
RUBY_ATOMIC_INC(release_count);
break;
}
}

static gvl_hook_t * single_hook = NULL;

static VALUE
thread_counters(VALUE thread)
{
VALUE array = rb_ary_new2(3);
rb_ary_push(array, UINT2NUM(acquire_enter_count));
rb_ary_push(array, UINT2NUM(acquire_exit_count));
rb_ary_push(array, UINT2NUM(release_count));
return array;
}

static VALUE
thread_reset_counters(VALUE thread)
{
RUBY_ATOMIC_SET(acquire_enter_count, 0);
RUBY_ATOMIC_SET(acquire_exit_count, 0);
RUBY_ATOMIC_SET(release_count, 0);
return Qtrue;
}

static VALUE
thread_register_gvl_callback(VALUE thread)
{
single_hook = rb_gvl_event_new(
*ex_callback,
RUBY_INTERNAL_EVENT_GVL_ACQUIRE_ENTER | RUBY_INTERNAL_EVENT_GVL_ACQUIRE_EXIT | RUBY_INTERNAL_EVENT_GVL_RELEASE
);

return Qnil;
}

static VALUE
thread_unregister_gvl_callback(VALUE thread)
{
if (single_hook) {
rb_gvl_event_delete(single_hook);
single_hook = NULL;
}

return Qnil;
}

static VALUE
thread_register_and_unregister_gvl_callback(VALUE thread)
{
gvl_hook_t * hooks[5];
for (int i = 0; i < 5; i++) {
hooks[i] = rb_gvl_event_new(*ex_callback, RUBY_INTERNAL_EVENT_GVL_ACQUIRE_ENTER);
}

if (!rb_gvl_event_delete(hooks[4])) return Qfalse;
if (!rb_gvl_event_delete(hooks[0])) return Qfalse;
if (!rb_gvl_event_delete(hooks[3])) return Qfalse;
if (!rb_gvl_event_delete(hooks[2])) return Qfalse;
if (!rb_gvl_event_delete(hooks[1])) return Qfalse;
return Qtrue;
}

void
Init_instrumentation(void)
{
VALUE mBug = rb_define_module("Bug");
VALUE klass = rb_define_module_under(mBug, "GVLInstrumentation");
rb_define_singleton_method(klass, "counters", thread_counters, 0);
rb_define_singleton_method(klass, "reset_counters", thread_reset_counters, 0);
rb_define_singleton_method(klass, "register_callback", thread_register_gvl_callback, 0);
rb_define_singleton_method(klass, "unregister_callback", thread_unregister_gvl_callback, 0);
rb_define_singleton_method(klass, "register_and_unregister_callbacks", thread_register_and_unregister_gvl_callback, 0);
}
1 change: 0 additions & 1 deletion include/ruby/thread.h
Original file line number Diff line number Diff line change
@@ -190,7 +190,6 @@ void *rb_nogvl(void *(*func)(void *), void *data1,
*/
#define RUBY_CALL_WO_GVL_FLAG_SKIP_CHECK_INTS_


RBIMPL_SYMBOL_EXPORT_END()

#endif /* RUBY_THREAD_H */
24 changes: 24 additions & 0 deletions test/-ext-/gvl/test_instrumentation_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: false
class TestGVLInstrumentation < Test::Unit::TestCase
def test_gvl_instrumentation
require '-test-/gvl/instrumentation'
Bug::GVLInstrumentation.reset_counters
Bug::GVLInstrumentation::register_callback

begin
threads = 5.times.map { Thread.new { sleep 0.05; 1 + 1; sleep 0.02 } }
threads.each(&:join)
Bug::GVLInstrumentation.counters.each do |c|
assert_predicate c,:nonzero?
end
ensure
Bug::GVLInstrumentation::unregister_callback
end
end

def test_gvl_instrumentation_unregister
require '-test-/gvl/instrumentation'
assert Bug::GVLInstrumentation::register_and_unregister_callbacks
end
end

16 changes: 0 additions & 16 deletions test/-ext-/gvl/test_last_thread.rb
Original file line number Diff line number Diff line change
@@ -18,21 +18,5 @@ def test_last_thread
assert_in_delta(1.0, t, 0.16)
end;
end

def test_gvl_instrumentation
require '-test-/gvl/call_without_gvl'
Bug::Thread::register_callback

begin
Bug::Thread::call_callbacks
ensure
Bug::Thread::unregister_callback
end
end

def test_gvl_instrumentation_unregister
require '-test-/gvl/call_without_gvl'
assert Bug::Thread::register_and_unregister_callbacks
end
end