diff --git a/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c b/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c index 1bf21e16e3f..2cff358ad69 100644 --- a/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +++ b/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c @@ -25,6 +25,9 @@ void self_test_clock_id(void) { // Safety: This function is assumed never to raise exceptions by callers thread_cpu_time_id thread_cpu_time_id_for(VALUE thread) { rb_nativethread_id_t thread_id = pthread_id_for(thread); + + if (thread_id == 0) return (thread_cpu_time_id) {.valid = false}; + clockid_t clock_id; int error = pthread_getcpuclockid(thread_id, &clock_id); diff --git a/ext/ddtrace_profiling_native_extension/extconf.rb b/ext/ddtrace_profiling_native_extension/extconf.rb index bbd049c1e1e..4d5df803c79 100644 --- a/ext/ddtrace_profiling_native_extension/extconf.rb +++ b/ext/ddtrace_profiling_native_extension/extconf.rb @@ -134,6 +134,9 @@ def add_compiler_flag(flag) $defs << '-DHAVE_PTHREAD_GETCPUCLOCKID' end +# On older Rubies, M:N threads were not available +$defs << '-DNO_MN_THREADS_AVAILABLE' if RUBY_VERSION < '3.3' + # On older Rubies, we did not need to include the ractor header (this was built into the MJIT header) $defs << '-DNO_RACTOR_HEADER_INCLUDE' if RUBY_VERSION < '3.3' diff --git a/ext/ddtrace_profiling_native_extension/private_vm_api_access.c b/ext/ddtrace_profiling_native_extension/private_vm_api_access.c index fb5d169db0f..99a65e44a23 100644 --- a/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +++ b/ext/ddtrace_profiling_native_extension/private_vm_api_access.c @@ -58,9 +58,12 @@ static inline rb_thread_t *thread_struct_from_object(VALUE thread) { } rb_nativethread_id_t pthread_id_for(VALUE thread) { - // struct rb_native_thread was introduced in Ruby 3.2 (preview2): https://github.com/ruby/ruby/pull/5836 + // struct rb_native_thread was introduced in Ruby 3.2: https://github.com/ruby/ruby/pull/5836 #ifndef NO_RB_NATIVE_THREAD - return thread_struct_from_object(thread)->nt->thread_id; + struct rb_native_thread* native_thread = thread_struct_from_object(thread)->nt; + // This can be NULL on Ruby 3.3 with MN threads (RUBY_MN_THREADS=1) + if (native_thread == NULL) return 0; + return native_thread->thread_id; #else return thread_struct_from_object(thread)->thread_id; #endif @@ -113,15 +116,16 @@ bool is_current_thread_holding_the_gvl(void) { if (current_owner == NULL) return (current_gvl_owner) {.valid = false}; - return (current_gvl_owner) { - .valid = true, - .owner = - #ifndef NO_RB_NATIVE_THREAD - current_owner->nt->thread_id - #else - current_owner->thread_id - #endif - }; + #ifndef NO_RB_NATIVE_THREAD + struct rb_native_thread* current_owner_native_thread = current_owner->nt; + + // This can be NULL on Ruby 3.3 with MN threads (RUBY_MN_THREADS=1) + if (current_owner_native_thread == NULL) return (current_gvl_owner) {.valid = false}; + + return (current_gvl_owner) {.valid = true, .owner = current_owner_native_thread->thread_id}; + #else + return (current_gvl_owner) {.valid = true, .owner = current_owner->thread_id}; + #endif } #else current_gvl_owner gvl_owner(void) { @@ -182,7 +186,9 @@ uint64_t native_thread_id_for(VALUE thread) { // The tid is only available on Ruby >= 3.1 + Linux (and FreeBSD). It's the same as `gettid()` aka the task id as seen in /proc #if !defined(NO_THREAD_TID) && defined(RB_THREAD_T_HAS_NATIVE_ID) #ifndef NO_RB_NATIVE_THREAD - return thread_struct_from_object(thread)->nt->tid; + struct rb_native_thread* native_thread = thread_struct_from_object(thread)->nt; + if (native_thread == NULL) rb_raise(rb_eRuntimeError, "BUG: rb_native_thread* is null. Is this Ruby running with RUBY_MN_THREADS=1?"); + return native_thread->tid; #else return thread_struct_from_object(thread)->tid; #endif @@ -811,3 +817,13 @@ VALUE invoke_location_for(VALUE thread, int *line_location) { *line_location = NUM2INT(rb_iseq_first_lineno(iseq)); return rb_iseq_path(iseq); } + +void self_test_mn_enabled(void) { + #ifdef NO_MN_THREADS_AVAILABLE + return; + #else + if (ddtrace_get_ractor()->threads.sched.enable_mn_threads == true) { + rb_raise(rb_eRuntimeError, "Ruby VM is running with RUBY_MN_THREADS=1. This is not yet supported"); + } + #endif +} diff --git a/ext/ddtrace_profiling_native_extension/private_vm_api_access.h b/ext/ddtrace_profiling_native_extension/private_vm_api_access.h index 608a25f7198..a98e9e8d528 100644 --- a/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +++ b/ext/ddtrace_profiling_native_extension/private_vm_api_access.h @@ -49,3 +49,6 @@ bool ddtrace_rb_ractor_main_p(void); // This is what Ruby shows in `Thread#to_s`. // The file is returned directly, and the line is recorded onto *line_location. VALUE invoke_location_for(VALUE thread, int *line_location); + +// Check if RUBY_MN_THREADS is enabled (aka main Ractor is not doing 1:1 threads) +void self_test_mn_enabled(void); diff --git a/ext/ddtrace_profiling_native_extension/profiling.c b/ext/ddtrace_profiling_native_extension/profiling.c index 1e7d754d56b..9dc9952b4a7 100644 --- a/ext/ddtrace_profiling_native_extension/profiling.c +++ b/ext/ddtrace_profiling_native_extension/profiling.c @@ -68,6 +68,7 @@ void DDTRACE_EXPORT Init_ddtrace_profiling_native_extension(void) { static VALUE native_working_p(DDTRACE_UNUSED VALUE _self) { self_test_clock_id(); + self_test_mn_enabled(); return Qtrue; }