datadog 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -1
- data/ext/datadog_profiling_native_extension/clock_id.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +64 -54
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +1 -1
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +16 -16
- data/ext/datadog_profiling_native_extension/collectors_stack.c +7 -7
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +219 -122
- data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
- data/ext/datadog_profiling_native_extension/http_transport.c +4 -4
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +3 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -1
- data/ext/datadog_profiling_native_extension/profiling.c +10 -8
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +8 -8
- data/ext/datadog_profiling_native_extension/stack_recorder.c +54 -54
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
- data/ext/datadog_profiling_native_extension/time_helpers.h +1 -1
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +47 -0
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +31 -0
- data/ext/libdatadog_api/crashtracker.c +3 -0
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +355 -157
- data/lib/datadog/appsec/assets/waf_rules/strict.json +62 -32
- data/lib/datadog/appsec/context.rb +54 -0
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +7 -7
- data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +6 -6
- data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +4 -4
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +19 -28
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +5 -5
- data/lib/datadog/appsec/contrib/rack/gateway/response.rb +3 -3
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +64 -96
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +10 -10
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +5 -5
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +6 -6
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +10 -11
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -49
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +21 -32
- data/lib/datadog/appsec/contrib/rails/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +6 -6
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +41 -63
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +2 -2
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +5 -5
- data/lib/datadog/appsec/event.rb +6 -6
- data/lib/datadog/appsec/ext.rb +3 -1
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +22 -32
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +5 -5
- data/lib/datadog/appsec/processor/rule_loader.rb +0 -3
- data/lib/datadog/appsec.rb +3 -3
- data/lib/datadog/auto_instrument.rb +3 -0
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +39 -11
- data/lib/datadog/core/configuration/components.rb +4 -2
- data/lib/datadog/core/configuration.rb +1 -1
- data/lib/datadog/{tracing → core}/contrib/rails/utils.rb +1 -3
- data/lib/datadog/core/crashtracking/component.rb +1 -3
- data/lib/datadog/core/telemetry/event.rb +87 -3
- data/lib/datadog/core/telemetry/logging.rb +2 -2
- data/lib/datadog/core/telemetry/metric.rb +22 -0
- data/lib/datadog/core/telemetry/worker.rb +33 -0
- data/lib/datadog/di/base.rb +115 -0
- data/lib/datadog/di/code_tracker.rb +7 -4
- data/lib/datadog/di/component.rb +17 -11
- data/lib/datadog/di/configuration/settings.rb +11 -1
- data/lib/datadog/di/contrib/railtie.rb +15 -0
- data/lib/datadog/di/contrib.rb +26 -0
- data/lib/datadog/di/error.rb +5 -0
- data/lib/datadog/di/instrumenter.rb +39 -18
- data/lib/datadog/di/{init.rb → preload.rb} +2 -4
- data/lib/datadog/di/probe_manager.rb +4 -4
- data/lib/datadog/di/probe_notification_builder.rb +16 -2
- data/lib/datadog/di/probe_notifier_worker.rb +5 -6
- data/lib/datadog/di/remote.rb +4 -4
- data/lib/datadog/di/transport.rb +2 -4
- data/lib/datadog/di.rb +5 -108
- data/lib/datadog/kit/appsec/events.rb +3 -3
- data/lib/datadog/kit/identity.rb +4 -4
- data/lib/datadog/profiling/component.rb +55 -53
- data/lib/datadog/profiling/http_transport.rb +1 -26
- data/lib/datadog/tracing/contrib/action_cable/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/action_mailer/integration.rb +6 -2
- data/lib/datadog/tracing/contrib/action_pack/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/action_view/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/active_job/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/active_record/integration.rb +6 -2
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +3 -1
- data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +3 -1
- data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +10 -0
- data/lib/datadog/tracing/contrib/active_support/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/auto_instrument.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/httprb/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/kafka/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/mongodb/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/opensearch/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/presto/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/rack/integration.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/framework.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/rest_client/integration.rb +3 -0
- data/lib/datadog/tracing/span.rb +12 -4
- data/lib/datadog/tracing/span_event.rb +123 -3
- data/lib/datadog/tracing/span_operation.rb +6 -0
- data/lib/datadog/tracing/transport/serializable_trace.rb +24 -6
- data/lib/datadog/version.rb +1 -1
- metadata +19 -10
- data/lib/datadog/appsec/reactive/operation.rb +0 -68
- data/lib/datadog/appsec/scope.rb +0 -58
- data/lib/datadog/core/crashtracking/agent_base_url.rb +0 -21
| @@ -9,6 +9,7 @@ | |
| 9 9 | 
             
            #include "private_vm_api_access.h"
         | 
| 10 10 | 
             
            #include "stack_recorder.h"
         | 
| 11 11 | 
             
            #include "time_helpers.h"
         | 
| 12 | 
            +
            #include "unsafe_api_calls_check.h"
         | 
| 12 13 |  | 
| 13 14 | 
             
            // Used to trigger sampling of threads, based on external "events", such as:
         | 
| 14 15 | 
             
            // * periodic timer for cpu-time and wall-time
         | 
| @@ -112,14 +113,14 @@ static uint32_t global_waiting_for_gvl_threshold_ns = MILLIS_AS_NS(10); | |
| 112 113 | 
             
            typedef enum { OTEL_CONTEXT_ENABLED_FALSE, OTEL_CONTEXT_ENABLED_ONLY, OTEL_CONTEXT_ENABLED_BOTH } otel_context_enabled;
         | 
| 113 114 |  | 
| 114 115 | 
             
            // Contains state for a single ThreadContext instance
         | 
| 115 | 
            -
            struct  | 
| 116 | 
            +
            typedef struct {
         | 
| 116 117 | 
             
              // Note: Places in this file that usually need to be changed when this struct is changed are tagged with
         | 
| 117 118 | 
             
              // "Update this when modifying state struct"
         | 
| 118 119 |  | 
| 119 120 | 
             
              // Required by Datadog::Profiling::Collectors::Stack as a scratch buffer during sampling
         | 
| 120 121 | 
             
              ddog_prof_Location *locations;
         | 
| 121 122 | 
             
              uint16_t max_frames;
         | 
| 122 | 
            -
              // Hashmap <Thread Object,  | 
| 123 | 
            +
              // Hashmap <Thread Object, per_thread_context>
         | 
| 123 124 | 
             
              st_table *hash_map_per_thread_context;
         | 
| 124 125 | 
             
              // Datadog::Profiling::StackRecorder instance
         | 
| 125 126 | 
             
              VALUE recorder_instance;
         | 
| @@ -162,10 +163,10 @@ struct thread_context_collector_state { | |
| 162 163 | 
             
                long wall_time_at_previous_gc_ns; // Will be INVALID_TIME unless there's accumulated time above
         | 
| 163 164 | 
             
                long wall_time_at_last_flushed_gc_event_ns; // Starts at 0 and then will always be valid
         | 
| 164 165 | 
             
              } gc_tracking;
         | 
| 165 | 
            -
            };
         | 
| 166 | 
            +
            } thread_context_collector_state;
         | 
| 166 167 |  | 
| 167 168 | 
             
            // Tracks per-thread state
         | 
| 168 | 
            -
            struct  | 
| 169 | 
            +
            typedef struct {
         | 
| 169 170 | 
             
              sampling_buffer *sampling_buffer;
         | 
| 170 171 | 
             
              char thread_id[THREAD_ID_LIMIT_CHARS];
         | 
| 171 172 | 
             
              ddog_CharSlice thread_id_char_slice;
         | 
| @@ -181,21 +182,21 @@ struct per_thread_context { | |
| 181 182 | 
             
                long cpu_time_at_start_ns;
         | 
| 182 183 | 
             
                long wall_time_at_start_ns;
         | 
| 183 184 | 
             
              } gc_tracking;
         | 
| 184 | 
            -
            };
         | 
| 185 | 
            +
            } per_thread_context;
         | 
| 185 186 |  | 
| 186 187 | 
             
            // Used to correlate profiles with traces
         | 
| 187 | 
            -
            struct  | 
| 188 | 
            +
            typedef struct {
         | 
| 188 189 | 
             
              bool valid;
         | 
| 189 190 | 
             
              uint64_t local_root_span_id;
         | 
| 190 191 | 
             
              uint64_t span_id;
         | 
| 191 192 | 
             
              VALUE trace_endpoint;
         | 
| 192 | 
            -
            };
         | 
| 193 | 
            +
            } trace_identifiers;
         | 
| 193 194 |  | 
| 194 | 
            -
            struct  | 
| 195 | 
            +
            typedef struct {
         | 
| 195 196 | 
             
              VALUE span;
         | 
| 196 197 | 
             
              VALUE span_id;
         | 
| 197 198 | 
             
              VALUE trace_id;
         | 
| 198 | 
            -
            };
         | 
| 199 | 
            +
            } otel_span;
         | 
