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
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);
}
28 changes: 16 additions & 12 deletions include/ruby/internal/event.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,22 @@
*
* @{
*/
#define RUBY_INTERNAL_EVENT_SWITCH 0x040000 /**< Thread switched. */
#define RUBY_EVENT_SWITCH 0x040000 /**< @old{RUBY_INTERNAL_EVENT_SWITCH} */
/* 0x080000 */
#define RUBY_INTERNAL_EVENT_NEWOBJ 0x100000 /**< Object allocated. */
#define RUBY_INTERNAL_EVENT_FREEOBJ 0x200000 /**< Object swept. */
#define RUBY_INTERNAL_EVENT_GC_START 0x400000 /**< GC started. */
#define RUBY_INTERNAL_EVENT_GC_END_MARK 0x800000 /**< GC ended mark phase. */
#define RUBY_INTERNAL_EVENT_GC_END_SWEEP 0x1000000 /**< GC ended sweep phase. */
#define RUBY_INTERNAL_EVENT_GC_ENTER 0x2000000 /**< `gc_enter()` is called. */
#define RUBY_INTERNAL_EVENT_GC_EXIT 0x4000000 /**< `gc_exit()` is called. */
#define RUBY_INTERNAL_EVENT_OBJSPACE_MASK 0x7f00000 /**< Bitmask of GC events. */
#define RUBY_INTERNAL_EVENT_MASK 0xffff0000 /**< Bitmask of internal events. */
#define RUBY_INTERNAL_EVENT_SWITCH 0x00040000 /**< Thread switched. */
#define RUBY_EVENT_SWITCH 0x00040000 /**< @old{RUBY_INTERNAL_EVENT_SWITCH} */
/*0x00080000 */
#define RUBY_INTERNAL_EVENT_NEWOBJ 0x00100000 /**< Object allocated. */
#define RUBY_INTERNAL_EVENT_FREEOBJ 0x00200000 /**< Object swept. */
#define RUBY_INTERNAL_EVENT_GC_START 0x00400000 /**< GC started. */
#define RUBY_INTERNAL_EVENT_GC_END_MARK 0x00800000 /**< GC ended mark phase. */
#define RUBY_INTERNAL_EVENT_GC_END_SWEEP 0x01000000 /**< GC ended sweep phase. */
#define RUBY_INTERNAL_EVENT_GC_ENTER 0x02000000 /**< `gc_enter()` is called. */
#define RUBY_INTERNAL_EVENT_GC_EXIT 0x04000000 /**< `gc_exit()` is called. */
#define RUBY_INTERNAL_EVENT_OBJSPACE_MASK 0x07f00000 /**< Bitmask of GC events. */
#define RUBY_INTERNAL_EVENT_GVL_ACQUIRE_ENTER 0x10000000 /** `gvl_acquire() is called */
#define RUBY_INTERNAL_EVENT_GVL_ACQUIRE_EXIT 0x20000000 /** `gvl_acquire() is exiting successfully */
#define RUBY_INTERNAL_EVENT_GVL_RELEASE 0x40000000 /** `gvl_release() is called */
#define RUBY_INTERNAL_EVENT_GVL_MASK 0x70000000 /**< Bitmask of GVL events. */
#define RUBY_INTERNAL_EVENT_MASK 0xffff0000 /**< Bitmask of internal events. */
Comment on lines +85 to +100
Copy link
Member

Choose a reason for hiding this comment

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

Maybe it's just me, but I find using hex for bit flags and bit masks is much harder to read than using left shifts for bit flags and &ing bit flags for bit masks. 😅

Copy link
Author

Choose a reason for hiding this comment

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

I don't think it's just you :) I just figured I'd match the existing pattern.


/** @} */

Expand Down
20 changes: 20 additions & 0 deletions include/ruby/thread_native.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,5 +201,25 @@ void rb_native_cond_initialize(rb_nativethread_cond_t *cond);
*/
void rb_native_cond_destroy(rb_nativethread_cond_t *cond);

typedef struct gvl_hook_event_args {
unsigned long waiting;
} gvl_hook_event_args_t;

typedef void (*rb_gvl_callback)(uint32_t event, gvl_hook_event_args_t args);

// TODO: this is going to be the same on Windows so move it somewhere sensible
typedef struct gvl_hook {
rb_gvl_callback callback;
rb_event_flag_t event;

struct gvl_hook *next;
} gvl_hook_t;

#include "ruby/internal/memory.h"
#include "ruby/atomic.h"

gvl_hook_t * rb_gvl_event_new(void *callback, rb_event_flag_t event);
bool rb_gvl_event_delete(gvl_hook_t * hook);
void rb_gvl_execute_hooks(rb_event_flag_t event, rb_atomic_t waiting);
RBIMPL_SYMBOL_EXPORT_END()
#endif
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

Loading