diff --git a/ext/-test-/gvl/instrumentation/instrumentation/depend b/ext/-test-/gvl/instrumentation/instrumentation/depend new file mode 100644 index 00000000000000..33366d5bb2aace --- /dev/null +++ b/ext/-test-/gvl/instrumentation/instrumentation/depend @@ -0,0 +1,163 @@ +# AUTOGENERATED DEPENDENCIES START +instrumentation.o: $(RUBY_EXTCONF_H) +instrumentation.o: $(arch_hdrdir)/ruby/config.h +instrumentation.o: $(hdrdir)/ruby/assert.h +instrumentation.o: $(hdrdir)/ruby/atomic.h +instrumentation.o: $(hdrdir)/ruby/backward.h +instrumentation.o: $(hdrdir)/ruby/backward/2/assume.h +instrumentation.o: $(hdrdir)/ruby/backward/2/attributes.h +instrumentation.o: $(hdrdir)/ruby/backward/2/bool.h +instrumentation.o: $(hdrdir)/ruby/backward/2/inttypes.h +instrumentation.o: $(hdrdir)/ruby/backward/2/limits.h +instrumentation.o: $(hdrdir)/ruby/backward/2/long_long.h +instrumentation.o: $(hdrdir)/ruby/backward/2/stdalign.h +instrumentation.o: $(hdrdir)/ruby/backward/2/stdarg.h +instrumentation.o: $(hdrdir)/ruby/defines.h +instrumentation.o: $(hdrdir)/ruby/intern.h +instrumentation.o: $(hdrdir)/ruby/internal/anyargs.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic/char.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic/double.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic/int.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic/long.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic/short.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h +instrumentation.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h +instrumentation.o: $(hdrdir)/ruby/internal/assume.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/alloc_size.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/artificial.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/cold.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/const.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/constexpr.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/deprecated.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/error.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/flag_enum.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/forceinline.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/format.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/noalias.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/nodiscard.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/noexcept.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/noinline.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/nonnull.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/noreturn.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/pure.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/restrict.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/warning.h +instrumentation.o: $(hdrdir)/ruby/internal/attr/weakref.h +instrumentation.o: $(hdrdir)/ruby/internal/cast.h +instrumentation.o: $(hdrdir)/ruby/internal/compiler_is.h +instrumentation.o: $(hdrdir)/ruby/internal/compiler_is/apple.h +instrumentation.o: $(hdrdir)/ruby/internal/compiler_is/clang.h +instrumentation.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h +instrumentation.o: $(hdrdir)/ruby/internal/compiler_is/intel.h +instrumentation.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h +instrumentation.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h +instrumentation.o: $(hdrdir)/ruby/internal/compiler_since.h +instrumentation.o: $(hdrdir)/ruby/internal/config.h +instrumentation.o: $(hdrdir)/ruby/internal/constant_p.h +instrumentation.o: $(hdrdir)/ruby/internal/core.h +instrumentation.o: $(hdrdir)/ruby/internal/core/rarray.h +instrumentation.o: $(hdrdir)/ruby/internal/core/rbasic.h +instrumentation.o: $(hdrdir)/ruby/internal/core/rbignum.h +instrumentation.o: $(hdrdir)/ruby/internal/core/rclass.h +instrumentation.o: $(hdrdir)/ruby/internal/core/rdata.h +instrumentation.o: $(hdrdir)/ruby/internal/core/rfile.h +instrumentation.o: $(hdrdir)/ruby/internal/core/rhash.h +instrumentation.o: $(hdrdir)/ruby/internal/core/robject.h +instrumentation.o: $(hdrdir)/ruby/internal/core/rregexp.h +instrumentation.o: $(hdrdir)/ruby/internal/core/rstring.h +instrumentation.o: $(hdrdir)/ruby/internal/core/rstruct.h +instrumentation.o: $(hdrdir)/ruby/internal/core/rtypeddata.h +instrumentation.o: $(hdrdir)/ruby/internal/ctype.h +instrumentation.o: $(hdrdir)/ruby/internal/dllexport.h +instrumentation.o: $(hdrdir)/ruby/internal/dosish.h +instrumentation.o: $(hdrdir)/ruby/internal/error.h +instrumentation.o: $(hdrdir)/ruby/internal/eval.h +instrumentation.o: $(hdrdir)/ruby/internal/event.h +instrumentation.o: $(hdrdir)/ruby/internal/fl_type.h +instrumentation.o: $(hdrdir)/ruby/internal/gc.h +instrumentation.o: $(hdrdir)/ruby/internal/glob.h +instrumentation.o: $(hdrdir)/ruby/internal/globals.h +instrumentation.o: $(hdrdir)/ruby/internal/has/attribute.h +instrumentation.o: $(hdrdir)/ruby/internal/has/builtin.h +instrumentation.o: $(hdrdir)/ruby/internal/has/c_attribute.h +instrumentation.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h +instrumentation.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h +instrumentation.o: $(hdrdir)/ruby/internal/has/extension.h +instrumentation.o: $(hdrdir)/ruby/internal/has/feature.h +instrumentation.o: $(hdrdir)/ruby/internal/has/warning.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/array.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/bignum.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/class.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/compar.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/complex.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/cont.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/dir.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/enum.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/enumerator.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/error.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/eval.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/file.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/gc.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/hash.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/io.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/load.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/marshal.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/numeric.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/object.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/parse.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/proc.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/process.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/random.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/range.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/rational.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/re.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/ruby.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/select.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/signal.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/sprintf.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/string.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/struct.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/thread.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/time.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/variable.h +instrumentation.o: $(hdrdir)/ruby/internal/intern/vm.h +instrumentation.o: $(hdrdir)/ruby/internal/interpreter.h +instrumentation.o: $(hdrdir)/ruby/internal/iterator.h +instrumentation.o: $(hdrdir)/ruby/internal/memory.h +instrumentation.o: $(hdrdir)/ruby/internal/method.h +instrumentation.o: $(hdrdir)/ruby/internal/module.h +instrumentation.o: $(hdrdir)/ruby/internal/newobj.h +instrumentation.o: $(hdrdir)/ruby/internal/rgengc.h +instrumentation.o: $(hdrdir)/ruby/internal/scan_args.h +instrumentation.o: $(hdrdir)/ruby/internal/special_consts.h +instrumentation.o: $(hdrdir)/ruby/internal/static_assert.h +instrumentation.o: $(hdrdir)/ruby/internal/stdalign.h +instrumentation.o: $(hdrdir)/ruby/internal/stdbool.h +instrumentation.o: $(hdrdir)/ruby/internal/symbol.h +instrumentation.o: $(hdrdir)/ruby/internal/value.h +instrumentation.o: $(hdrdir)/ruby/internal/value_type.h +instrumentation.o: $(hdrdir)/ruby/internal/variable.h +instrumentation.o: $(hdrdir)/ruby/internal/warning_push.h +instrumentation.o: $(hdrdir)/ruby/internal/xmalloc.h +instrumentation.o: $(hdrdir)/ruby/missing.h +instrumentation.o: $(hdrdir)/ruby/ruby.h +instrumentation.o: $(hdrdir)/ruby/st.h +instrumentation.o: $(hdrdir)/ruby/subst.h +instrumentation.o: $(hdrdir)/ruby/thread.h +instrumentation.o: $(hdrdir)/ruby/thread_native.h +instrumentation.o: instrumentation.c +# AUTOGENERATED DEPENDENCIES END diff --git a/ext/-test-/gvl/instrumentation/instrumentation/extconf.rb b/ext/-test-/gvl/instrumentation/instrumentation/extconf.rb new file mode 100644 index 00000000000000..81845048b23816 --- /dev/null +++ b/ext/-test-/gvl/instrumentation/instrumentation/extconf.rb @@ -0,0 +1,2 @@ +# frozen_string_literal: false +create_makefile("-test-/gvl/instrumentation") diff --git a/ext/-test-/gvl/instrumentation/instrumentation/instrumentation.c b/ext/-test-/gvl/instrumentation/instrumentation/instrumentation.c new file mode 100644 index 00000000000000..a7c72cc2371604 --- /dev/null +++ b/ext/-test-/gvl/instrumentation/instrumentation/instrumentation.c @@ -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); +} diff --git a/include/ruby/internal/event.h b/include/ruby/internal/event.h index 04b137a1939e85..633d17fa9e0541 100644 --- a/include/ruby/internal/event.h +++ b/include/ruby/internal/event.h @@ -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. */ /** @} */ diff --git a/include/ruby/thread_native.h b/include/ruby/thread_native.h index c23b15e133a81f..7c4b24273fc685 100644 --- a/include/ruby/thread_native.h +++ b/include/ruby/thread_native.h @@ -201,5 +201,20 @@ 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); + +typedef struct gvl_hook { + rb_gvl_callback callback; + rb_event_flag_t event; + + struct gvl_hook *next; +} gvl_hook_t; + +gvl_hook_t * rb_gvl_event_new(rb_gvl_callback callback, rb_event_flag_t event); +bool rb_gvl_event_delete(gvl_hook_t * hook); RBIMPL_SYMBOL_EXPORT_END() #endif diff --git a/test/-ext-/gvl/test_instrumentation_api.rb b/test/-ext-/gvl/test_instrumentation_api.rb new file mode 100644 index 00000000000000..e93df2becfbbee --- /dev/null +++ b/test/-ext-/gvl/test_instrumentation_api.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: false +class TestGVLInstrumentation < Test::Unit::TestCase + def setup + pend("TODO: No windows support yet") if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM + end + + 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_fork_safe + skip "No fork()" unless Process.respond_to?(:fork) + + require '-test-/gvl/instrumentation' + Bug::GVLInstrumentation::register_callback + + begin + pid = fork do + Bug::GVLInstrumentation.reset_counters + 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 + end + _, status = Process.wait2(pid) + assert_predicate status, :success? + 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 + diff --git a/thread_pthread.c b/thread_pthread.c index 55289de73a3be4..2e581a10a81c28 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -101,6 +101,83 @@ # endif #endif +static gvl_hook_t * rb_gvl_hooks = NULL; +static pthread_rwlock_t rb_gvl_hooks_rw_lock = PTHREAD_RWLOCK_INITIALIZER; + +gvl_hook_t * +rb_gvl_event_new(rb_gvl_callback callback, rb_event_flag_t event) { + gvl_hook_t *hook = ALLOC_N(gvl_hook_t, 1); + hook->callback = callback; + hook->event = event; + + int r; + if ((r = pthread_rwlock_wrlock(&rb_gvl_hooks_rw_lock))) { + rb_bug_errno("pthread_rwlock_wrlock", r); + } + + hook->next = rb_gvl_hooks; + ATOMIC_PTR_EXCHANGE(rb_gvl_hooks, hook); + + if ((r = pthread_rwlock_unlock(&rb_gvl_hooks_rw_lock))) { + rb_bug_errno("pthread_rwlock_unlock", r); + } + return hook; +} + +bool +rb_gvl_event_delete(gvl_hook_t * hook) { + int r; + if ((r = pthread_rwlock_wrlock(&rb_gvl_hooks_rw_lock))) { + rb_bug_errno("pthread_rwlock_wrlock", r); + } + + bool success = FALSE; + + if (rb_gvl_hooks == hook) { + ATOMIC_PTR_EXCHANGE(rb_gvl_hooks, hook->next); + success = TRUE; + } else { + gvl_hook_t *h = rb_gvl_hooks; + + do { + if (h->next == hook) { + h->next = hook->next; + success = TRUE; + } + } while ((h = h->next)); + } + + if ((r = pthread_rwlock_unlock(&rb_gvl_hooks_rw_lock))) { + rb_bug_errno("pthread_rwlock_unlock", r); + } + + if (success) { + ruby_xfree(hook); + } + return success; +} + +static void +rb_gvl_execute_hooks(rb_event_flag_t event, rb_atomic_t waiting) { + int r; + if ((r = pthread_rwlock_rdlock(&rb_gvl_hooks_rw_lock))) { + rb_bug_errno("pthread_rwlock_rdlock", r); + } + + if (rb_gvl_hooks) { + gvl_hook_t *h = rb_gvl_hooks; + gvl_hook_event_args_t args = { .waiting = waiting }; + do { + if (h->event & event) { + (*h->callback)(event, args); + } + } while((h = h->next)); + } + if ((r = pthread_rwlock_unlock(&rb_gvl_hooks_rw_lock))) { + rb_bug_errno("pthread_rwlock_unlock", r); + } +} + enum rtimer_state { /* alive, after timer_create: */ RTIMER_DISARM, @@ -290,6 +367,10 @@ gvl_acquire_common(rb_global_vm_lock_t *gvl, rb_thread_t *th) "we must not be in ubf_list and GVL waitq at the same time"); list_add_tail(&gvl->waitq, &nd->node.gvl); + ATOMIC_INC(gvl->waiting); + if (rb_gvl_hooks) { + rb_gvl_execute_hooks(RUBY_INTERNAL_EVENT_GVL_ACQUIRE_ENTER, gvl->waiting); + } do { if (!gvl->timer) { @@ -301,6 +382,7 @@ gvl_acquire_common(rb_global_vm_lock_t *gvl, rb_thread_t *th) } while (gvl->owner); list_del_init(&nd->node.gvl); + ATOMIC_DEC(gvl->waiting); if (gvl->need_yield) { gvl->need_yield = 0; @@ -311,6 +393,11 @@ gvl_acquire_common(rb_global_vm_lock_t *gvl, rb_thread_t *th) gvl->timer_err = ETIMEDOUT; } gvl->owner = th; + + if (rb_gvl_hooks) { + rb_gvl_execute_hooks(RUBY_INTERNAL_EVENT_GVL_ACQUIRE_EXIT, gvl->waiting); + } + if (!gvl->timer) { if (!designate_timer_thread(gvl) && !ubf_threads_empty()) { rb_thread_wakeup_timer_thread(-1); @@ -329,6 +416,10 @@ gvl_acquire(rb_global_vm_lock_t *gvl, rb_thread_t *th) static const native_thread_data_t * gvl_release_common(rb_global_vm_lock_t *gvl) { + if (rb_gvl_hooks) { + rb_gvl_execute_hooks(RUBY_INTERNAL_EVENT_GVL_RELEASE, gvl->waiting); + } + native_thread_data_t *next; gvl->owner = 0; next = list_top(&gvl->waitq, native_thread_data_t, node.ubf); @@ -390,6 +481,7 @@ rb_gvl_init(rb_global_vm_lock_t *gvl) rb_native_cond_initialize(&gvl->switch_wait_cond); list_head_init(&gvl->waitq); gvl->owner = 0; + gvl->waiting = 0; gvl->timer = 0; gvl->timer_err = ETIMEDOUT; gvl->need_yield = 0; @@ -644,9 +736,14 @@ static void native_thread_init(rb_thread_t *th); void Init_native_thread(rb_thread_t *th) { + int r; + if ((r = pthread_rwlock_init(&rb_gvl_hooks_rw_lock, NULL))) { + rb_bug_errno("pthread_rwlock_init", r); + } + #if defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) if (condattr_monotonic) { - int r = pthread_condattr_init(condattr_monotonic); + r = pthread_condattr_init(condattr_monotonic); if (r == 0) { r = pthread_condattr_setclock(condattr_monotonic, CLOCK_MONOTONIC); } diff --git a/thread_pthread.h b/thread_pthread.h index 2ac354046c010b..1b38a6d34cedbc 100644 --- a/thread_pthread.h +++ b/thread_pthread.h @@ -59,6 +59,7 @@ typedef struct rb_global_vm_lock_struct { * timer. */ struct list_head waitq; /* <=> native_thread_data_t.node.ubf */ + rb_atomic_t waiting; const struct rb_thread_struct *timer; int timer_err; diff --git a/thread_win32.c b/thread_win32.c index 9b44ceb96a9e4a..6838820084aa36 100644 --- a/thread_win32.c +++ b/thread_win32.c @@ -35,6 +35,16 @@ static volatile DWORD ruby_native_thread_key = TLS_OUT_OF_INDEXES; static int w32_wait_events(HANDLE *events, int count, DWORD timeout, rb_thread_t *th); +gvl_hook_t * +rb_gvl_event_new(rb_gvl_callback callback, rb_event_flag_t event) { + // not implemented yet +} + +bool +rb_gvl_event_delete(gvl_hook_t * hook) { + // not implemented yet +} + RBIMPL_ATTR_NORETURN() static void w32_error(const char *func)