diff --git a/ext/ddtrace_profiling_native_extension/extconf.rb b/ext/ddtrace_profiling_native_extension/extconf.rb index 712b1cd84e6..b9833aac7c2 100644 --- a/ext/ddtrace_profiling_native_extension/extconf.rb +++ b/ext/ddtrace_profiling_native_extension/extconf.rb @@ -125,6 +125,9 @@ def add_compiler_flag(flag) # On older Rubies, we need to use a backported version of this function. See private_vm_api_access.h for details. $defs << '-DUSE_BACKPORTED_RB_PROFILE_FRAME_METHOD_NAME' if RUBY_VERSION < '3' +# On older Rubies, there are no Ractors +$defs << '-DNO_RACTORS' if RUBY_VERSION < '3' + # On older Rubies, we need to use rb_thread_t instead of rb_execution_context_t $defs << '-DUSE_THREAD_INSTEAD_OF_EXECUTION_CONTEXT' if RUBY_VERSION < '2.5' 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 dbc18b05975..9a6f368fab3 100644 --- a/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +++ b/ext/ddtrace_profiling_native_extension/private_vm_api_access.c @@ -690,3 +690,28 @@ int ruby_thread_has_gvl_p(void) { return 0; } #endif // NO_THREAD_HAS_GVL + +#ifndef NO_RACTORS + // This API and definition are exported as a public symbol by the VM BUT the function header is not defined in any public header, so we + // repeat it here to be able to use in our code. + bool rb_ractor_main_p_(void); + extern struct rb_ractor_struct *ruby_single_main_ractor; + + // Taken from upstream ractor_core.h at commit d9cf0388599a3234b9f3c06ddd006cd59a58ab8b (November 2022, Ruby 3.2 trunk) + // to allow us to ensure that we're always operating on the main ractor (if Ruby has ractors) + // Modifications: + // * None + bool ddtrace_rb_ractor_main_p(void) + { + if (ruby_single_main_ractor) { + return true; + } + else { + return rb_ractor_main_p_(); + } + } +#else + // Simplify callers on older Rubies, instead of having them probe if the VM supports Ractors we just tell them that yes + // they're always on the main Ractor + bool ddtrace_rb_ractor_main_p(void) { return true; } +#endif // NO_RACTORS 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 fcd4e7b4b8f..51901513565 100644 --- a/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +++ b/ext/ddtrace_profiling_native_extension/private_vm_api_access.h @@ -18,6 +18,8 @@ bool is_thread_alive(VALUE thread); VALUE thread_name_for(VALUE thread); int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, VALUE *buff, int *lines, bool* is_ruby_frame); +// Returns true if the current thread belongs to the main Ractor or if Ruby has no Ractor support +bool ddtrace_rb_ractor_main_p(void); // Ruby 3.0 finally added support for showing CFUNC frames (frames for methods written using native code) // in stack traces gathered via `rb_profile_frames` (https://github.com/ruby/ruby/pull/3299). diff --git a/ext/ddtrace_profiling_native_extension/profiling.c b/ext/ddtrace_profiling_native_extension/profiling.c index 05341241614..7c89fe30efb 100644 --- a/ext/ddtrace_profiling_native_extension/profiling.c +++ b/ext/ddtrace_profiling_native_extension/profiling.c @@ -2,6 +2,7 @@ #include "clock_id.h" #include "helpers.h" +#include "private_vm_api_access.h" // Each class/module here is implemented in their separate file void collectors_cpu_and_wall_time_init(VALUE profiling_module); @@ -11,6 +12,7 @@ void http_transport_init(VALUE profiling_module); void stack_recorder_init(VALUE profiling_module); static VALUE native_working_p(VALUE self); +static VALUE _native_ddtrace_rb_ractor_main_p(DDTRACE_UNUSED VALUE _self); void DDTRACE_EXPORT Init_ddtrace_profiling_native_extension(void) { VALUE datadog_module = rb_define_module("Datadog"); @@ -27,6 +29,10 @@ void DDTRACE_EXPORT Init_ddtrace_profiling_native_extension(void) { collectors_stack_init(profiling_module); http_transport_init(profiling_module); stack_recorder_init(profiling_module); + + // Hosts methods used for testing the native code using RSpec + VALUE testing_module = rb_define_module_under(native_extension_module, "Testing"); + rb_define_singleton_method(testing_module, "_native_ddtrace_rb_ractor_main_p", _native_ddtrace_rb_ractor_main_p, 0); } static VALUE native_working_p(DDTRACE_UNUSED VALUE _self) { @@ -34,3 +40,7 @@ static VALUE native_working_p(DDTRACE_UNUSED VALUE _self) { return Qtrue; } + +static VALUE _native_ddtrace_rb_ractor_main_p(DDTRACE_UNUSED VALUE _self) { + return ddtrace_rb_ractor_main_p() ? Qtrue : Qfalse; +} diff --git a/spec/datadog/profiling/native_extension_spec.rb b/spec/datadog/profiling/native_extension_spec.rb index 35dc620cc98..04b0579f09c 100644 --- a/spec/datadog/profiling/native_extension_spec.rb +++ b/spec/datadog/profiling/native_extension_spec.rb @@ -1,4 +1,4 @@ -# typed: false +# typed: ignore require 'datadog/profiling/spec_helper' @@ -160,4 +160,41 @@ def wait_for_thread_to_die end end end + + describe 'ddtrace_rb_ractor_main_p' do + subject(:ddtrace_rb_ractor_main_p) { Datadog::Profiling::NativeExtension::Testing._native_ddtrace_rb_ractor_main_p } + + context 'when Ruby has no support for Ractors' do + before { skip 'Behavior does not apply to current Ruby version' if RUBY_VERSION >= '3' } + + it { is_expected.to be true } + end + + context 'when Ruby has support for Ractors' do + before { skip 'Behavior does not apply to current Ruby version' if RUBY_VERSION < '3' } + + context 'on the main Ractor' do + it { is_expected.to be true } + end + + context 'on a background Ractor' do + # @ivoanjo: When we initially added this test, our test suite kept deadlocking in CI in a later test (not on + # this one). + # + # It turns out that Ruby 3.0 Ractors seem to have some bug that even running `Ractor.new { 'hello' }.take` will + # cause a later spec to fail, usually with a (native C) stack with `gc_finalize_deferred`. + # + # I was able to see this even on both Linux with 3.0.3 and macOS with 3.0.4. Thus, I decided to skip this + # spec on Ruby 3.0. We can always run it manually if we change something around this helper; and we have + # coverage on 3.1+ anyway. + before { skip 'Ruby 3.0 Ractors are too buggy to run this spec' if RUBY_VERSION.start_with?('3.0.') } + + subject(:ddtrace_rb_ractor_main_p) do + Ractor.new { Datadog::Profiling::NativeExtension::Testing._native_ddtrace_rb_ractor_main_p }.take + end + + it { is_expected.to be false } + end + end + end end