| 199 200 |  | 
| 200 201 | 
             
            static void thread_context_collector_typed_data_mark(void *state_ptr);
         | 
| 201 202 | 
             
            static void thread_context_collector_typed_data_free(void *state_ptr);
         | 
| @@ -203,24 +204,24 @@ static int hash_map_per_thread_context_mark(st_data_t key_thread, st_data_t _val | |
| 203 204 | 
             
            static int hash_map_per_thread_context_free_values(st_data_t _thread, st_data_t value_per_thread_context, st_data_t _argument);
         | 
| 204 205 | 
             
            static VALUE _native_new(VALUE klass);
         | 
| 205 206 | 
             
            static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
         | 
| 206 | 
            -
            static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE profiler_overhead_stack_thread);
         | 
| 207 | 
            +
            static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE profiler_overhead_stack_thread, VALUE allow_exception);
         | 
| 207 208 | 
             
            static VALUE _native_on_gc_start(VALUE self, VALUE collector_instance);
         | 
| 208 209 | 
             
            static VALUE _native_on_gc_finish(VALUE self, VALUE collector_instance);
         | 
| 209 | 
            -
            static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state);
         | 
| 210 | 
            +
            static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state, VALUE allow_exception);
         | 
| 210 211 | 
             
            static void update_metrics_and_sample(
         | 
| 211 | 
            -
               | 
| 212 | 
            +
              thread_context_collector_state *state,
         | 
| 212 213 | 
             
              VALUE thread_being_sampled,
         | 
| 213 214 | 
             
              VALUE stack_from_thread,
         | 
| 214 | 
            -
               | 
| 215 | 
            +
              per_thread_context *thread_context,
         | 
| 215 216 | 
             
              sampling_buffer* sampling_buffer,
         | 
| 216 217 | 
             
              long current_cpu_time_ns,
         | 
| 217 218 | 
             
              long current_monotonic_wall_time_ns
         | 
| 218 219 | 
             
            );
         | 
| 219 220 | 
             
            static void trigger_sample_for_thread(
         | 
| 220 | 
            -
               | 
| 221 | 
            +
              thread_context_collector_state *state,
         | 
| 221 222 | 
             
              VALUE thread,
         | 
| 222 223 | 
             
              VALUE stack_from_thread,
         | 
| 223 | 
            -
               | 
| 224 | 
            +
              per_thread_context *thread_context,
         | 
| 224 225 | 
             
              sampling_buffer* sampling_buffer,
         | 
| 225 226 | 
             
              sample_values values,
         | 
| 226 227 | 
             
              long current_monotonic_wall_time_ns,
         | 
| @@ -230,37 +231,37 @@ static void trigger_sample_for_thread( | |
| 230 231 | 
             
              bool is_safe_to_allocate_objects
         | 
| 231 232 | 
             
            );
         | 
| 232 233 | 
             
            static VALUE _native_thread_list(VALUE self);
         | 
| 233 | 
            -
            static  | 
| 234 | 
            -
            static  | 
| 235 | 
            -
            static void initialize_context(VALUE thread,  | 
| 236 | 
            -
            static void free_context( | 
| 234 | 
            +
            static per_thread_context *get_or_create_context_for(VALUE thread, thread_context_collector_state *state);
         | 
| 235 | 
            +
            static per_thread_context *get_context_for(VALUE thread, thread_context_collector_state *state);
         | 
| 236 | 
            +
            static void initialize_context(VALUE thread, per_thread_context *thread_context, thread_context_collector_state *state);
         | 
| 237 | 
            +
            static void free_context(per_thread_context* thread_context);
         | 
| 237 238 | 
             
            static VALUE _native_inspect(VALUE self, VALUE collector_instance);
         | 
| 238 | 
            -
            static VALUE per_thread_context_st_table_as_ruby_hash( | 
| 239 | 
            +
            static VALUE per_thread_context_st_table_as_ruby_hash(thread_context_collector_state *state);
         | 
| 239 240 | 
             
            static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value_context, st_data_t result_hash);
         | 
| 240 | 
            -
            static VALUE stats_as_ruby_hash( | 
| 241 | 
            -
            static VALUE gc_tracking_as_ruby_hash( | 
| 242 | 
            -
            static void remove_context_for_dead_threads( | 
| 241 | 
            +
            static VALUE stats_as_ruby_hash(thread_context_collector_state *state);
         | 
| 242 | 
            +
            static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state);
         | 
| 243 | 
            +
            static void remove_context_for_dead_threads(thread_context_collector_state *state);
         | 
| 243 244 | 
             
            static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, st_data_t _argument);
         | 
| 244 245 | 
             
            static VALUE _native_per_thread_context(VALUE self, VALUE collector_instance);
         | 
| 245 246 | 
             
            static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, long gc_start_time_ns, bool is_wall_time);
         | 
| 246 | 
            -
            static long cpu_time_now_ns( | 
| 247 | 
            +
            static long cpu_time_now_ns(per_thread_context *thread_context);
         | 
| 247 248 | 
             
            static long thread_id_for(VALUE thread);
         | 
| 248 249 | 
             
            static VALUE _native_stats(VALUE self, VALUE collector_instance);
         | 
| 249 250 | 
             
            static VALUE _native_gc_tracking(VALUE self, VALUE collector_instance);
         | 
| 250 251 | 
             
            static void trace_identifiers_for(
         | 
| 251 | 
            -
               | 
| 252 | 
            +
              thread_context_collector_state *state,
         | 
| 252 253 | 
             
              VALUE thread,
         | 
| 253 | 
            -
               | 
| 254 | 
            +
              trace_identifiers *trace_identifiers_result,
         | 
| 254 255 | 
             
              bool is_safe_to_allocate_objects
         | 
| 255 256 | 
             
            );
         | 
| 256 257 | 
             
            static bool should_collect_resource(VALUE root_span);
         | 
| 257 258 | 
             
            static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
         | 
| 258 | 
            -
            static VALUE thread_list( | 
| 259 | 
            +
            static VALUE thread_list(thread_context_collector_state *state);
         | 
| 259 260 | 
             
            static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object);
         | 
| 260 261 | 
             
            static VALUE _native_new_empty_thread(VALUE self);
         | 
| 261 262 | 
             
            static ddog_CharSlice ruby_value_type_to_class_name(enum ruby_value_type type);
         | 
| 262 263 | 
             
            static void ddtrace_otel_trace_identifiers_for(
         | 
| 263 | 
            -
               | 
| 264 | 
            +
              thread_context_collector_state *state,
         | 
| 264 265 | 
             
              VALUE *active_trace,
         | 
| 265 266 | 
             
              VALUE *root_span,
         | 
| 266 267 | 
             
              VALUE *numeric_span_id,
         | 
| @@ -270,10 +271,10 @@ static void ddtrace_otel_trace_identifiers_for( | |
| 270 271 | 
             
            );
         | 
| 271 272 | 
             
            static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples);
         | 
| 272 273 | 
             
            static bool handle_gvl_waiting(
         | 
| 273 | 
            -
               | 
| 274 | 
            +
              thread_context_collector_state *state,
         | 
| 274 275 | 
             
              VALUE thread_being_sampled,
         | 
| 275 276 | 
             
              VALUE stack_from_thread,
         | 
| 276 | 
            -
               | 
| 277 | 
            +
              per_thread_context *thread_context,
         | 
| 277 278 | 
             
              sampling_buffer* sampling_buffer,
         | 
| 278 279 | 
             
              long current_cpu_time_ns
         | 
| 279 280 | 
             
            );
         | 
| @@ -283,13 +284,14 @@ static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread); | |
| 283 284 | 
             
            static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
         | 
| 284 285 | 
             
            static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE delta_ns);
         | 
| 285 286 | 
             
            static void otel_without_ddtrace_trace_identifiers_for(
         | 
| 286 | 
            -
               | 
| 287 | 
            +
              thread_context_collector_state *state,
         | 
| 287 288 | 
             
              VALUE thread,
         | 
| 288 | 
            -
               | 
| 289 | 
            +
              trace_identifiers *trace_identifiers_result,
         | 
| 289 290 | 
             
              bool is_safe_to_allocate_objects
         | 
| 290 291 | 
             
            );
         | 
| 291 | 
            -
            static  | 
| 292 | 
            +
            static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key);
         | 
| 292 293 | 
             
            static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
         | 
| 294 | 
            +
            static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
         | 
| 293 295 |  | 
| 294 296 | 
             
            void collectors_thread_context_init(VALUE profiling_module) {
         | 
| 295 297 | 
             
              VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
         | 
| @@ -310,11 +312,11 @@ void collectors_thread_context_init(VALUE profiling_module) { | |
| 310 312 | 
             
              rb_define_singleton_method(collectors_thread_context_class, "_native_initialize", _native_initialize, -1);
         | 
| 311 313 | 
             
              rb_define_singleton_method(collectors_thread_context_class, "_native_inspect", _native_inspect, 1);
         | 
| 312 314 | 
             
              rb_define_singleton_method(collectors_thread_context_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
         | 
| 313 | 
            -
              rb_define_singleton_method(testing_module, "_native_sample", _native_sample,  | 
| 315 | 
            +
              rb_define_singleton_method(testing_module, "_native_sample", _native_sample, 3);
         | 
| 314 316 | 
             
              rb_define_singleton_method(testing_module, "_native_sample_allocation", _native_sample_allocation, 3);
         | 
| 315 317 | 
             
              rb_define_singleton_method(testing_module, "_native_on_gc_start", _native_on_gc_start, 1);
         | 
| 316 318 | 
             
              rb_define_singleton_method(testing_module, "_native_on_gc_finish", _native_on_gc_finish, 1);
         | 
| 317 | 
            -
              rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc,  | 
| 319 | 
            +
              rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc, 3);
         | 
| 318 320 | 
             
              rb_define_singleton_method(testing_module, "_native_thread_list", _native_thread_list, 0);
         | 
| 319 321 | 
             
              rb_define_singleton_method(testing_module, "_native_per_thread_context", _native_per_thread_context, 1);
         | 
| 320 322 | 
             
              rb_define_singleton_method(testing_module, "_native_stats", _native_stats, 1);
         | 
| @@ -355,7 +357,7 @@ void collectors_thread_context_init(VALUE profiling_module) { | |
| 355 357 | 
             
              gc_profiling_init();
         | 
| 356 358 | 
             
            }
         | 
| 357 359 |  | 
| 358 | 
            -
            // This structure is used to define a Ruby object that stores a pointer to a  | 
| 360 | 
            +
            // This structure is used to define a Ruby object that stores a pointer to a thread_context_collector_state
         | 
| 359 361 | 
             
            // See also https://github.com/ruby/ruby/blob/master/doc/extension.rdoc for how this works
         | 
| 360 362 | 
             
            static const rb_data_type_t thread_context_collector_typed_data = {
         | 
| 361 363 | 
             
              .wrap_struct_name = "Datadog::Profiling::Collectors::ThreadContext",
         | 
| @@ -371,7 +373,7 @@ static const rb_data_type_t thread_context_collector_typed_data = { | |
| 371 373 | 
             
            // This function is called by the Ruby GC to give us a chance to mark any Ruby objects that we're holding on to,
         | 
| 372 374 | 
             
            // so that they don't get garbage collected
         | 
| 373 375 | 
             
            static void thread_context_collector_typed_data_mark(void *state_ptr) {
         | 
| 374 | 
            -
               | 
| 376 | 
            +
              thread_context_collector_state *state = (thread_context_collector_state *) state_ptr;
         | 
| 375 377 |  | 
| 376 378 | 
             
              // Update this when modifying state struct
         | 
| 377 379 | 
             
              rb_gc_mark(state->recorder_instance);
         | 
| @@ -382,7 +384,7 @@ static void thread_context_collector_typed_data_mark(void *state_ptr) { | |
| 382 384 | 
             
            }
         | 
| 383 385 |  | 
| 384 386 | 
             
            static void thread_context_collector_typed_data_free(void *state_ptr) {
         | 
| 385 | 
            -
               | 
| 387 | 
            +
              thread_context_collector_state *state = (thread_context_collector_state *) state_ptr;
         | 
| 386 388 |  | 
| 387 389 | 
             
              // Update this when modifying state struct
         | 
| 388 390 |  | 
| @@ -407,13 +409,13 @@ static int hash_map_per_thread_context_mark(st_data_t key_thread, DDTRACE_UNUSED | |
| 407 409 |  | 
| 408 410 | 
             
            // Used to clear each of the per_thread_contexts inside the hash_map_per_thread_context
         | 
| 409 411 | 
             
            static int hash_map_per_thread_context_free_values(DDTRACE_UNUSED st_data_t _thread, st_data_t value_per_thread_context, DDTRACE_UNUSED st_data_t _argument) {
         | 
| 410 | 
            -
               | 
| 412 | 
            +
              per_thread_context *thread_context = (per_thread_context*) value_per_thread_context;
         | 
| 411 413 | 
             
              free_context(thread_context);
         | 
| 412 414 | 
             
              return ST_CONTINUE;
         | 
| 413 415 | 
             
            }
         | 
| 414 416 |  | 
| 415 417 | 
             
            static VALUE _native_new(VALUE klass) {
         | 
| 416 | 
            -
               | 
| 418 | 
            +
              thread_context_collector_state *state = ruby_xcalloc(1, sizeof(thread_context_collector_state));
         | 
| 417 419 |  | 
| 418 420 | 
             
              // Note: Any exceptions raised from this note until the TypedData_Wrap_Struct call will lead to the state memory
         | 
| 419 421 | 
             
              // being leaked.
         | 
| @@ -469,8 +471,8 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel | |
| 469 471 | 
             
              ENFORCE_BOOLEAN(timeline_enabled);
         | 
| 470 472 | 
             
              ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
         | 
| 471 473 |  | 
| 472 | 
            -
               | 
| 473 | 
            -
              TypedData_Get_Struct(self_instance,  | 
| 474 | 
            +
              thread_context_collector_state *state;
         | 
| 475 | 
            +
              TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
         | 
| 474 476 |  | 
| 475 477 | 
             
              // Update this when modifying state struct
         | 
| 476 478 | 
             
              state->max_frames = sampling_buffer_check_max_frames(NUM2INT(max_frames));
         | 
| @@ -504,40 +506,63 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel | |
| 504 506 |  | 
| 505 507 | 
             
            // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
         | 
| 506 508 | 
             
            // It SHOULD NOT be used for other purposes.
         | 
| 507 | 
            -
            static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE profiler_overhead_stack_thread) {
         | 
| 509 | 
            +
            static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE profiler_overhead_stack_thread, VALUE allow_exception) {
         | 
| 510 | 
            +
              ENFORCE_BOOLEAN(allow_exception);
         | 
| 511 | 
            +
             | 
| 508 512 | 
             
              if (!is_thread_alive(profiler_overhead_stack_thread)) rb_raise(rb_eArgError, "Unexpected: profiler_overhead_stack_thread is not alive");
         | 
| 509 513 |  | 
| 514 | 
            +
              if (allow_exception == Qfalse) debug_enter_unsafe_context();
         | 
| 515 | 
            +
             | 
| 510 516 | 
             
              thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(RAISE_ON_FAILURE), profiler_overhead_stack_thread);
         | 
| 517 | 
            +
             | 
| 518 | 
            +
              if (allow_exception == Qfalse) debug_leave_unsafe_context();
         | 
| 519 | 
            +
             | 
| 511 520 | 
             
              return Qtrue;
         | 
| 512 521 | 
             
            }
         | 
| 513 522 |  | 
| 514 523 | 
             
            // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
         | 
| 515 524 | 
             
            // It SHOULD NOT be used for other purposes.
         | 
| 516 525 | 
             
            static VALUE _native_on_gc_start(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
         | 
| 526 | 
            +
              debug_enter_unsafe_context();
         | 
| 527 | 
            +
             | 
| 517 528 | 
             
              thread_context_collector_on_gc_start(collector_instance);
         | 
| 529 | 
            +
             | 
| 530 | 
            +
              debug_leave_unsafe_context();
         | 
| 531 | 
            +
             | 
| 518 532 | 
             
              return Qtrue;
         | 
| 519 533 | 
             
            }
         | 
| 520 534 |  | 
| 521 535 | 
             
            // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
         | 
| 522 536 | 
             
            // It SHOULD NOT be used for other purposes.
         | 
| 523 537 | 
             
            static VALUE _native_on_gc_finish(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
         | 
| 538 | 
            +
              debug_enter_unsafe_context();
         | 
| 539 | 
            +
             | 
| 524 540 | 
             
              (void) !thread_context_collector_on_gc_finish(collector_instance);
         | 
| 541 | 
            +
             | 
| 542 | 
            +
              debug_leave_unsafe_context();
         | 
| 543 | 
            +
             | 
| 525 544 | 
             
              return Qtrue;
         | 
| 526 545 | 
             
            }
         | 
| 527 546 |  | 
| 528 547 | 
             
            // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
         | 
| 529 548 | 
             
            // It SHOULD NOT be used for other purposes.
         | 
| 530 | 
            -
            static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state) {
         | 
| 549 | 
            +
            static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state, VALUE allow_exception) {
         | 
| 531 550 | 
             
              ENFORCE_BOOLEAN(reset_monotonic_to_system_state);
         | 
| 551 | 
            +
              ENFORCE_BOOLEAN(allow_exception);
         | 
| 532 552 |  | 
| 533 | 
            -
               | 
| 534 | 
            -
              TypedData_Get_Struct(collector_instance,  | 
| 553 | 
            +
              thread_context_collector_state *state;
         | 
| 554 | 
            +
              TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
         | 
| 535 555 |  | 
| 536 556 | 
             
              if (reset_monotonic_to_system_state == Qtrue) {
         | 
| 537 557 | 
             
                state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
         | 
| 538 558 | 
             
              }
         | 
| 539 559 |  | 
| 560 | 
            +
              if (allow_exception == Qfalse) debug_enter_unsafe_context();
         | 
| 561 | 
            +
             | 
| 540 562 | 
             
              thread_context_collector_sample_after_gc(collector_instance);
         | 
| 563 | 
            +
             | 
| 564 | 
            +
              if (allow_exception == Qfalse) debug_leave_unsafe_context();
         | 
| 565 | 
            +
             | 
| 541 566 | 
             
              return Qtrue;
         | 
| 542 567 | 
             
            }
         | 
| 543 568 |  | 
| @@ -552,11 +577,11 @@ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_ | |
| 552 577 | 
             
            // The `profiler_overhead_stack_thread` is used to attribute the profiler overhead to a stack borrowed from a different thread
         | 
| 553 578 | 
             
            // (belonging to ddtrace), so that the overhead is visible in the profile rather than blamed on user code.
         | 
| 554 579 | 
             
            void thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns, VALUE profiler_overhead_stack_thread) {
         | 
| 555 | 
            -
               | 
| 556 | 
            -
              TypedData_Get_Struct(self_instance,  | 
| 580 | 
            +
              thread_context_collector_state *state;
         | 
| 581 | 
            +
              TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
         | 
| 557 582 |  | 
| 558 583 | 
             
              VALUE current_thread = rb_thread_current();
         | 
| 559 | 
            -
               | 
| 584 | 
            +
              per_thread_context *current_thread_context = get_or_create_context_for(current_thread, state);
         | 
| 560 585 | 
             
              long cpu_time_at_sample_start_for_current_thread = cpu_time_now_ns(current_thread_context);
         | 
| 561 586 |  | 
| 562 587 | 
             
              VALUE threads = thread_list(state);
         | 
| @@ -564,7 +589,7 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic | |
| 564 589 | 
             
              const long thread_count = RARRAY_LEN(threads);
         | 
| 565 590 | 
             
              for (long i = 0; i < thread_count; i++) {
         | 
| 566 591 | 
             
                VALUE thread = RARRAY_AREF(threads, i);
         | 
| 567 | 
            -
                 | 
| 592 | 
            +
                per_thread_context *thread_context = get_or_create_context_for(thread, state);
         | 
| 568 593 |  | 
| 569 594 | 
             
                // We account for cpu-time for the current thread in a different way -- we use the cpu-time at sampling start, to avoid
         | 
| 570 595 | 
             
                // blaming the time the profiler took on whatever's running on the thread right now
         | 
| @@ -600,10 +625,10 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic | |
| 600 625 | 
             
            }
         | 
| 601 626 |  | 
| 602 627 | 
             
            static void update_metrics_and_sample(
         | 
| 603 | 
            -
               | 
| 628 | 
            +
              thread_context_collector_state *state,
         | 
| 604 629 | 
             
              VALUE thread_being_sampled,
         | 
| 605 630 | 
             
              VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
         | 
| 606 | 
            -
               | 
| 631 | 
            +
              per_thread_context *thread_context,
         | 
| 607 632 | 
             
              sampling_buffer* sampling_buffer,
         | 
| 608 633 | 
             
              long current_cpu_time_ns,
         | 
| 609 634 | 
             
              long current_monotonic_wall_time_ns
         | 
| @@ -671,12 +696,12 @@ static void update_metrics_and_sample( | |
| 671 696 | 
             
            // Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
         | 
| 672 697 | 
             
            // Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
         | 
| 673 698 | 
             
            void thread_context_collector_on_gc_start(VALUE self_instance) {
         | 
| 674 | 
            -
               | 
| 699 | 
            +
              thread_context_collector_state *state;
         | 
| 675 700 | 
             
              if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return;
         | 
| 676 701 | 
             
              // This should never fail the the above check passes
         | 
| 677 | 
            -
              TypedData_Get_Struct(self_instance,  | 
| 702 | 
            +
              TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
         | 
| 678 703 |  | 
| 679 | 
            -
               | 
| 704 | 
            +
              per_thread_context *thread_context = get_context_for(rb_thread_current(), state);
         | 
| 680 705 |  | 
| 681 706 | 
             
              // If there was no previously-existing context for this thread, we won't allocate one (see safety). For now we just drop
         | 
| 682 707 | 
             
              // the GC sample, under the assumption that "a thread that is so new that we never sampled it even once before it triggers
         | 
| @@ -704,12 +729,12 @@ void thread_context_collector_on_gc_start(VALUE self_instance) { | |
| 704 729 | 
             
            // Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
         | 
| 705 730 | 
             
            __attribute__((warn_unused_result))
         | 
| 706 731 | 
             
            bool thread_context_collector_on_gc_finish(VALUE self_instance) {
         | 
| 707 | 
            -
               | 
| 732 | 
            +
              thread_context_collector_state *state;
         | 
| 708 733 | 
             
              if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
         | 
| 709 734 | 
             
              // This should never fail the the above check passes
         | 
| 710 | 
            -
              TypedData_Get_Struct(self_instance,  | 
| 735 | 
            +
              TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
         | 
| 711 736 |  | 
| 712 | 
            -
               | 
| 737 | 
            +
              per_thread_context *thread_context = get_context_for(rb_thread_current(), state);
         | 
| 713 738 |  | 
| 714 739 | 
             
              // If there was no previously-existing context for this thread, we won't allocate one (see safety). We keep a metric for
         | 
| 715 740 | 
             
              // how often this happens -- see on_gc_start.
         | 
| @@ -782,8 +807,8 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) { | |
| 782 807 | 
             
            // Assumption 3: Unlike `on_gc_start` and `on_gc_finish`, this method is allowed to allocate memory as needed.
         | 
| 783 808 | 
             
            // Assumption 4: This function is called from the main Ractor (if Ruby has support for Ractors).
         | 
| 784 809 | 
             
            VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
         | 
| 785 | 
            -
               | 
| 786 | 
            -
              TypedData_Get_Struct(self_instance,  | 
| 810 | 
            +
              thread_context_collector_state *state;
         | 
| 811 | 
            +
              TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
         | 
| 787 812 |  | 
| 788 813 | 
             
              if (state->gc_tracking.wall_time_at_previous_gc_ns == INVALID_TIME) {
         | 
| 789 814 | 
             
                rb_raise(rb_eRuntimeError, "BUG: Unexpected call to sample_after_gc without valid GC information available");
         | 
| @@ -832,10 +857,10 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) { | |
| 832 857 | 
             
            }
         | 
| 833 858 |  | 
| 834 859 | 
             
            static void trigger_sample_for_thread(
         | 
| 835 | 
            -
               | 
| 860 | 
            +
              thread_context_collector_state *state,
         | 
| 836 861 | 
             
              VALUE thread,
         | 
| 837 862 | 
             
              VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
         | 
| 838 | 
            -
               | 
| 863 | 
            +
              per_thread_context *thread_context,
         | 
| 839 864 | 
             
              sampling_buffer* sampling_buffer,
         | 
| 840 865 | 
             
              sample_values values,
         | 
| 841 866 | 
             
              long current_monotonic_wall_time_ns,
         | 
| @@ -883,7 +908,7 @@ static void trigger_sample_for_thread( | |
| 883 908 | 
             
                };
         | 
| 884 909 | 
             
              }
         | 
| 885 910 |  | 
| 886 | 
            -
               | 
| 911 | 
            +
              trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
         | 
| 887 912 | 
             
              trace_identifiers_for(state, thread, &trace_identifiers_result, is_safe_to_allocate_objects);
         | 
| 888 913 |  | 
| 889 914 | 
             
              if (!trace_identifiers_result.valid && state->otel_context_enabled != OTEL_CONTEXT_ENABLED_FALSE) {
         | 
| @@ -982,18 +1007,24 @@ static void trigger_sample_for_thread( | |
| 982 1007 | 
             
            // It SHOULD NOT be used for other purposes.
         | 
| 983 1008 | 
             
            static VALUE _native_thread_list(DDTRACE_UNUSED VALUE _self) {
         | 
| 984 1009 | 
             
              VALUE result = rb_ary_new();
         | 
| 1010 | 
            +
             | 
| 1011 | 
            +
              debug_enter_unsafe_context();
         | 
| 1012 | 
            +
             | 
| 985 1013 | 
             
              ddtrace_thread_list(result);
         | 
| 1014 | 
            +
             | 
| 1015 | 
            +
              debug_leave_unsafe_context();
         | 
| 1016 | 
            +
             | 
| 986 1017 | 
             
              return result;
         | 
| 987 1018 | 
             
            }
         | 
| 988 1019 |  | 
| 989 | 
            -
            static  | 
| 990 | 
            -
               | 
| 1020 | 
            +
            static per_thread_context *get_or_create_context_for(VALUE thread, thread_context_collector_state *state) {
         | 
| 1021 | 
            +
              per_thread_context* thread_context = NULL;
         | 
| 991 1022 | 
             
              st_data_t value_context = 0;
         | 
| 992 1023 |  | 
| 993 1024 | 
             
              if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
         | 
| 994 | 
            -
                thread_context = ( | 
| 1025 | 
            +
                thread_context = (per_thread_context*) value_context;
         | 
| 995 1026 | 
             
              } else {
         | 
| 996 | 
            -
                thread_context = ruby_xcalloc(1, sizeof( | 
| 1027 | 
            +
                thread_context = ruby_xcalloc(1, sizeof(per_thread_context));
         | 
| 997 1028 | 
             
                initialize_context(thread, thread_context, state);
         | 
| 998 1029 | 
             
                st_insert(state->hash_map_per_thread_context, (st_data_t) thread, (st_data_t) thread_context);
         | 
| 999 1030 | 
             
              }
         | 
| @@ -1001,12 +1032,12 @@ static struct per_thread_context *get_or_create_context_for(VALUE thread, struct | |
| 1001 1032 | 
             
              return thread_context;
         | 
| 1002 1033 | 
             
            }
         | 
| 1003 1034 |  | 
| 1004 | 
            -
            static  | 
| 1005 | 
            -
               | 
| 1035 | 
            +
            static per_thread_context *get_context_for(VALUE thread, thread_context_collector_state *state) {
         | 
| 1036 | 
            +
              per_thread_context* thread_context = NULL;
         | 
| 1006 1037 | 
             
              st_data_t value_context = 0;
         | 
| 1007 1038 |  | 
| 1008 1039 | 
             
              if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
         | 
| 1009 | 
            -
                thread_context = ( | 
| 1040 | 
            +
                thread_context = (per_thread_context*) value_context;
         | 
| 1010 1041 | 
             
              }
         | 
| 1011 1042 |  | 
| 1012 1043 | 
             
              return thread_context;
         | 
| @@ -1033,7 +1064,7 @@ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) { | |
| 1033 1064 | 
             
              return strncmp(invoke_file + invoke_file_len - logging_gem_path_len, LOGGING_GEM_PATH, logging_gem_path_len) == 0;
         | 
| 1034 1065 | 
             
            }
         | 
| 1035 1066 |  | 
| 1036 | 
            -
            static void initialize_context(VALUE thread,  | 
| 1067 | 
            +
            static void initialize_context(VALUE thread, per_thread_context *thread_context, thread_context_collector_state *state) {
         | 
| 1037 1068 | 
             
              thread_context->sampling_buffer = sampling_buffer_new(state->max_frames, state->locations);
         | 
| 1038 1069 |  | 
| 1039 1070 | 
             
              snprintf(thread_context->thread_id, THREAD_ID_LIMIT_CHARS, "%"PRIu64" (%lu)", native_thread_id_for(thread), (unsigned long) thread_id_for(thread));
         | 
| @@ -1090,14 +1121,14 @@ static void initialize_context(VALUE thread, struct per_thread_context *thread_c | |
| 1090 1121 | 
             
              #endif
         | 
| 1091 1122 | 
             
            }
         | 
| 1092 1123 |  | 
| 1093 | 
            -
            static void free_context( | 
| 1124 | 
            +
            static void free_context(per_thread_context* thread_context) {
         | 
| 1094 1125 | 
             
              sampling_buffer_free(thread_context->sampling_buffer);
         | 
| 1095 1126 | 
             
              ruby_xfree(thread_context);
         | 
| 1096 1127 | 
             
            }
         | 
| 1097 1128 |  | 
| 1098 1129 | 
             
            static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
         | 
| 1099 | 
            -
               | 
| 1100 | 
            -
              TypedData_Get_Struct(collector_instance,  | 
| 1130 | 
            +
              thread_context_collector_state *state;
         | 
| 1131 | 
            +
              TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
         | 
| 1101 1132 |  | 
| 1102 1133 | 
             
              VALUE result = rb_str_new2(" (native state)");
         | 
| 1103 1134 |  | 
| @@ -1125,7 +1156,7 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc | |
| 1125 1156 | 
             
              return result;
         | 
| 1126 1157 | 
             
            }
         | 
| 1127 1158 |  | 
| 1128 | 
            -
            static VALUE per_thread_context_st_table_as_ruby_hash( | 
| 1159 | 
            +
            static VALUE per_thread_context_st_table_as_ruby_hash(thread_context_collector_state *state) {
         | 
| 1129 1160 | 
             
              VALUE result = rb_hash_new();
         | 
| 1130 1161 | 
             
              st_foreach(state->hash_map_per_thread_context, per_thread_context_as_ruby_hash, result);
         | 
| 1131 1162 | 
             
              return result;
         | 
| @@ -1133,7 +1164,7 @@ static VALUE per_thread_context_st_table_as_ruby_hash(struct thread_context_coll | |
| 1133 1164 |  | 
| 1134 1165 | 
             
            static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value_context, st_data_t result_hash) {
         | 
| 1135 1166 | 
             
              VALUE thread = (VALUE) key_thread;
         | 
| 1136 | 
            -
               | 
| 1167 | 
            +
              per_thread_context *thread_context = (per_thread_context*) value_context;
         | 
| 1137 1168 | 
             
              VALUE result = (VALUE) result_hash;
         | 
| 1138 1169 | 
             
              VALUE context_as_hash = rb_hash_new();
         | 
| 1139 1170 | 
             
              rb_hash_aset(result, thread, context_as_hash);
         | 
| @@ -1158,7 +1189,7 @@ static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value | |
| 1158 1189 | 
             
              return ST_CONTINUE;
         | 
| 1159 1190 | 
             
            }
         | 
| 1160 1191 |  | 
| 1161 | 
            -
            static VALUE stats_as_ruby_hash( | 
| 1192 | 
            +
            static VALUE stats_as_ruby_hash(thread_context_collector_state *state) {
         | 
| 1162 1193 | 
             
              // Update this when modifying state struct (stats inner struct)
         | 
| 1163 1194 | 
             
              VALUE stats_as_hash = rb_hash_new();
         | 
| 1164 1195 | 
             
              VALUE arguments[] = {
         | 
| @@ -1169,7 +1200,7 @@ static VALUE stats_as_ruby_hash(struct thread_context_collector_state *state) { | |
| 1169 1200 | 
             
              return stats_as_hash;
         | 
| 1170 1201 | 
             
            }
         | 
| 1171 1202 |  | 
| 1172 | 
            -
            static VALUE gc_tracking_as_ruby_hash( | 
| 1203 | 
            +
            static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state) {
         | 
| 1173 1204 | 
             
              // Update this when modifying state struct (gc_tracking inner struct)
         | 
| 1174 1205 | 
             
              VALUE result = rb_hash_new();
         | 
| 1175 1206 | 
             
              VALUE arguments[] = {
         | 
| @@ -1182,13 +1213,13 @@ static VALUE gc_tracking_as_ruby_hash(struct thread_context_collector_state *sta | |
| 1182 1213 | 
             
              return result;
         | 
| 1183 1214 | 
             
            }
         | 
| 1184 1215 |  | 
| 1185 | 
            -
            static void remove_context_for_dead_threads( | 
| 1216 | 
            +
            static void remove_context_for_dead_threads(thread_context_collector_state *state) {
         | 
| 1186 1217 | 
             
              st_foreach(state->hash_map_per_thread_context, remove_if_dead_thread, 0 /* unused */);
         | 
| 1187 1218 | 
             
            }
         | 
| 1188 1219 |  | 
| 1189 1220 | 
             
            static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, DDTRACE_UNUSED st_data_t _argument) {
         | 
| 1190 1221 | 
             
              VALUE thread = (VALUE) key_thread;
         | 
| 1191 | 
            -
               | 
| 1222 | 
            +
              per_thread_context* thread_context = (per_thread_context*) value_context;
         | 
| 1192 1223 |  | 
| 1193 1224 | 
             
              if (is_thread_alive(thread)) return ST_CONTINUE;
         | 
| 1194 1225 |  | 
| @@ -1201,8 +1232,8 @@ static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, | |
| 1201 1232 | 
             
            //
         | 
| 1202 1233 | 
             
            // Returns the whole contents of the per_thread_context structs being tracked.
         | 
| 1203 1234 | 
             
            static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
         | 
| 1204 | 
            -
               | 
| 1205 | 
            -
              TypedData_Get_Struct(collector_instance,  | 
| 1235 | 
            +
              thread_context_collector_state *state;
         | 
| 1236 | 
            +
              TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
         | 
| 1206 1237 |  | 
| 1207 1238 | 
             
              return per_thread_context_st_table_as_ruby_hash(state);
         | 
| 1208 1239 | 
             
            }
         | 
| @@ -1247,7 +1278,7 @@ static long update_time_since_previous_sample(long *time_at_previous_sample_ns, | |
| 1247 1278 | 
             
            }
         | 
| 1248 1279 |  | 
| 1249 1280 | 
             
            // Safety: This function is assumed never to raise exceptions by callers
         | 
| 1250 | 
            -
            static long cpu_time_now_ns( | 
| 1281 | 
            +
            static long cpu_time_now_ns(per_thread_context *thread_context) {
         | 
| 1251 1282 | 
             
              thread_cpu_time cpu_time = thread_cpu_time_for(thread_context->thread_cpu_time_id);
         | 
| 1252 1283 |  | 
| 1253 1284 | 
             
              if (!cpu_time.valid) {
         | 
| @@ -1285,8 +1316,8 @@ VALUE enforce_thread_context_collector_instance(VALUE object) { | |
| 1285 1316 | 
             
            // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
         | 
| 1286 1317 | 
             
            // It SHOULD NOT be used for other purposes.
         | 
| 1287 1318 | 
             
            static VALUE _native_stats(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
         | 
| 1288 | 
            -
               | 
| 1289 | 
            -
              TypedData_Get_Struct(collector_instance,  | 
| 1319 | 
            +
              thread_context_collector_state *state;
         | 
| 1320 | 
            +
              TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
         | 
| 1290 1321 |  | 
| 1291 1322 | 
             
              return stats_as_ruby_hash(state);
         | 
| 1292 1323 | 
             
            }
         | 
| @@ -1294,17 +1325,17 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) | |
| 1294 1325 | 
             
            // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
         | 
| 1295 1326 | 
             
            // It SHOULD NOT be used for other purposes.
         | 
| 1296 1327 | 
             
            static VALUE _native_gc_tracking(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
         | 
| 1297 | 
            -
               | 
| 1298 | 
            -
              TypedData_Get_Struct(collector_instance,  | 
| 1328 | 
            +
              thread_context_collector_state *state;
         | 
| 1329 | 
            +
              TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
         | 
| 1299 1330 |  | 
| 1300 1331 | 
             
              return gc_tracking_as_ruby_hash(state);
         | 
| 1301 1332 | 
             
            }
         | 
| 1302 1333 |  | 
| 1303 1334 | 
             
            // Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
         | 
| 1304 1335 | 
             
            static void trace_identifiers_for(
         | 
| 1305 | 
            -
               | 
| 1336 | 
            +
              thread_context_collector_state *state,
         | 
| 1306 1337 | 
             
              VALUE thread,
         | 
| 1307 | 
            -
               | 
| 1338 | 
            +
              trace_identifiers *trace_identifiers_result,
         | 
| 1308 1339 | 
             
              bool is_safe_to_allocate_objects
         | 
| 1309 1340 | 
             
            ) {
         | 
| 1310 1341 | 
             
              if (state->otel_context_enabled == OTEL_CONTEXT_ENABLED_ONLY) return;
         | 
| @@ -1384,8 +1415,8 @@ static bool should_collect_resource(VALUE root_span) { | |
| 1384 1415 | 
             
            // Assumption: This method gets called BEFORE restarting profiling -- e.g. there are no components attempting to
         | 
| 1385 1416 | 
             
            // trigger samples at the same time.
         | 
| 1386 1417 | 
             
            static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
         | 
| 1387 | 
            -
               | 
| 1388 | 
            -
              TypedData_Get_Struct(collector_instance,  | 
| 1418 | 
            +
              thread_context_collector_state *state;
         | 
| 1419 | 
            +
              TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
         | 
| 1389 1420 |  | 
| 1390 1421 | 
             
              // Release all context memory before clearing the existing context
         | 
| 1391 1422 | 
             
              st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
         | 
| @@ -1399,7 +1430,7 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector | |
| 1399 1430 | 
             
              return Qtrue;
         | 
| 1400 1431 | 
             
            }
         | 
| 1401 1432 |  | 
| 1402 | 
            -
            static VALUE thread_list( | 
| 1433 | 
            +
            static VALUE thread_list(thread_context_collector_state *state) {
         | 
| 1403 1434 | 
             
              VALUE result = state->thread_list_buffer;
         | 
| 1404 1435 | 
             
              rb_ary_clear(result);
         | 
| 1405 1436 | 
             
              ddtrace_thread_list(result);
         | 
| @@ -1407,8 +1438,8 @@ static VALUE thread_list(struct thread_context_collector_state *state) { | |
| 1407 1438 | 
             
            }
         | 
| 1408 1439 |  | 
| 1409 1440 | 
             
            void thread_context_collector_sample_allocation(VALUE self_instance, unsigned int sample_weight, VALUE new_object) {
         | 
| 1410 | 
            -
               | 
| 1411 | 
            -
              TypedData_Get_Struct(self_instance,  | 
| 1441 | 
            +
              thread_context_collector_state *state;
         | 
| 1442 | 
            +
              TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
         | 
| 1412 1443 |  | 
| 1413 1444 | 
             
              VALUE current_thread = rb_thread_current();
         | 
| 1414 1445 |  | 
| @@ -1481,7 +1512,7 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in | |
| 1481 1512 |  | 
| 1482 1513 | 
             
              track_object(state->recorder_instance, new_object, sample_weight, optional_class_name);
         | 
| 1483 1514 |  | 
| 1484 | 
            -
               | 
| 1515 | 
            +
              per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
         | 
| 1485 1516 |  | 
| 1486 1517 | 
             
              trigger_sample_for_thread(
         | 
| 1487 1518 | 
             
                state,
         | 
| @@ -1501,7 +1532,12 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in | |
| 1501 1532 | 
             
            // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
         | 
| 1502 1533 | 
             
            // It SHOULD NOT be used for other purposes.
         | 
| 1503 1534 | 
             
            static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object) {
         | 
| 1535 | 
            +
              debug_enter_unsafe_context();
         | 
| 1536 | 
            +
             | 
| 1504 1537 | 
             
              thread_context_collector_sample_allocation(collector_instance, NUM2UINT(sample_weight), new_object);
         | 
| 1538 | 
            +
             | 
| 1539 | 
            +
              debug_leave_unsafe_context();
         | 
| 1540 | 
            +
             | 
| 1505 1541 | 
             
              return Qtrue;
         | 
| 1506 1542 | 
             
            }
         | 
| 1507 1543 |  | 
| @@ -1549,7 +1585,7 @@ static VALUE read_otel_current_span_key_const(DDTRACE_UNUSED VALUE _unused) { | |
| 1549 1585 | 
             
              return rb_const_get(trace_module, rb_intern("CURRENT_SPAN_KEY"));
         | 
| 1550 1586 | 
             
            }
         | 
| 1551 1587 |  | 
| 1552 | 
            -
            static VALUE get_otel_current_span_key( | 
| 1588 | 
            +
            static VALUE get_otel_current_span_key(thread_context_collector_state *state, bool is_safe_to_allocate_objects) {
         | 
| 1553 1589 | 
             
              if (state->otel_current_span_key == Qtrue) { // Qtrue means we haven't tried to extract it yet
         | 
| 1554 1590 | 
             
                if (!is_safe_to_allocate_objects) {
         | 
| 1555 1591 | 
             
                  // Calling read_otel_current_span_key_const below can trigger exceptions and arbitrary Ruby code running (e.g.
         | 
| @@ -1572,7 +1608,7 @@ static VALUE get_otel_current_span_key(struct thread_context_collector_state *st | |
| 1572 1608 | 
             
            // This method gets used when ddtrace is being used indirectly via the opentelemetry APIs. Information gets stored slightly
         | 
| 1573 1609 | 
             
            // differently, and this codepath handles it.
         | 
| 1574 1610 | 
             
            static void ddtrace_otel_trace_identifiers_for(
         | 
| 1575 | 
            -
               | 
| 1611 | 
            +
              thread_context_collector_state *state,
         | 
| 1576 1612 | 
             
              VALUE *active_trace,
         | 
| 1577 1613 | 
             
              VALUE *root_span,
         | 
| 1578 1614 | 
             
              VALUE *numeric_span_id,
         | 
| @@ -1597,7 +1633,7 @@ static void ddtrace_otel_trace_identifiers_for( | |
| 1597 1633 | 
             
              // trace and span representing it. Each ddtrace trace is then connected to the previous otel span, forming a linked
         | 
| 1598 1634 | 
             
              // list. The local root span is going to be the trace/span we find at the end of this linked list.
         | 
| 1599 1635 | 
             
              while (otel_values != Qnil) {
         | 
| 1600 | 
            -
                VALUE otel_span =  | 
| 1636 | 
            +
                VALUE otel_span = safely_lookup_hash_without_going_into_ruby_code(otel_values, otel_current_span_key);
         | 
| 1601 1637 | 
             
                if (otel_span == Qnil) break;
         | 
| 1602 1638 | 
             
                VALUE next_trace = rb_ivar_get(otel_span, at_datadog_trace_id);
         | 
| 1603 1639 | 
             
                if (next_trace == Qnil) break;
         | 
| @@ -1616,8 +1652,8 @@ static void ddtrace_otel_trace_identifiers_for( | |
| 1616 1652 | 
             
            }
         | 
| 1617 1653 |  | 
| 1618 1654 | 
             
            void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples) {
         | 
| 1619 | 
            -
               | 
| 1620 | 
            -
              TypedData_Get_Struct(self_instance,  | 
| 1655 | 
            +
              thread_context_collector_state *state;
         | 
| 1656 | 
            +
              TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
         | 
| 1621 1657 |  | 
| 1622 1658 | 
             
              ddog_prof_Label labels[] = {
         | 
| 1623 1659 | 
             
                // Providing .num = 0 should not be needed but the tracer-2.7 docker image ships a buggy gcc that complains about this
         | 
| @@ -1640,7 +1676,12 @@ void thread_context_collector_sample_skipped_allocation_samples(VALUE self_insta | |
| 1640 1676 | 
             
            }
         | 
| 1641 1677 |  | 
| 1642 1678 | 
             
            static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples) {
         | 
| 1679 | 
            +
              debug_enter_unsafe_context();
         | 
| 1680 | 
            +
             | 
| 1643 1681 | 
             
              thread_context_collector_sample_skipped_allocation_samples(collector_instance, NUM2UINT(skipped_samples));
         | 
| 1682 | 
            +
             | 
| 1683 | 
            +
              debug_leave_unsafe_context();
         | 
| 1684 | 
            +
             | 
| 1644 1685 | 
             
              return Qtrue;
         | 
| 1645 1686 | 
             
            }
         | 
| 1646 1687 |  | 
| @@ -1666,9 +1707,9 @@ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self | |
| 1666 1707 | 
             
            //    root span id.
         | 
| 1667 1708 | 
             
            //    This matches the semantics of how ddtrace tracing creates a TraceOperation and assigns a local root span to it.
         | 
| 1668 1709 | 
             
            static void otel_without_ddtrace_trace_identifiers_for(
         | 
| 1669 | 
            -
               | 
| 1710 | 
            +
              thread_context_collector_state *state,
         | 
| 1670 1711 | 
             
              VALUE thread,
         | 
| 1671 | 
            -
               | 
| 1712 | 
            +
              trace_identifiers *trace_identifiers_result,
         | 
| 1672 1713 | 
             
              bool is_safe_to_allocate_objects
         | 
| 1673 1714 | 
             
            ) {
         | 
| 1674 1715 | 
             
              VALUE context_storage = rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
         | 
| @@ -1682,14 +1723,14 @@ static void otel_without_ddtrace_trace_identifiers_for( | |
| 1682 1723 | 
             
              int active_context_index = RARRAY_LEN(context_storage) - 1;
         | 
| 1683 1724 | 
             
              if (active_context_index < 0) return;
         | 
| 1684 1725 |  | 
| 1685 | 
            -
               | 
| 1726 | 
            +
              otel_span active_span = otel_span_from(rb_ary_entry(context_storage, active_context_index), otel_current_span_key);
         | 
| 1686 1727 | 
             
              if (active_span.span == Qnil) return;
         | 
| 1687 1728 |  | 
| 1688 | 
            -
               | 
| 1729 | 
            +
              otel_span local_root_span = active_span;
         | 
| 1689 1730 |  | 
| 1690 1731 | 
             
              // Now find the oldest span starting from the active span that still has the same trace id as the active span
         | 
| 1691 1732 | 
             
              for (int i = active_context_index - 1; i >= 0; i--) {
         | 
| 1692 | 
            -
                 | 
| 1733 | 
            +
                otel_span checking_span = otel_span_from(rb_ary_entry(context_storage, i), otel_current_span_key);
         | 
| 1693 1734 | 
             
                if (checking_span.span == Qnil) return;
         | 
| 1694 1735 |  | 
| 1695 1736 | 
             
                if (rb_str_equal(active_span.trace_id, checking_span.trace_id) == Qfalse) break;
         | 
| @@ -1709,7 +1750,7 @@ static void otel_without_ddtrace_trace_identifiers_for( | |
| 1709 1750 |  | 
| 1710 1751 | 
             
              VALUE root_span_type = rb_ivar_get(local_root_span.span, at_kind_id /* @kind */);
         | 
| 1711 1752 | 
             
              // We filter out spans that don't have `kind: :server`
         | 
| 1712 | 
            -
              if (root_span_type == Qnil || !RB_TYPE_P(root_span_type, T_SYMBOL) || SYM2ID(root_span_type) != server_id) return;
         | 
| 1753 | 
            +
              if (root_span_type == Qnil || !RB_TYPE_P(root_span_type, T_SYMBOL) || !RB_STATIC_SYM_P(root_span_type) || SYM2ID(root_span_type) != server_id) return;
         | 
| 1713 1754 |  | 
| 1714 1755 | 
             
              VALUE trace_resource = rb_ivar_get(local_root_span.span, at_name_id /* @name */);
         | 
| 1715 1756 | 
             
              if (!RB_TYPE_P(trace_resource, T_STRING)) return;
         | 
| @@ -1717,8 +1758,8 @@ static void otel_without_ddtrace_trace_identifiers_for( | |
| 1717 1758 | 
             
              trace_identifiers_result->trace_endpoint = trace_resource;
         | 
| 1718 1759 | 
             
            }
         | 
| 1719 1760 |  | 
| 1720 | 
            -
            static  | 
| 1721 | 
            -
               | 
| 1761 | 
            +
            static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key) {
         | 
| 1762 | 
            +
              otel_span failed = {.span = Qnil, .span_id = Qnil, .trace_id = Qnil};
         | 
| 1722 1763 |  | 
| 1723 1764 | 
             
              if (otel_context == Qnil) return failed;
         | 
| 1724 1765 |  | 
| @@ -1726,7 +1767,7 @@ static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_sp | |
| 1726 1767 | 
             
              if (context_entries == Qnil || !RB_TYPE_P(context_entries, T_HASH)) return failed;
         | 
| 1727 1768 |  | 
| 1728 1769 | 
             
              // If it exists, context_entries is expected to be a Hash[OpenTelemetry::Context::Key, OpenTelemetry::Trace::Span]
         | 
| 1729 | 
            -
              VALUE span =  | 
| 1770 | 
            +
              VALUE span = safely_lookup_hash_without_going_into_ruby_code(context_entries, otel_current_span_key);
         | 
| 1730 1771 | 
             
              if (span == Qnil) return failed;
         | 
| 1731 1772 |  | 
| 1732 1773 | 
             
              // If it exists, span_context is expected to be a OpenTelemetry::Trace::SpanContext (don't confuse it with OpenTelemetry::Context)
         | 
| @@ -1737,7 +1778,7 @@ static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_sp | |
| 1737 1778 | 
             
              VALUE trace_id = rb_ivar_get(span_context, at_trace_id_id /* @trace_id */);
         | 
| 1738 1779 | 
             
              if (span_id == Qnil || trace_id == Qnil || !RB_TYPE_P(span_id, T_STRING) || !RB_TYPE_P(trace_id, T_STRING)) return failed;
         | 
| 1739 1780 |  | 
| 1740 | 
            -
              return ( | 
| 1781 | 
            +
              return (otel_span) {.span = span, .span_id = span_id, .trace_id = trace_id};
         | 
| 1741 1782 | 
             
            }
         | 
| 1742 1783 |  | 
| 1743 1784 | 
             
            // Otel span ids are represented as a big-endian 8-byte string
         | 
| @@ -1839,8 +1880,8 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) { | |
| 1839 1880 | 
             
              // NOTE: In normal use, current_thread is expected to be == rb_thread_current(); the `current_thread` parameter only
         | 
| 1840 1881 | 
             
              // exists to enable testing.
         | 
| 1841 1882 | 
             
              VALUE thread_context_collector_sample_after_gvl_running(VALUE self_instance, VALUE current_thread, long current_monotonic_wall_time_ns) {
         | 
| 1842 | 
            -
                 | 
| 1843 | 
            -
                TypedData_Get_Struct(self_instance,  | 
| 1883 | 
            +
                thread_context_collector_state *state;
         | 
| 1884 | 
            +
                TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
         | 
| 1844 1885 |  | 
| 1845 1886 | 
             
                if (!state->timeline_enabled) rb_raise(rb_eRuntimeError, "GVL profiling requires timeline to be enabled");
         | 
| 1846 1887 |  | 
| @@ -1854,7 +1895,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) { | |
| 1854 1895 | 
             
                  return Qfalse;
         | 
| 1855 1896 | 
             
                }
         | 
| 1856 1897 |  | 
| 1857 | 
            -
                 | 
| 1898 | 
            +
                per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
         | 
| 1858 1899 |  | 
| 1859 1900 | 
             
                // We don't actually account for cpu-time during Waiting for GVL. BUT, we may chose to push an
         | 
| 1860 1901 | 
             
                // extra sample to represent the period prior to Waiting for GVL. To support that, we retrieve the current
         | 
| @@ -1880,10 +1921,10 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) { | |
| 1880 1921 | 
             
              // need to take when sampling cpu/wall-time for a thread that's in the "Waiting for GVL" state.
         | 
| 1881 1922 | 
             
              __attribute__((warn_unused_result))
         | 
| 1882 1923 | 
             
              static bool handle_gvl_waiting(
         | 
| 1883 | 
            -
                 | 
| 1924 | 
            +
                thread_context_collector_state *state,
         | 
| 1884 1925 | 
             
                VALUE thread_being_sampled,
         | 
| 1885 1926 | 
             
                VALUE stack_from_thread,
         | 
| 1886 | 
            -
                 | 
| 1927 | 
            +
                per_thread_context *thread_context,
         | 
| 1887 1928 | 
             
                sampling_buffer* sampling_buffer,
         | 
| 1888 1929 | 
             
                long current_cpu_time_ns
         | 
| 1889 1930 | 
             
              ) {
         | 
| @@ -1979,40 +2020,62 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) { | |
| 1979 2020 | 
             
              static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
         | 
| 1980 2021 | 
             
                ENFORCE_THREAD(thread);
         | 
| 1981 2022 |  | 
| 2023 | 
            +
                debug_enter_unsafe_context();
         | 
| 2024 | 
            +
             | 
| 1982 2025 | 
             
                thread_context_collector_on_gvl_waiting(thread_from_thread_object(thread));
         | 
| 2026 | 
            +
             | 
| 2027 | 
            +
                debug_leave_unsafe_context();
         | 
| 2028 | 
            +
             | 
| 1983 2029 | 
             
                return Qnil;
         | 
| 1984 2030 | 
             
              }
         | 
| 1985 2031 |  | 
| 1986 2032 | 
             
              static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
         | 
| 1987 2033 | 
             
                ENFORCE_THREAD(thread);
         | 
| 1988 2034 |  | 
| 2035 | 
            +
                debug_enter_unsafe_context();
         | 
| 2036 | 
            +
             | 
| 1989 2037 | 
             
                intptr_t gvl_waiting_at = gvl_profiling_state_thread_object_get(thread);
         | 
| 2038 | 
            +
             | 
| 2039 | 
            +
                debug_leave_unsafe_context();
         | 
| 2040 | 
            +
             | 
| 1990 2041 | 
             
                return LONG2NUM(gvl_waiting_at);
         | 
| 1991 2042 | 
             
              }
         | 
| 1992 2043 |  | 
| 1993 2044 | 
             
              static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread) {
         | 
| 1994 2045 | 
             
                ENFORCE_THREAD(thread);
         | 
| 1995 2046 |  | 
| 1996 | 
            -
                 | 
| 2047 | 
            +
                debug_enter_unsafe_context();
         | 
| 2048 | 
            +
             | 
| 2049 | 
            +
                VALUE result = thread_context_collector_on_gvl_running(thread_from_thread_object(thread)) == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
         | 
| 2050 | 
            +
             | 
| 2051 | 
            +
                debug_leave_unsafe_context();
         | 
| 2052 | 
            +
             | 
| 2053 | 
            +
                return result;
         | 
| 1997 2054 | 
             
              }
         | 
| 1998 2055 |  | 
| 1999 2056 | 
             
              static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
         | 
| 2000 2057 | 
             
                ENFORCE_THREAD(thread);
         | 
| 2001 2058 |  | 
| 2002 | 
            -
                 | 
| 2059 | 
            +
                debug_enter_unsafe_context();
         | 
| 2060 | 
            +
             | 
| 2061 | 
            +
                VALUE result = thread_context_collector_sample_after_gvl_running(
         | 
| 2003 2062 | 
             
                  collector_instance,
         | 
| 2004 2063 | 
             
                  thread,
         | 
| 2005 2064 | 
             
                  monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
         | 
| 2006 2065 | 
             
                );
         | 
| 2066 | 
            +
             | 
| 2067 | 
            +
                debug_leave_unsafe_context();
         | 
| 2068 | 
            +
             | 
| 2069 | 
            +
                return result;
         | 
| 2007 2070 | 
             
              }
         | 
| 2008 2071 |  | 
| 2009 2072 | 
             
              static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE delta_ns) {
         | 
| 2010 2073 | 
             
                ENFORCE_THREAD(thread);
         | 
| 2011 2074 |  | 
| 2012 | 
            -
                 | 
| 2013 | 
            -
                TypedData_Get_Struct(collector_instance,  | 
| 2075 | 
            +
                thread_context_collector_state *state;
         | 
| 2076 | 
            +
                TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
         | 
| 2014 2077 |  | 
| 2015 | 
            -
                 | 
| 2078 | 
            +
                per_thread_context *thread_context = get_context_for(thread, state);
         | 
| 2016 2079 | 
             
                if (thread_context == NULL) rb_raise(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
         | 
| 2017 2080 |  | 
| 2018 2081 | 
             
                thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
         | 
| @@ -2022,11 +2085,45 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) { | |
| 2022 2085 |  | 
| 2023 2086 | 
             
            #else
         | 
| 2024 2087 | 
             
              static bool handle_gvl_waiting(
         | 
| 2025 | 
            -
                DDTRACE_UNUSED  | 
| 2088 | 
            +
                DDTRACE_UNUSED thread_context_collector_state *state,
         | 
| 2026 2089 | 
             
                DDTRACE_UNUSED VALUE thread_being_sampled,
         | 
| 2027 2090 | 
             
                DDTRACE_UNUSED VALUE stack_from_thread,
         | 
| 2028 | 
            -
                DDTRACE_UNUSED  | 
| 2091 | 
            +
                DDTRACE_UNUSED per_thread_context *thread_context,
         | 
| 2029 2092 | 
             
                DDTRACE_UNUSED sampling_buffer* sampling_buffer,
         | 
| 2030 2093 | 
             
                DDTRACE_UNUSED long current_cpu_time_ns
         | 
| 2031 2094 | 
             
              ) { return false; }
         | 
| 2032 2095 | 
             
            #endif // NO_GVL_INSTRUMENTATION
         | 
| 2096 | 
            +
             | 
| 2097 | 
            +
            #define MAX_SAFE_LOOKUP_SIZE 16
         | 
| 2098 | 
            +
             | 
| 2099 | 
            +
            typedef struct { VALUE lookup_key; VALUE result; } safe_lookup_hash_state;
         | 
| 2100 | 
            +
             | 
| 2101 | 
            +
            static int safe_lookup_hash_iterate(VALUE key, VALUE value, VALUE state_ptr) {
         | 
| 2102 | 
            +
              safe_lookup_hash_state *state = (safe_lookup_hash_state *) state_ptr;
         | 
| 2103 | 
            +
             | 
| 2104 | 
            +
              if (key == state->lookup_key) {
         | 
| 2105 | 
            +
                state->result = value;
         | 
| 2106 | 
            +
                return ST_STOP;
         | 
| 2107 | 
            +
              }
         | 
| 2108 | 
            +
             | 
| 2109 | 
            +
              return ST_CONTINUE;
         | 
| 2110 | 
            +
            }
         | 
| 2111 | 
            +
             | 
| 2112 | 
            +
            // This method exists because we need to look up a hash during sampling, but we don't want to invoke any
         | 
| 2113 | 
            +
            // Ruby code as a side effect. To be able to look up by hash, `rb_hash_lookup` calls `#hash` on the key,
         | 
| 2114 | 
            +
            // which we want to avoid.
         | 
| 2115 | 
            +
            // Thus, instead, we opt to just iterate through the hash and check if we can find what we're looking for.
         | 
| 2116 | 
            +
            //
         | 
| 2117 | 
            +
            // To avoid having too much overhead here we only iterate in hashes up to MAX_SAFE_LOOKUP_SIZE.
         | 
| 2118 | 
            +
            // Note that we don't even try to iterate if the hash is bigger -- this is to avoid flaky behavior where
         | 
| 2119 | 
            +
            // depending on the internal storage order of the hash we may or not find the key, and instead we always
         | 
| 2120 | 
            +
            // enforce the size.
         | 
| 2121 | 
            +
            static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key) {
         | 
| 2122 | 
            +
              if (!RB_TYPE_P(hash, T_HASH) || RHASH_SIZE(hash) > MAX_SAFE_LOOKUP_SIZE) return Qnil;
         | 
| 2123 | 
            +
             | 
| 2124 | 
            +
              safe_lookup_hash_state state = {.lookup_key = key, .result = Qnil};
         | 
| 2125 | 
            +
             | 
| 2126 | 
            +
              rb_hash_foreach(hash, safe_lookup_hash_iterate, (VALUE) &state);
         | 
| 2127 | 
            +
             | 
| 2128 | 
            +
              return state.result;
         | 
| 2129 | 
            +
            }
         |