datadog 2.8.0 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -1
  3. data/ext/datadog_profiling_native_extension/clock_id.h +2 -2
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +64 -54
  5. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
  6. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +1 -1
  7. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +16 -16
  8. data/ext/datadog_profiling_native_extension/collectors_stack.c +7 -7
  9. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +219 -122
  10. data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
  11. data/ext/datadog_profiling_native_extension/http_transport.c +4 -4
  12. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +3 -0
  13. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -1
  14. data/ext/datadog_profiling_native_extension/profiling.c +10 -8
  15. data/ext/datadog_profiling_native_extension/ruby_helpers.c +8 -8
  16. data/ext/datadog_profiling_native_extension/stack_recorder.c +54 -54
  17. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
  18. data/ext/datadog_profiling_native_extension/time_helpers.h +1 -1
  19. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +47 -0
  20. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +31 -0
  21. data/ext/libdatadog_api/crashtracker.c +3 -0
  22. data/lib/datadog/appsec/assets/waf_rules/recommended.json +355 -157
  23. data/lib/datadog/appsec/assets/waf_rules/strict.json +62 -32
  24. data/lib/datadog/appsec/context.rb +54 -0
  25. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +7 -7
  26. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +6 -6
  27. data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +4 -4
  28. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +19 -28
  29. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +5 -5
  30. data/lib/datadog/appsec/contrib/rack/gateway/response.rb +3 -3
  31. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +64 -96
  32. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +10 -10
  33. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +5 -5
  34. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +6 -6
  35. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +10 -11
  36. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -49
  37. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +21 -32
  38. data/lib/datadog/appsec/contrib/rails/patcher.rb +1 -1
  39. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +6 -6
  40. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +41 -63
  41. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +2 -2
  42. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +5 -5
  43. data/lib/datadog/appsec/event.rb +6 -6
  44. data/lib/datadog/appsec/ext.rb +3 -1
  45. data/lib/datadog/appsec/monitor/gateway/watcher.rb +22 -32
  46. data/lib/datadog/appsec/monitor/reactive/set_user.rb +5 -5
  47. data/lib/datadog/appsec/processor/rule_loader.rb +0 -3
  48. data/lib/datadog/appsec.rb +3 -3
  49. data/lib/datadog/auto_instrument.rb +3 -0
  50. data/lib/datadog/core/configuration/agent_settings_resolver.rb +39 -11
  51. data/lib/datadog/core/configuration/components.rb +4 -2
  52. data/lib/datadog/core/configuration.rb +1 -1
  53. data/lib/datadog/{tracing → core}/contrib/rails/utils.rb +1 -3
  54. data/lib/datadog/core/crashtracking/component.rb +1 -3
  55. data/lib/datadog/core/telemetry/event.rb +87 -3
  56. data/lib/datadog/core/telemetry/logging.rb +2 -2
  57. data/lib/datadog/core/telemetry/metric.rb +22 -0
  58. data/lib/datadog/core/telemetry/worker.rb +33 -0
  59. data/lib/datadog/di/base.rb +115 -0
  60. data/lib/datadog/di/code_tracker.rb +7 -4
  61. data/lib/datadog/di/component.rb +17 -11
  62. data/lib/datadog/di/configuration/settings.rb +11 -1
  63. data/lib/datadog/di/contrib/railtie.rb +15 -0
  64. data/lib/datadog/di/contrib.rb +26 -0
  65. data/lib/datadog/di/error.rb +5 -0
  66. data/lib/datadog/di/instrumenter.rb +39 -18
  67. data/lib/datadog/di/{init.rb → preload.rb} +2 -4
  68. data/lib/datadog/di/probe_manager.rb +4 -4
  69. data/lib/datadog/di/probe_notification_builder.rb +16 -2
  70. data/lib/datadog/di/probe_notifier_worker.rb +5 -6
  71. data/lib/datadog/di/remote.rb +4 -4
  72. data/lib/datadog/di/transport.rb +2 -4
  73. data/lib/datadog/di.rb +5 -108
  74. data/lib/datadog/kit/appsec/events.rb +3 -3
  75. data/lib/datadog/kit/identity.rb +4 -4
  76. data/lib/datadog/profiling/component.rb +55 -53
  77. data/lib/datadog/profiling/http_transport.rb +1 -26
  78. data/lib/datadog/tracing/contrib/action_cable/integration.rb +5 -2
  79. data/lib/datadog/tracing/contrib/action_mailer/integration.rb +6 -2
  80. data/lib/datadog/tracing/contrib/action_pack/integration.rb +5 -2
  81. data/lib/datadog/tracing/contrib/action_view/integration.rb +5 -2
  82. data/lib/datadog/tracing/contrib/active_job/integration.rb +5 -2
  83. data/lib/datadog/tracing/contrib/active_record/integration.rb +6 -2
  84. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +3 -1
  85. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +3 -1
  86. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +10 -0
  87. data/lib/datadog/tracing/contrib/active_support/integration.rb +5 -2
  88. data/lib/datadog/tracing/contrib/auto_instrument.rb +2 -2
  89. data/lib/datadog/tracing/contrib/aws/integration.rb +3 -0
  90. data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +3 -0
  91. data/lib/datadog/tracing/contrib/httprb/integration.rb +3 -0
  92. data/lib/datadog/tracing/contrib/kafka/integration.rb +3 -0
  93. data/lib/datadog/tracing/contrib/mongodb/integration.rb +3 -0
  94. data/lib/datadog/tracing/contrib/opensearch/integration.rb +3 -0
  95. data/lib/datadog/tracing/contrib/presto/integration.rb +3 -0
  96. data/lib/datadog/tracing/contrib/rack/integration.rb +2 -2
  97. data/lib/datadog/tracing/contrib/rails/framework.rb +2 -2
  98. data/lib/datadog/tracing/contrib/rails/patcher.rb +1 -1
  99. data/lib/datadog/tracing/contrib/rest_client/integration.rb +3 -0
  100. data/lib/datadog/tracing/span.rb +12 -4
  101. data/lib/datadog/tracing/span_event.rb +123 -3
  102. data/lib/datadog/tracing/span_operation.rb +6 -0
  103. data/lib/datadog/tracing/transport/serializable_trace.rb +24 -6
  104. data/lib/datadog/version.rb +1 -1
  105. metadata +19 -10
  106. data/lib/datadog/appsec/reactive/operation.rb +0 -68
  107. data/lib/datadog/appsec/scope.rb +0 -58
  108. 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 thread_context_collector_state {
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, struct per_thread_context>
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 per_thread_context {
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 trace_identifiers {
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 otel_span {
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
- struct thread_context_collector_state *state,
212
+ thread_context_collector_state *state,
212
213
  VALUE thread_being_sampled,
213
214
  VALUE stack_from_thread,
214
- struct per_thread_context *thread_context,
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
- struct thread_context_collector_state *state,
221
+ thread_context_collector_state *state,
221
222
  VALUE thread,
222
223
  VALUE stack_from_thread,
223
- struct per_thread_context *thread_context,
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 struct per_thread_context *get_or_create_context_for(VALUE thread, struct thread_context_collector_state *state);
234
- static struct per_thread_context *get_context_for(VALUE thread, struct thread_context_collector_state *state);
235
- static void initialize_context(VALUE thread, struct per_thread_context *thread_context, struct thread_context_collector_state *state);
236
- static void free_context(struct per_thread_context* thread_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(struct thread_context_collector_state *state);
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(struct thread_context_collector_state *state);
241
- static VALUE gc_tracking_as_ruby_hash(struct thread_context_collector_state *state);
242
- static void remove_context_for_dead_threads(struct thread_context_collector_state *state);
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(struct per_thread_context *thread_context);
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
- struct thread_context_collector_state *state,
252
+ thread_context_collector_state *state,
252
253
  VALUE thread,
253
- struct trace_identifiers *trace_identifiers_result,
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(struct thread_context_collector_state *state);
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
- struct thread_context_collector_state *state,
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
- struct thread_context_collector_state *state,
274
+ thread_context_collector_state *state,
274
275
  VALUE thread_being_sampled,
275
276
  VALUE stack_from_thread,
276
- struct per_thread_context *thread_context,
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
- struct thread_context_collector_state *state,
287
+ thread_context_collector_state *state,
287
288
  VALUE thread,
288
- struct trace_identifiers *trace_identifiers_result,
289
+ trace_identifiers *trace_identifiers_result,
289
290
  bool is_safe_to_allocate_objects
290
291
  );
291
- static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key);
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, 2);
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, 2);
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 struct thread_context_collector_state
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
- struct thread_context_collector_state *state = (struct thread_context_collector_state *) state_ptr;
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
- struct thread_context_collector_state *state = (struct thread_context_collector_state *) state_ptr;
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
- struct per_thread_context *thread_context = (struct per_thread_context*) value_per_thread_context;
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
- struct thread_context_collector_state *state = ruby_xcalloc(1, sizeof(struct thread_context_collector_state));
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
- struct thread_context_collector_state *state;
473
- TypedData_Get_Struct(self_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
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
- struct thread_context_collector_state *state;
534
- TypedData_Get_Struct(collector_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
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
- struct thread_context_collector_state *state;
556
- TypedData_Get_Struct(self_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
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
- struct per_thread_context *current_thread_context = get_or_create_context_for(current_thread, state);
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
- struct per_thread_context *thread_context = get_or_create_context_for(thread, state);
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
- struct thread_context_collector_state *state,
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
- struct per_thread_context *thread_context,
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
- struct thread_context_collector_state *state;
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, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
702
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
678
703
 
679
- struct per_thread_context *thread_context = get_context_for(rb_thread_current(), state);
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
- struct thread_context_collector_state *state;
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, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
735
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
711
736
 
712
- struct per_thread_context *thread_context = get_context_for(rb_thread_current(), state);
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
- struct thread_context_collector_state *state;
786
- TypedData_Get_Struct(self_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
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
- struct thread_context_collector_state *state,
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
- struct per_thread_context *thread_context,
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
- struct trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
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 struct per_thread_context *get_or_create_context_for(VALUE thread, struct thread_context_collector_state *state) {
990
- struct per_thread_context* thread_context = NULL;
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 = (struct per_thread_context*) value_context;
1025
+ thread_context = (per_thread_context*) value_context;
995
1026
  } else {
996
- thread_context = ruby_xcalloc(1, sizeof(struct per_thread_context));
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 struct per_thread_context *get_context_for(VALUE thread, struct thread_context_collector_state *state) {
1005
- struct per_thread_context* thread_context = NULL;
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 = (struct per_thread_context*) value_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, struct per_thread_context *thread_context, struct thread_context_collector_state *state) {
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(struct per_thread_context* thread_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
- struct thread_context_collector_state *state;
1100
- TypedData_Get_Struct(collector_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
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(struct thread_context_collector_state *state) {
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
- struct per_thread_context *thread_context = (struct per_thread_context*) value_context;
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(struct thread_context_collector_state *state) {
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(struct thread_context_collector_state *state) {
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(struct thread_context_collector_state *state) {
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
- struct per_thread_context* thread_context = (struct per_thread_context*) value_context;
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
- struct thread_context_collector_state *state;
1205
- TypedData_Get_Struct(collector_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
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(struct per_thread_context *thread_context) {
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
- struct thread_context_collector_state *state;
1289
- TypedData_Get_Struct(collector_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
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
- struct thread_context_collector_state *state;
1298
- TypedData_Get_Struct(collector_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
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
- struct thread_context_collector_state *state,
1336
+ thread_context_collector_state *state,
1306
1337
  VALUE thread,
1307
- struct trace_identifiers *trace_identifiers_result,
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
- struct thread_context_collector_state *state;
1388
- TypedData_Get_Struct(collector_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
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(struct thread_context_collector_state *state) {
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
- struct thread_context_collector_state *state;
1411
- TypedData_Get_Struct(self_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
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
- struct per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
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(struct thread_context_collector_state *state, bool is_safe_to_allocate_objects) {
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
- struct thread_context_collector_state *state,
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 = rb_hash_lookup(otel_values, otel_current_span_key);
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
- struct thread_context_collector_state *state;
1620
- TypedData_Get_Struct(self_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
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
- struct thread_context_collector_state *state,
1710
+ thread_context_collector_state *state,
1670
1711
  VALUE thread,
1671
- struct trace_identifiers *trace_identifiers_result,
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
- struct otel_span active_span = otel_span_from(rb_ary_entry(context_storage, active_context_index), otel_current_span_key);
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
- struct otel_span local_root_span = active_span;
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
- struct otel_span checking_span = otel_span_from(rb_ary_entry(context_storage, i), otel_current_span_key);
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 struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key) {
1721
- struct otel_span failed = {.span = Qnil, .span_id = Qnil, .trace_id = Qnil};
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 = rb_hash_lookup(context_entries, otel_current_span_key);
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 (struct otel_span) {.span = span, .span_id = span_id, .trace_id = trace_id};
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
- struct thread_context_collector_state *state;
1843
- TypedData_Get_Struct(self_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
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
- struct per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
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
- struct thread_context_collector_state *state,
1924
+ thread_context_collector_state *state,
1884
1925
  VALUE thread_being_sampled,
1885
1926
  VALUE stack_from_thread,
1886
- struct per_thread_context *thread_context,
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
- return thread_context_collector_on_gvl_running(thread_from_thread_object(thread)) == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
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
- return thread_context_collector_sample_after_gvl_running(
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
- struct thread_context_collector_state *state;
2013
- TypedData_Get_Struct(collector_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
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
- struct per_thread_context *thread_context = get_context_for(thread, state);
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 struct thread_context_collector_state *state,
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 struct per_thread_context *thread_context,
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
+ }