summaryrefslogtreecommitdiff
path: root/ext/-test-/thread
diff options
context:
space:
mode:
Diffstat (limited to 'ext/-test-/thread')
-rw-r--r--ext/-test-/thread/id/extconf.rb3
-rw-r--r--ext/-test-/thread/id/id.c15
-rw-r--r--ext/-test-/thread/instrumentation/depend4
-rw-r--r--ext/-test-/thread/instrumentation/instrumentation.c215
-rw-r--r--ext/-test-/thread/lock_native_thread/extconf.rb2
-rw-r--r--ext/-test-/thread/lock_native_thread/lock_native_thread.c50
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