mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	[Feature #18339] GVL Instrumentation API
Ref: https://bugs.ruby-lang.org/issues/18339 Design: - This tries to minimize the overhead when no hook is registered. It should only incur an extra unsynchronized boolean check. - The hook list is protected with a read-write lock as to cause contention when some hooks are registered. - The hooks MUST be thread safe, and MUST NOT call into Ruby as they are executed outside the GVL. - It's simply a noop on Windows. API: ``` rb_internal_thread_event_hook_t * rb_internal_thread_add_event_hook(rb_internal_thread_event_callback callback, rb_event_flag_t internal_event, void *user_data); bool rb_internal_thread_remove_event_hook(rb_internal_thread_event_hook_t * hook); ``` You can subscribe to 3 events: - READY: called right before attempting to acquire the GVL - RESUMED: called right after successfully acquiring the GVL - SUSPENDED: called right after releasing the GVL. The hooks MUST be threadsafe, as they are executed outside of the GVL, they also MUST NOT call any Ruby API.
This commit is contained in:
		
							parent
							
								
									d142eff658
								
							
						
					
					
						commit
						9125374726
					
				
				
				Notes:
				
					git
				
				2022-06-03 22:13:53 +09:00 
				
			
			
			
		
		
					 7 changed files with 468 additions and 1 deletions
				
			
		
							
								
								
									
										164
									
								
								ext/-test-/thread/instrumentation/depend
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								ext/-test-/thread/instrumentation/depend
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,164 @@
 | 
			
		|||
# 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/abi.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
 | 
			
		||||
							
								
								
									
										2
									
								
								ext/-test-/thread/instrumentation/extconf.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								ext/-test-/thread/instrumentation/extconf.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
# frozen_string_literal: false
 | 
			
		||||
create_makefile("-test-/thread/instrumentation")
 | 
			
		||||
							
								
								
									
										95
									
								
								ext/-test-/thread/instrumentation/instrumentation.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								ext/-test-/thread/instrumentation/instrumentation.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,95 @@
 | 
			
		|||
#include "ruby/ruby.h"
 | 
			
		||||
#include "ruby/atomic.h"
 | 
			
		||||
#include "ruby/thread.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, const rb_internal_thread_event_data_t *event_data, void *user_data)
 | 
			
		||||
{
 | 
			
		||||
    switch(event) {
 | 
			
		||||
      case RUBY_INTERNAL_THREAD_EVENT_READY:
 | 
			
		||||
        RUBY_ATOMIC_INC(acquire_enter_count);
 | 
			
		||||
        break;
 | 
			
		||||
      case RUBY_INTERNAL_THREAD_EVENT_RESUMED:
 | 
			
		||||
        RUBY_ATOMIC_INC(acquire_exit_count);
 | 
			
		||||
        break;
 | 
			
		||||
      case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED:
 | 
			
		||||
        RUBY_ATOMIC_INC(release_count);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static rb_internal_thread_event_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_callback(VALUE thread)
 | 
			
		||||
{
 | 
			
		||||
    single_hook = rb_internal_thread_add_event_hook(
 | 
			
		||||
        *ex_callback,
 | 
			
		||||
        RUBY_INTERNAL_THREAD_EVENT_READY | RUBY_INTERNAL_THREAD_EVENT_RESUMED | RUBY_INTERNAL_THREAD_EVENT_SUSPENDED,
 | 
			
		||||
        NULL
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return Qnil;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static VALUE
 | 
			
		||||
thread_unregister_callback(VALUE thread)
 | 
			
		||||
{
 | 
			
		||||
    if (single_hook) {
 | 
			
		||||
        rb_internal_thread_remove_event_hook(single_hook);
 | 
			
		||||
        single_hook = NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Qnil;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static VALUE
 | 
			
		||||
thread_register_and_unregister_callback(VALUE thread)
 | 
			
		||||
{
 | 
			
		||||
    rb_internal_thread_event_hook_t * hooks[5];
 | 
			
		||||
    for (int i = 0; i < 5; i++) {
 | 
			
		||||
        hooks[i] = rb_internal_thread_add_event_hook(*ex_callback, RUBY_INTERNAL_THREAD_EVENT_READY, NULL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!rb_internal_thread_remove_event_hook(hooks[4])) return Qfalse;
 | 
			
		||||
    if (!rb_internal_thread_remove_event_hook(hooks[0])) return Qfalse;
 | 
			
		||||
    if (!rb_internal_thread_remove_event_hook(hooks[3])) return Qfalse;
 | 
			
		||||
    if (!rb_internal_thread_remove_event_hook(hooks[2])) return Qfalse;
 | 
			
		||||
    if (!rb_internal_thread_remove_event_hook(hooks[1])) return Qfalse;
 | 
			
		||||
    return Qtrue;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
Init_instrumentation(void)
 | 
			
		||||
{
 | 
			
		||||
    VALUE mBug = rb_define_module("Bug");
 | 
			
		||||
    VALUE klass = rb_define_module_under(mBug, "ThreadInstrumentation");
 | 
			
		||||
    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_callback, 0);
 | 
			
		||||
    rb_define_singleton_method(klass, "unregister_callback", thread_unregister_callback, 0);
 | 
			
		||||
    rb_define_singleton_method(klass, "register_and_unregister_callbacks", thread_register_and_unregister_callback, 0);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -190,6 +190,44 @@ void *rb_nogvl(void *(*func)(void *), void *data1,
 | 
			
		|||
 */
 | 
			
		||||
#define RUBY_CALL_WO_GVL_FLAG_SKIP_CHECK_INTS_
 | 
			
		||||
 | 
			
		||||
#define RUBY_INTERNAL_THREAD_EVENT_READY      0x01 /** acquiring GVL */
 | 
			
		||||
#define RUBY_INTERNAL_THREAD_EVENT_RESUMED    0x02 /** acquired GVL */
 | 
			
		||||
#define RUBY_INTERNAL_THREAD_EVENT_SUSPENDED  0x04 /** released GVL */
 | 
			
		||||
#define RUBY_INTERNAL_THREAD_EVENT_MASK       0x07 /** All Thread events */
 | 
			
		||||
 | 
			
		||||
typedef void rb_internal_thread_event_data_t; // for future extension.
 | 
			
		||||
 | 
			
		||||
typedef void (*rb_internal_thread_event_callback)(rb_event_flag_t event,
 | 
			
		||||
              const rb_internal_thread_event_data_t *event_data,
 | 
			
		||||
              void *user_data);
 | 
			
		||||
typedef struct rb_internal_thread_event_hook rb_internal_thread_event_hook_t;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Registers a thread event hook function.
 | 
			
		||||
 *
 | 
			
		||||
 * @param[in]  func    A callback.
 | 
			
		||||
 * @param[in]  events  A set of events that `func` should run.
 | 
			
		||||
 * @param[in]  data    Passed as-is to `func`.
 | 
			
		||||
 * @return     An opaque pointer to the hook, to unregister it later.
 | 
			
		||||
 * @note       This functionality is a noop on Windows.
 | 
			
		||||
 * @warning    This function MUST not be called from a thread event callback.
 | 
			
		||||
 */
 | 
			
		||||
rb_internal_thread_event_hook_t *rb_internal_thread_add_event_hook(
 | 
			
		||||
        rb_internal_thread_event_callback func, rb_event_flag_t events,
 | 
			
		||||
        void *data);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unregister the passed hook.
 | 
			
		||||
 *
 | 
			
		||||
 * @param[in]  hook.  The hook to unregister.
 | 
			
		||||
 * @return     Wether the hook was found and unregistered.
 | 
			
		||||
 * @note       This functionality is a noop on Windows.
 | 
			
		||||
 * @warning    This function MUST not be called from a thread event callback.
 | 
			
		||||
*/
 | 
			
		||||
bool rb_internal_thread_remove_event_hook(
 | 
			
		||||
        rb_internal_thread_event_hook_t * hook);
 | 
			
		||||
 | 
			
		||||
RBIMPL_SYMBOL_EXPORT_END()
 | 
			
		||||
 | 
			
		||||
#endif /* RUBY_THREAD_H */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										50
									
								
								test/-ext-/thread/test_instrumentation_api.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								test/-ext-/thread/test_instrumentation_api.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,50 @@
 | 
			
		|||
# frozen_string_literal: false
 | 
			
		||||
class TestThreadInstrumentation < Test::Unit::TestCase
 | 
			
		||||
  def setup
 | 
			
		||||
    pend("TODO: No windows support yet") if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_thread_instrumentation
 | 
			
		||||
    require '-test-/thread/instrumentation'
 | 
			
		||||
    Bug::ThreadInstrumentation.reset_counters
 | 
			
		||||
    Bug::ThreadInstrumentation::register_callback
 | 
			
		||||
 | 
			
		||||
    begin
 | 
			
		||||
      threads = 5.times.map { Thread.new { sleep 0.05; 1 + 1; sleep 0.02 } }
 | 
			
		||||
      threads.each(&:join)
 | 
			
		||||
      Bug::ThreadInstrumentation.counters.each do |c|
 | 
			
		||||
        assert_predicate c,:nonzero?
 | 
			
		||||
      end
 | 
			
		||||
    ensure
 | 
			
		||||
      Bug::ThreadInstrumentation::unregister_callback
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_thread_instrumentation_fork_safe
 | 
			
		||||
    skip "No fork()" unless Process.respond_to?(:fork)
 | 
			
		||||
 | 
			
		||||
    require '-test-/thread/instrumentation'
 | 
			
		||||
    Bug::ThreadInstrumentation::register_callback
 | 
			
		||||
 | 
			
		||||
    begin
 | 
			
		||||
      pid = fork do
 | 
			
		||||
        Bug::ThreadInstrumentation.reset_counters
 | 
			
		||||
        threads = 5.times.map { Thread.new { sleep 0.05; 1 + 1; sleep 0.02 } }
 | 
			
		||||
        threads.each(&:join)
 | 
			
		||||
        Bug::ThreadInstrumentation.counters.each do |c|
 | 
			
		||||
          assert_predicate c,:nonzero?
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
      _, status = Process.wait2(pid)
 | 
			
		||||
      assert_predicate status, :success?
 | 
			
		||||
    ensure
 | 
			
		||||
      Bug::ThreadInstrumentation::unregister_callback
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_thread_instrumentation_unregister
 | 
			
		||||
    require '-test-/thread/instrumentation'
 | 
			
		||||
    assert Bug::ThreadInstrumentation::register_and_unregister_callbacks
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										108
									
								
								thread_pthread.c
									
										
									
									
									
								
							
							
						
						
									
										108
									
								
								thread_pthread.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -98,6 +98,95 @@
 | 
			
		|||
#  endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
struct rb_internal_thread_event_hook {
 | 
			
		||||
    rb_internal_thread_event_callback callback;
 | 
			
		||||
    rb_event_flag_t event;
 | 
			
		||||
    void *user_data;
 | 
			
		||||
 | 
			
		||||
    struct rb_internal_thread_event_hook *next;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static rb_internal_thread_event_hook_t *rb_internal_thread_event_hooks = NULL;
 | 
			
		||||
static pthread_rwlock_t rb_internal_thread_event_hooks_rw_lock = PTHREAD_RWLOCK_INITIALIZER;
 | 
			
		||||
 | 
			
		||||
rb_internal_thread_event_hook_t *
 | 
			
		||||
rb_internal_thread_add_event_hook(rb_internal_thread_event_callback callback, rb_event_flag_t internal_event, void *user_data)
 | 
			
		||||
{
 | 
			
		||||
    rb_internal_thread_event_hook_t *hook = ALLOC_N(rb_internal_thread_event_hook_t, 1);
 | 
			
		||||
    hook->callback = callback;
 | 
			
		||||
    hook->user_data = user_data;
 | 
			
		||||
    hook->event = internal_event;
 | 
			
		||||
 | 
			
		||||
    int r;
 | 
			
		||||
    if ((r = pthread_rwlock_wrlock(&rb_internal_thread_event_hooks_rw_lock))) {
 | 
			
		||||
        rb_bug_errno("pthread_rwlock_wrlock", r);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hook->next = rb_internal_thread_event_hooks;
 | 
			
		||||
    ATOMIC_PTR_EXCHANGE(rb_internal_thread_event_hooks, hook);
 | 
			
		||||
 | 
			
		||||
    if ((r = pthread_rwlock_unlock(&rb_internal_thread_event_hooks_rw_lock))) {
 | 
			
		||||
        rb_bug_errno("pthread_rwlock_unlock", r);
 | 
			
		||||
    }
 | 
			
		||||
    return hook;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
rb_internal_thread_remove_event_hook(rb_internal_thread_event_hook_t * hook)
 | 
			
		||||
{
 | 
			
		||||
    int r;
 | 
			
		||||
    if ((r = pthread_rwlock_wrlock(&rb_internal_thread_event_hooks_rw_lock))) {
 | 
			
		||||
        rb_bug_errno("pthread_rwlock_wrlock", r);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool success = FALSE;
 | 
			
		||||
 | 
			
		||||
    if (rb_internal_thread_event_hooks == hook) {
 | 
			
		||||
        ATOMIC_PTR_EXCHANGE(rb_internal_thread_event_hooks, hook->next);
 | 
			
		||||
        success = TRUE;
 | 
			
		||||
    } else {
 | 
			
		||||
        rb_internal_thread_event_hook_t *h = rb_internal_thread_event_hooks;
 | 
			
		||||
 | 
			
		||||
        do {
 | 
			
		||||
            if (h->next == hook) {
 | 
			
		||||
                h->next = hook->next;
 | 
			
		||||
                success = TRUE;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        } while ((h = h->next));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ((r = pthread_rwlock_unlock(&rb_internal_thread_event_hooks_rw_lock))) {
 | 
			
		||||
        rb_bug_errno("pthread_rwlock_unlock", r);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (success) {
 | 
			
		||||
        ruby_xfree(hook);
 | 
			
		||||
    }
 | 
			
		||||
    return success;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
rb_thread_execute_hooks(rb_event_flag_t event)
 | 
			
		||||
{
 | 
			
		||||
    int r;
 | 
			
		||||
    if ((r = pthread_rwlock_rdlock(&rb_internal_thread_event_hooks_rw_lock))) {
 | 
			
		||||
        rb_bug_errno("pthread_rwlock_rdlock", r);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (rb_internal_thread_event_hooks) {
 | 
			
		||||
        rb_internal_thread_event_hook_t *h = rb_internal_thread_event_hooks;
 | 
			
		||||
        do {
 | 
			
		||||
            if (h->event & event) {
 | 
			
		||||
                (*h->callback)(event, NULL, h->user_data);
 | 
			
		||||
            }
 | 
			
		||||
        } while((h = h->next));
 | 
			
		||||
    }
 | 
			
		||||
    if ((r = pthread_rwlock_unlock(&rb_internal_thread_event_hooks_rw_lock))) {
 | 
			
		||||
        rb_bug_errno("pthread_rwlock_unlock", r);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum rtimer_state {
 | 
			
		||||
    /* alive, after timer_create: */
 | 
			
		||||
    RTIMER_DISARM,
 | 
			
		||||
| 
						 | 
				
			
			@ -295,6 +384,10 @@ thread_sched_to_running_common(struct rb_thread_sched *sched, rb_thread_t *th)
 | 
			
		|||
        // waiting -> ready
 | 
			
		||||
        thread_sched_to_ready_common(sched, th);
 | 
			
		||||
 | 
			
		||||
        if (rb_internal_thread_event_hooks) {
 | 
			
		||||
            rb_thread_execute_hooks(RUBY_INTERNAL_THREAD_EVENT_READY);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // wait for running chance
 | 
			
		||||
        do {
 | 
			
		||||
            if (!sched->timer) {
 | 
			
		||||
| 
						 | 
				
			
			@ -319,6 +412,10 @@ thread_sched_to_running_common(struct rb_thread_sched *sched, rb_thread_t *th)
 | 
			
		|||
    // ready -> running
 | 
			
		||||
    sched->running = th;
 | 
			
		||||
 | 
			
		||||
    if (rb_internal_thread_event_hooks) {
 | 
			
		||||
        rb_thread_execute_hooks(RUBY_INTERNAL_THREAD_EVENT_RESUMED);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!sched->timer) {
 | 
			
		||||
        if (!designate_timer_thread(sched) && !ubf_threads_empty()) {
 | 
			
		||||
            rb_thread_wakeup_timer_thread(-1);
 | 
			
		||||
| 
						 | 
				
			
			@ -337,6 +434,10 @@ thread_sched_to_running(struct rb_thread_sched *sched, rb_thread_t *th)
 | 
			
		|||
static rb_thread_t *
 | 
			
		||||
thread_sched_to_waiting_common(struct rb_thread_sched *sched)
 | 
			
		||||
{
 | 
			
		||||
    if (rb_internal_thread_event_hooks) {
 | 
			
		||||
        rb_thread_execute_hooks(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    rb_thread_t *next;
 | 
			
		||||
    sched->running = NULL;
 | 
			
		||||
    next = ccan_list_top(&sched->readyq, rb_thread_t, sched.node.readyq);
 | 
			
		||||
| 
						 | 
				
			
			@ -687,9 +788,14 @@ native_thread_init(struct rb_native_thread *nt)
 | 
			
		|||
void
 | 
			
		||||
Init_native_thread(rb_thread_t *main_th)
 | 
			
		||||
{
 | 
			
		||||
    int r;
 | 
			
		||||
    if ((r = pthread_rwlock_init(&rb_internal_thread_event_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);
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,6 +29,18 @@ 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);
 | 
			
		||||
 | 
			
		||||
rb_internal_thread_event_hook_t *
 | 
			
		||||
rb_internal_thread_add_event_hook(rb_internal_thread_event_callback callback, rb_event_flag_t internal_event, void *user_data)
 | 
			
		||||
{
 | 
			
		||||
    // not implemented
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
rb_internal_thread_remove_event_hook(rb_internal_thread_event_hook_t * hook)
 | 
			
		||||
{
 | 
			
		||||
    // not implemented
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RBIMPL_ATTR_NORETURN()
 | 
			
		||||
static void
 | 
			
		||||
w32_error(const char *func)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue