aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean Boussier <jean.boussier@gmail.com>2022-01-27 17:12:22 +0100
committerJean Boussier <jean.boussier@gmail.com>2022-06-03 15:13:33 +0200
commit9125374726fbf68c05ee7585d4a374ffc5efc5db (patch)
tree5f820c00632eb80a336161245baaf9248dd9eb51
parentd142eff6586de0018c9442129201b03c826f2a1e (diff)
downloadruby-9125374726fbf68c05ee7585d4a374ffc5efc5db.tar.gz
[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.
-rw-r--r--ext/-test-/thread/instrumentation/depend164
-rw-r--r--ext/-test-/thread/instrumentation/extconf.rb2
-rw-r--r--ext/-test-/thread/instrumentation/instrumentation.c95
-rw-r--r--include/ruby/thread.h38
-rw-r--r--test/-ext-/thread/test_instrumentation_api.rb50
-rw-r--r--thread_pthread.c108
-rw-r--r--thread_win32.c12
7 files changed, 468 insertions, 1 deletions
diff --git a/ext/-test-/thread/instrumentation/depend b/ext/-test-/thread/instrumentation/depend
new file mode 100644
index 0000000000..e2fcd060d8
--- /dev/null
+++ b/ext/-test-/thread/instrumentation/depend
@@ -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
diff --git a/ext/-test-/thread/instrumentation/extconf.rb b/ext/-test-/thread/instrumentation/extconf.rb
new file mode 100644
index 0000000000..a48ba3c045
--- /dev/null
+++ b/ext/-test-/thread/instrumentation/extconf.rb
@@ -0,0 +1,2 @@
+# frozen_string_literal: false
+create_makefile("-test-/thread/instrumentation")
diff --git a/ext/-test-/thread/instrumentation/instrumentation.c b/ext/-test-/thread/instrumentation/instrumentation.c
new file mode 100644
index 0000000000..c298d76ad6
--- /dev/null
+++ b/ext/-test-/thread/instrumentation/instrumentation.c
@@ -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);
+}
diff --git a/include/ruby/thread.h b/include/ruby/thread.h
index 18c792b386..7f9e623fe5 100644
--- a/include/ruby/thread.h
+++ b/include/ruby/thread.h
@@ -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 */
diff --git a/test/-ext-/thread/test_instrumentation_api.rb b/test/-ext-/thread/test_instrumentation_api.rb
new file mode 100644
index 0000000000..9f3f3601a6
--- /dev/null
+++ b/test/-ext-/thread/test_instrumentation_api.rb
@@ -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
+
diff --git a/thread_pthread.c b/thread_pthread.c
index 125cf57c77..fee32bae34 100644
--- a/thread_pthread.c
+++ b/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);
}
diff --git a/thread_win32.c b/thread_win32.c
index a8c9b94cd7..2a3656450b 100644
--- a/thread_win32.c
+++ b/thread_win32.c
@@ -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)