diff options
Diffstat (limited to 'ext/-test-/thread')
-rw-r--r-- | ext/-test-/thread/id/extconf.rb | 3 | ||||
-rw-r--r-- | ext/-test-/thread/id/id.c | 15 | ||||
-rw-r--r-- | ext/-test-/thread/instrumentation/depend | 4 | ||||
-rw-r--r-- | ext/-test-/thread/instrumentation/instrumentation.c | 215 | ||||
-rw-r--r-- | ext/-test-/thread/lock_native_thread/extconf.rb | 2 | ||||
-rw-r--r-- | ext/-test-/thread/lock_native_thread/lock_native_thread.c | 50 |
6 files changed, 219 insertions, 70 deletions
diff --git a/ext/-test-/thread/id/extconf.rb b/ext/-test-/thread/id/extconf.rb new file mode 100644 index 0000000000..a0ae0eff15 --- /dev/null +++ b/ext/-test-/thread/id/extconf.rb @@ -0,0 +1,3 @@ +if have_func("gettid") + create_makefile("-test-/thread/id") +end diff --git a/ext/-test-/thread/id/id.c b/ext/-test-/thread/id/id.c new file mode 100644 index 0000000000..b46a5955e2 --- /dev/null +++ b/ext/-test-/thread/id/id.c @@ -0,0 +1,15 @@ +#include <ruby.h> + +static VALUE +bug_gettid(VALUE self) +{ + pid_t tid = gettid(); + return PIDT2NUM(tid); +} + +void +Init_id(void) +{ + VALUE klass = rb_define_module_under(rb_define_module("Bug"), "ThreadID"); + rb_define_module_function(klass, "gettid", bug_gettid, 0); +} diff --git a/ext/-test-/thread/instrumentation/depend b/ext/-test-/thread/instrumentation/depend index e2fcd060d8..a37e4d5675 100644 --- a/ext/-test-/thread/instrumentation/depend +++ b/ext/-test-/thread/instrumentation/depend @@ -52,6 +52,7 @@ 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/packed_struct.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 @@ -111,7 +112,6 @@ 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 @@ -142,12 +142,12 @@ 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/stdckdint.h instrumentation.o: $(hdrdir)/ruby/internal/symbol.h instrumentation.o: $(hdrdir)/ruby/internal/value.h instrumentation.o: $(hdrdir)/ruby/internal/value_type.h diff --git a/ext/-test-/thread/instrumentation/instrumentation.c b/ext/-test-/thread/instrumentation/instrumentation.c index d2a2c2740b..d81bc0f2a7 100644 --- a/ext/-test-/thread/instrumentation/instrumentation.c +++ b/ext/-test-/thread/instrumentation/instrumentation.c @@ -2,90 +2,137 @@ #include "ruby/atomic.h" #include "ruby/thread.h" -static rb_atomic_t started_count = 0; -static rb_atomic_t ready_count = 0; -static rb_atomic_t resumed_count = 0; -static rb_atomic_t suspended_count = 0; -static rb_atomic_t exited_count = 0; - -#if __STDC_VERSION__ >= 201112 - #define RB_THREAD_LOCAL_SPECIFIER _Thread_local -#elif defined(__GNUC__) && !defined(RB_THREAD_LOCAL_SPECIFIER_IS_UNSUPPORTED) - /* note that ICC (linux) and Clang are covered by __GNUC__ */ - #define RB_THREAD_LOCAL_SPECIFIER __thread -#else - #define RB_THREAD_LOCAL_SPECIFIER +#ifndef RB_THREAD_LOCAL_SPECIFIER +# define RB_THREAD_LOCAL_SPECIFIER #endif -static RB_THREAD_LOCAL_SPECIFIER unsigned int local_ready_count = 0; -static RB_THREAD_LOCAL_SPECIFIER unsigned int local_resumed_count = 0; -static RB_THREAD_LOCAL_SPECIFIER unsigned int local_suspended_count = 0; +static VALUE last_thread = Qnil; +static VALUE timeline_value = Qnil; + +struct thread_event { + VALUE thread; + rb_event_flag_t event; +}; + +#define MAX_EVENTS 1024 +static struct thread_event event_timeline[MAX_EVENTS]; +static rb_atomic_t timeline_cursor; static void -ex_callback(rb_event_flag_t event, const rb_internal_thread_event_data_t *event_data, void *user_data) +event_timeline_gc_mark(void *ptr) { + rb_atomic_t cursor; + for (cursor = 0; cursor < timeline_cursor; cursor++) { + rb_gc_mark(event_timeline[cursor].thread); + } +} + +static const rb_data_type_t event_timeline_type = { + "TestThreadInstrumentation/event_timeline", + {event_timeline_gc_mark, NULL, NULL,}, + 0, 0, + RUBY_TYPED_FREE_IMMEDIATELY, +}; + +static void +reset_timeline(void) +{ + timeline_cursor = 0; + memset(event_timeline, 0, sizeof(struct thread_event) * MAX_EVENTS); +} + +static rb_event_flag_t +find_last_event(VALUE thread) +{ + rb_atomic_t cursor = timeline_cursor; + if (cursor) { + do { + if (event_timeline[cursor].thread == thread){ + return event_timeline[cursor].event; + } + cursor--; + } while (cursor > 0); + } + return 0; +} + +static const char * +event_name(rb_event_flag_t event) { switch (event) { case RUBY_INTERNAL_THREAD_EVENT_STARTED: - RUBY_ATOMIC_INC(started_count); - break; + return "started"; case RUBY_INTERNAL_THREAD_EVENT_READY: - RUBY_ATOMIC_INC(ready_count); - local_ready_count++; - break; + return "ready"; case RUBY_INTERNAL_THREAD_EVENT_RESUMED: - RUBY_ATOMIC_INC(resumed_count); - local_resumed_count++; - break; + return "resumed"; case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED: - RUBY_ATOMIC_INC(suspended_count); - local_suspended_count++; - break; + return "suspended"; case RUBY_INTERNAL_THREAD_EVENT_EXITED: - RUBY_ATOMIC_INC(exited_count); - break; + return "exited"; } + return "no-event"; } -static rb_internal_thread_event_hook_t * single_hook = NULL; - -static VALUE -thread_counters(VALUE thread) +static void +unexpected(bool strict, const char *format, VALUE thread, rb_event_flag_t last_event) { - VALUE array = rb_ary_new2(5); - rb_ary_push(array, UINT2NUM(started_count)); - rb_ary_push(array, UINT2NUM(ready_count)); - rb_ary_push(array, UINT2NUM(resumed_count)); - rb_ary_push(array, UINT2NUM(suspended_count)); - rb_ary_push(array, UINT2NUM(exited_count)); - return array; + const char *last_event_name = event_name(last_event); + if (strict) { + rb_bug(format, thread, last_event_name); + } + else { + fprintf(stderr, format, thread, last_event_name); + fprintf(stderr, "\n"); + } } -static VALUE -thread_local_counters(VALUE thread) +static void +ex_callback(rb_event_flag_t event, const rb_internal_thread_event_data_t *event_data, void *user_data) { - VALUE array = rb_ary_new2(3); - rb_ary_push(array, UINT2NUM(local_ready_count)); - rb_ary_push(array, UINT2NUM(local_resumed_count)); - rb_ary_push(array, UINT2NUM(local_suspended_count)); - return array; -} + rb_event_flag_t last_event = find_last_event(event_data->thread); + bool strict = (bool)user_data; -static VALUE -thread_reset_counters(VALUE thread) -{ - RUBY_ATOMIC_SET(started_count, 0); - RUBY_ATOMIC_SET(ready_count, 0); - RUBY_ATOMIC_SET(resumed_count, 0); - RUBY_ATOMIC_SET(suspended_count, 0); - RUBY_ATOMIC_SET(exited_count, 0); - local_ready_count = 0; - local_resumed_count = 0; - local_suspended_count = 0; - return Qtrue; + if (last_event != 0) { + switch (event) { + case RUBY_INTERNAL_THREAD_EVENT_STARTED: + unexpected(strict, "[thread=%"PRIxVALUE"] `started` event can't be preceded by `%s`", event_data->thread, last_event); + break; + case RUBY_INTERNAL_THREAD_EVENT_READY: + if (last_event != RUBY_INTERNAL_THREAD_EVENT_STARTED && last_event != RUBY_INTERNAL_THREAD_EVENT_SUSPENDED) { + unexpected(strict, "[thread=%"PRIxVALUE"] `ready` must be preceded by `started` or `suspended`, got: `%s`", event_data->thread, last_event); + } + break; + case RUBY_INTERNAL_THREAD_EVENT_RESUMED: + if (last_event != RUBY_INTERNAL_THREAD_EVENT_READY) { + unexpected(strict, "[thread=%"PRIxVALUE"] `resumed` must be preceded by `ready`, got: `%s`", event_data->thread, last_event); + } + break; + case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED: + if (last_event != RUBY_INTERNAL_THREAD_EVENT_RESUMED) { + unexpected(strict, "[thread=%"PRIxVALUE"] `suspended` must be preceded by `resumed`, got: `%s`", event_data->thread, last_event); + } + break; + case RUBY_INTERNAL_THREAD_EVENT_EXITED: + if (last_event != RUBY_INTERNAL_THREAD_EVENT_RESUMED && last_event != RUBY_INTERNAL_THREAD_EVENT_SUSPENDED) { + unexpected(strict, "[thread=%"PRIxVALUE"] `exited` must be preceded by `resumed` or `suspended`, got: `%s`", event_data->thread, last_event); + } + break; + } + } + + rb_atomic_t cursor = RUBY_ATOMIC_FETCH_ADD(timeline_cursor, 1); + if (cursor >= MAX_EVENTS) { + rb_bug("TestThreadInstrumentation: ran out of event_timeline space"); + } + + event_timeline[cursor].thread = event_data->thread; + event_timeline[cursor].event = event; } +static rb_internal_thread_event_hook_t * single_hook = NULL; + static VALUE -thread_register_callback(VALUE thread) +thread_register_callback(VALUE thread, VALUE strict) { single_hook = rb_internal_thread_add_event_hook( ex_callback, @@ -94,13 +141,33 @@ thread_register_callback(VALUE thread) RUBY_INTERNAL_THREAD_EVENT_RESUMED | RUBY_INTERNAL_THREAD_EVENT_SUSPENDED | RUBY_INTERNAL_THREAD_EVENT_EXITED, - NULL + (void *)RTEST(strict) ); return Qnil; } static VALUE +event_symbol(rb_event_flag_t event) +{ + switch (event) { + case RUBY_INTERNAL_THREAD_EVENT_STARTED: + return rb_id2sym(rb_intern("started")); + case RUBY_INTERNAL_THREAD_EVENT_READY: + return rb_id2sym(rb_intern("ready")); + case RUBY_INTERNAL_THREAD_EVENT_RESUMED: + return rb_id2sym(rb_intern("resumed")); + case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED: + return rb_id2sym(rb_intern("suspended")); + case RUBY_INTERNAL_THREAD_EVENT_EXITED: + return rb_id2sym(rb_intern("exited")); + default: + rb_bug("TestThreadInstrumentation: Unexpected event"); + break; + } +} + +static VALUE thread_unregister_callback(VALUE thread) { if (single_hook) { @@ -108,7 +175,18 @@ thread_unregister_callback(VALUE thread) single_hook = NULL; } - return Qnil; + VALUE events = rb_ary_new_capa(timeline_cursor); + rb_atomic_t cursor; + for (cursor = 0; cursor < timeline_cursor; cursor++) { + VALUE pair = rb_ary_new_capa(2); + rb_ary_push(pair, event_timeline[cursor].thread); + rb_ary_push(pair, event_symbol(event_timeline[cursor].event)); + rb_ary_push(events, pair); + } + + reset_timeline(); + + return events; } static VALUE @@ -132,10 +210,11 @@ 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, "local_counters", thread_local_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_global_variable(&timeline_value); + timeline_value = TypedData_Wrap_Struct(0, &event_timeline_type, 0); + + rb_global_variable(&last_thread); + rb_define_singleton_method(klass, "register_callback", thread_register_callback, 1); 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/ext/-test-/thread/lock_native_thread/extconf.rb b/ext/-test-/thread/lock_native_thread/extconf.rb new file mode 100644 index 0000000000..832bfde01a --- /dev/null +++ b/ext/-test-/thread/lock_native_thread/extconf.rb @@ -0,0 +1,2 @@ +# frozen_string_literal: false +create_makefile("-test-/thread/lock_native_thread") diff --git a/ext/-test-/thread/lock_native_thread/lock_native_thread.c b/ext/-test-/thread/lock_native_thread/lock_native_thread.c new file mode 100644 index 0000000000..2eb75809a9 --- /dev/null +++ b/ext/-test-/thread/lock_native_thread/lock_native_thread.c @@ -0,0 +1,50 @@ + +#include "ruby/ruby.h" +#include "ruby/thread.h" + +#ifdef HAVE_PTHREAD_H +#include <pthread.h> + +static pthread_key_t tls_key; + +static VALUE +get_tls(VALUE self) +{ + return (VALUE)pthread_getspecific(tls_key); +} + +static VALUE +set_tls(VALUE self, VALUE vn) +{ + pthread_setspecific(tls_key, (void *)vn); + return Qnil; +} + +static VALUE +lock_native_thread(VALUE self) +{ + return rb_thread_lock_native_thread() ? Qtrue : Qfalse; +} + +void +Init_lock_native_thread(void) +{ + int r; + + if ((r = pthread_key_create(&tls_key, NULL)) != 0) { + rb_bug("pthread_key_create() returns %d", r); + } + pthread_setspecific(tls_key, NULL); + + rb_define_method(rb_cThread, "lock_native_thread", lock_native_thread, 0); + rb_define_method(rb_cThread, "get_tls", get_tls, 0); + rb_define_method(rb_cThread, "set_tls", set_tls, 1); +} + +#else // HAVE_PTHREAD_H +void +Init_lock_native_thread(void) +{ + // do nothing +} +#endif // HAVE_PTHREAD_H |