datadog 2.8.0 → 2.10.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.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -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 +66 -56
  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_stack.h +2 -2
  10. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +221 -127
  11. data/ext/datadog_profiling_native_extension/heap_recorder.c +50 -92
  12. data/ext/datadog_profiling_native_extension/heap_recorder.h +2 -2
  13. data/ext/datadog_profiling_native_extension/http_transport.c +4 -4
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +3 -0
  15. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -1
  16. data/ext/datadog_profiling_native_extension/profiling.c +10 -8
  17. data/ext/datadog_profiling_native_extension/ruby_helpers.c +8 -8
  18. data/ext/datadog_profiling_native_extension/stack_recorder.c +63 -76
  19. data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -2
  20. data/ext/datadog_profiling_native_extension/time_helpers.h +1 -1
  21. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +47 -0
  22. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +31 -0
  23. data/ext/libdatadog_api/crashtracker.c +3 -0
  24. data/lib/datadog/appsec/actions_handler.rb +27 -0
  25. data/lib/datadog/appsec/assets/waf_rules/recommended.json +355 -157
  26. data/lib/datadog/appsec/assets/waf_rules/strict.json +62 -32
  27. data/lib/datadog/appsec/component.rb +14 -8
  28. data/lib/datadog/appsec/configuration/settings.rb +9 -0
  29. data/lib/datadog/appsec/context.rb +74 -0
  30. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +12 -8
  31. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +6 -6
  32. data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +4 -4
  33. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +1 -7
  34. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +20 -30
  35. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +6 -6
  36. data/lib/datadog/appsec/contrib/rack/gateway/response.rb +3 -3
  37. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +67 -96
  38. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +11 -11
  39. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +6 -6
  40. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +7 -7
  41. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +10 -11
  42. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -60
  43. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +23 -33
  44. data/lib/datadog/appsec/contrib/rails/patcher.rb +4 -14
  45. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +7 -7
  46. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +45 -65
  47. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +5 -28
  48. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +6 -6
  49. data/lib/datadog/appsec/event.rb +6 -6
  50. data/lib/datadog/appsec/ext.rb +8 -1
  51. data/lib/datadog/appsec/metrics/collector.rb +38 -0
  52. data/lib/datadog/appsec/metrics/exporter.rb +35 -0
  53. data/lib/datadog/appsec/metrics/telemetry.rb +23 -0
  54. data/lib/datadog/appsec/metrics.rb +13 -0
  55. data/lib/datadog/appsec/monitor/gateway/watcher.rb +23 -32
  56. data/lib/datadog/appsec/monitor/reactive/set_user.rb +6 -6
  57. data/lib/datadog/appsec/processor/rule_loader.rb +0 -3
  58. data/lib/datadog/appsec/processor.rb +4 -3
  59. data/lib/datadog/appsec/response.rb +18 -80
  60. data/lib/datadog/appsec/security_engine/result.rb +67 -0
  61. data/lib/datadog/appsec/security_engine/runner.rb +88 -0
  62. data/lib/datadog/appsec/security_engine.rb +9 -0
  63. data/lib/datadog/appsec.rb +17 -8
  64. data/lib/datadog/auto_instrument.rb +3 -0
  65. data/lib/datadog/core/configuration/agent_settings_resolver.rb +39 -11
  66. data/lib/datadog/core/configuration/components.rb +4 -2
  67. data/lib/datadog/core/configuration.rb +1 -1
  68. data/lib/datadog/{tracing → core}/contrib/rails/utils.rb +1 -3
  69. data/lib/datadog/core/crashtracking/component.rb +1 -3
  70. data/lib/datadog/core/telemetry/event.rb +87 -3
  71. data/lib/datadog/core/telemetry/logging.rb +2 -2
  72. data/lib/datadog/core/telemetry/metric.rb +22 -0
  73. data/lib/datadog/core/telemetry/worker.rb +33 -0
  74. data/lib/datadog/di/base.rb +115 -0
  75. data/lib/datadog/di/code_tracker.rb +7 -4
  76. data/lib/datadog/di/component.rb +19 -11
  77. data/lib/datadog/di/configuration/settings.rb +11 -1
  78. data/lib/datadog/di/contrib/railtie.rb +15 -0
  79. data/lib/datadog/di/contrib.rb +26 -0
  80. data/lib/datadog/di/error.rb +5 -0
  81. data/lib/datadog/di/instrumenter.rb +39 -18
  82. data/lib/datadog/di/{init.rb → preload.rb} +2 -4
  83. data/lib/datadog/di/probe_manager.rb +4 -4
  84. data/lib/datadog/di/probe_notification_builder.rb +22 -2
  85. data/lib/datadog/di/probe_notifier_worker.rb +5 -6
  86. data/lib/datadog/di/redactor.rb +0 -1
  87. data/lib/datadog/di/remote.rb +30 -9
  88. data/lib/datadog/di/transport.rb +2 -4
  89. data/lib/datadog/di.rb +5 -108
  90. data/lib/datadog/kit/appsec/events.rb +3 -3
  91. data/lib/datadog/kit/identity.rb +4 -4
  92. data/lib/datadog/profiling/component.rb +55 -53
  93. data/lib/datadog/profiling/http_transport.rb +1 -26
  94. data/lib/datadog/tracing/contrib/action_cable/integration.rb +5 -2
  95. data/lib/datadog/tracing/contrib/action_mailer/integration.rb +6 -2
  96. data/lib/datadog/tracing/contrib/action_pack/integration.rb +5 -2
  97. data/lib/datadog/tracing/contrib/action_view/integration.rb +5 -2
  98. data/lib/datadog/tracing/contrib/active_job/integration.rb +5 -2
  99. data/lib/datadog/tracing/contrib/active_record/integration.rb +6 -2
  100. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +3 -1
  101. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +3 -1
  102. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +10 -0
  103. data/lib/datadog/tracing/contrib/active_support/integration.rb +5 -2
  104. data/lib/datadog/tracing/contrib/auto_instrument.rb +2 -2
  105. data/lib/datadog/tracing/contrib/aws/integration.rb +3 -0
  106. data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +3 -0
  107. data/lib/datadog/tracing/contrib/extensions.rb +15 -3
  108. data/lib/datadog/tracing/contrib/http/integration.rb +3 -0
  109. data/lib/datadog/tracing/contrib/httprb/integration.rb +3 -0
  110. data/lib/datadog/tracing/contrib/kafka/integration.rb +3 -0
  111. data/lib/datadog/tracing/contrib/mongodb/integration.rb +3 -0
  112. data/lib/datadog/tracing/contrib/opensearch/integration.rb +3 -0
  113. data/lib/datadog/tracing/contrib/presto/integration.rb +3 -0
  114. data/lib/datadog/tracing/contrib/rack/integration.rb +2 -2
  115. data/lib/datadog/tracing/contrib/rails/framework.rb +2 -2
  116. data/lib/datadog/tracing/contrib/rails/patcher.rb +1 -1
  117. data/lib/datadog/tracing/contrib/rest_client/integration.rb +3 -0
  118. data/lib/datadog/tracing/span.rb +12 -4
  119. data/lib/datadog/tracing/span_event.rb +123 -3
  120. data/lib/datadog/tracing/span_operation.rb +6 -0
  121. data/lib/datadog/tracing/transport/serializable_trace.rb +24 -6
  122. data/lib/datadog/version.rb +1 -1
  123. metadata +40 -17
  124. data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
  125. data/lib/datadog/appsec/processor/context.rb +0 -107
  126. data/lib/datadog/appsec/reactive/operation.rb +0 -68
  127. data/lib/datadog/appsec/scope.rb +0 -58
  128. 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
 
@@ -1419,11 +1450,8 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
1419
1450
 
1420
1451
  // Since this is stack allocated, be careful about moving it
1421
1452
  ddog_CharSlice class_name;
1422
- ddog_CharSlice *optional_class_name = NULL;
1423
1453
  char imemo_type[100];
1424
1454
 
1425
- optional_class_name = &class_name;
1426
-
1427
1455
  if (
1428
1456
  type == RUBY_T_OBJECT ||
1429
1457
  type == RUBY_T_CLASS ||
@@ -1479,9 +1507,9 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
1479
1507
  class_name = ruby_vm_type; // For other weird internal things we just use the VM type
1480
1508
  }
1481
1509
 
1482
- track_object(state->recorder_instance, new_object, sample_weight, optional_class_name);
1510
+ track_object(state->recorder_instance, new_object, sample_weight, class_name);
1483
1511
 
1484
- struct per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
1512
+ per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
1485
1513
 
1486
1514
  trigger_sample_for_thread(
1487
1515
  state,
@@ -1492,7 +1520,7 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
1492
1520
  (sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = true},
1493
1521
  INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
1494
1522
  &ruby_vm_type,
1495
- optional_class_name,
1523
+ &class_name,
1496
1524
  /* is_gvl_waiting_state: */ false,
1497
1525
  /* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
1498
1526
  );
@@ -1501,7 +1529,12 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
1501
1529
  // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
1502
1530
  // It SHOULD NOT be used for other purposes.
1503
1531
  static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object) {
1532
+ debug_enter_unsafe_context();
1533
+
1504
1534
  thread_context_collector_sample_allocation(collector_instance, NUM2UINT(sample_weight), new_object);
1535
+
1536
+ debug_leave_unsafe_context();
1537
+
1505
1538
  return Qtrue;
1506
1539
  }
1507
1540
 
@@ -1549,7 +1582,7 @@ static VALUE read_otel_current_span_key_const(DDTRACE_UNUSED VALUE _unused) {
1549
1582
  return rb_const_get(trace_module, rb_intern("CURRENT_SPAN_KEY"));
1550
1583
  }
1551
1584
 
1552
- static VALUE get_otel_current_span_key(struct thread_context_collector_state *state, bool is_safe_to_allocate_objects) {
1585
+ static VALUE get_otel_current_span_key(thread_context_collector_state *state, bool is_safe_to_allocate_objects) {
1553
1586
  if (state->otel_current_span_key == Qtrue) { // Qtrue means we haven't tried to extract it yet
1554
1587
  if (!is_safe_to_allocate_objects) {
1555
1588
  // Calling read_otel_current_span_key_const below can trigger exceptions and arbitrary Ruby code running (e.g.
@@ -1572,7 +1605,7 @@ static VALUE get_otel_current_span_key(struct thread_context_collector_state *st
1572
1605
  // This method gets used when ddtrace is being used indirectly via the opentelemetry APIs. Information gets stored slightly
1573
1606
  // differently, and this codepath handles it.
1574
1607
  static void ddtrace_otel_trace_identifiers_for(
1575
- struct thread_context_collector_state *state,
1608
+ thread_context_collector_state *state,
1576
1609
  VALUE *active_trace,
1577
1610
  VALUE *root_span,
1578
1611
  VALUE *numeric_span_id,
@@ -1597,7 +1630,7 @@ static void ddtrace_otel_trace_identifiers_for(
1597
1630
  // trace and span representing it. Each ddtrace trace is then connected to the previous otel span, forming a linked
1598
1631
  // list. The local root span is going to be the trace/span we find at the end of this linked list.
1599
1632
  while (otel_values != Qnil) {
1600
- VALUE otel_span = rb_hash_lookup(otel_values, otel_current_span_key);
1633
+ VALUE otel_span = safely_lookup_hash_without_going_into_ruby_code(otel_values, otel_current_span_key);
1601
1634
  if (otel_span == Qnil) break;
1602
1635
  VALUE next_trace = rb_ivar_get(otel_span, at_datadog_trace_id);
1603
1636
  if (next_trace == Qnil) break;
@@ -1616,8 +1649,8 @@ static void ddtrace_otel_trace_identifiers_for(
1616
1649
  }
1617
1650
 
1618
1651
  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);
1652
+ thread_context_collector_state *state;
1653
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1621
1654
 
1622
1655
  ddog_prof_Label labels[] = {
1623
1656
  // Providing .num = 0 should not be needed but the tracer-2.7 docker image ships a buggy gcc that complains about this
@@ -1640,7 +1673,12 @@ void thread_context_collector_sample_skipped_allocation_samples(VALUE self_insta
1640
1673
  }
1641
1674
 
1642
1675
  static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples) {
1676
+ debug_enter_unsafe_context();
1677
+
1643
1678
  thread_context_collector_sample_skipped_allocation_samples(collector_instance, NUM2UINT(skipped_samples));
1679
+
1680
+ debug_leave_unsafe_context();
1681
+
1644
1682
  return Qtrue;
1645
1683
  }
1646
1684
 
@@ -1666,9 +1704,9 @@ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self
1666
1704
  // root span id.
1667
1705
  // This matches the semantics of how ddtrace tracing creates a TraceOperation and assigns a local root span to it.
1668
1706
  static void otel_without_ddtrace_trace_identifiers_for(
1669
- struct thread_context_collector_state *state,
1707
+ thread_context_collector_state *state,
1670
1708
  VALUE thread,
1671
- struct trace_identifiers *trace_identifiers_result,
1709
+ trace_identifiers *trace_identifiers_result,
1672
1710
  bool is_safe_to_allocate_objects
1673
1711
  ) {
1674
1712
  VALUE context_storage = rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
@@ -1682,14 +1720,14 @@ static void otel_without_ddtrace_trace_identifiers_for(
1682
1720
  int active_context_index = RARRAY_LEN(context_storage) - 1;
1683
1721
  if (active_context_index < 0) return;
1684
1722
 
1685
- struct otel_span active_span = otel_span_from(rb_ary_entry(context_storage, active_context_index), otel_current_span_key);
1723
+ otel_span active_span = otel_span_from(rb_ary_entry(context_storage, active_context_index), otel_current_span_key);
1686
1724
  if (active_span.span == Qnil) return;
1687
1725
 
1688
- struct otel_span local_root_span = active_span;
1726
+ otel_span local_root_span = active_span;
1689
1727
 
1690
1728
  // Now find the oldest span starting from the active span that still has the same trace id as the active span
1691
1729
  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);
1730
+ otel_span checking_span = otel_span_from(rb_ary_entry(context_storage, i), otel_current_span_key);
1693
1731
  if (checking_span.span == Qnil) return;
1694
1732
 
1695
1733
  if (rb_str_equal(active_span.trace_id, checking_span.trace_id) == Qfalse) break;
@@ -1709,7 +1747,7 @@ static void otel_without_ddtrace_trace_identifiers_for(
1709
1747
 
1710
1748
  VALUE root_span_type = rb_ivar_get(local_root_span.span, at_kind_id /* @kind */);
1711
1749
  // 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;
1750
+ 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
1751
 
1714
1752
  VALUE trace_resource = rb_ivar_get(local_root_span.span, at_name_id /* @name */);
1715
1753
  if (!RB_TYPE_P(trace_resource, T_STRING)) return;
@@ -1717,8 +1755,8 @@ static void otel_without_ddtrace_trace_identifiers_for(
1717
1755
  trace_identifiers_result->trace_endpoint = trace_resource;
1718
1756
  }
1719
1757
 
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};
1758
+ static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key) {
1759
+ otel_span failed = {.span = Qnil, .span_id = Qnil, .trace_id = Qnil};
1722
1760
 
1723
1761
  if (otel_context == Qnil) return failed;
1724
1762
 
@@ -1726,7 +1764,7 @@ static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_sp
1726
1764
  if (context_entries == Qnil || !RB_TYPE_P(context_entries, T_HASH)) return failed;
1727
1765
 
1728
1766
  // 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);
1767
+ VALUE span = safely_lookup_hash_without_going_into_ruby_code(context_entries, otel_current_span_key);
1730
1768
  if (span == Qnil) return failed;
1731
1769
 
1732
1770
  // If it exists, span_context is expected to be a OpenTelemetry::Trace::SpanContext (don't confuse it with OpenTelemetry::Context)
@@ -1737,7 +1775,7 @@ static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_sp
1737
1775
  VALUE trace_id = rb_ivar_get(span_context, at_trace_id_id /* @trace_id */);
1738
1776
  if (span_id == Qnil || trace_id == Qnil || !RB_TYPE_P(span_id, T_STRING) || !RB_TYPE_P(trace_id, T_STRING)) return failed;
1739
1777
 
1740
- return (struct otel_span) {.span = span, .span_id = span_id, .trace_id = trace_id};
1778
+ return (otel_span) {.span = span, .span_id = span_id, .trace_id = trace_id};
1741
1779
  }
1742
1780
 
1743
1781
  // Otel span ids are represented as a big-endian 8-byte string
@@ -1839,8 +1877,8 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
1839
1877
  // NOTE: In normal use, current_thread is expected to be == rb_thread_current(); the `current_thread` parameter only
1840
1878
  // exists to enable testing.
1841
1879
  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);
1880
+ thread_context_collector_state *state;
1881
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1844
1882
 
1845
1883
  if (!state->timeline_enabled) rb_raise(rb_eRuntimeError, "GVL profiling requires timeline to be enabled");
1846
1884
 
@@ -1854,7 +1892,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
1854
1892
  return Qfalse;
1855
1893
  }
1856
1894
 
1857
- struct per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
1895
+ per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
1858
1896
 
1859
1897
  // We don't actually account for cpu-time during Waiting for GVL. BUT, we may chose to push an
1860
1898
  // extra sample to represent the period prior to Waiting for GVL. To support that, we retrieve the current
@@ -1880,10 +1918,10 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
1880
1918
  // need to take when sampling cpu/wall-time for a thread that's in the "Waiting for GVL" state.
1881
1919
  __attribute__((warn_unused_result))
1882
1920
  static bool handle_gvl_waiting(
1883
- struct thread_context_collector_state *state,
1921
+ thread_context_collector_state *state,
1884
1922
  VALUE thread_being_sampled,
1885
1923
  VALUE stack_from_thread,
1886
- struct per_thread_context *thread_context,
1924
+ per_thread_context *thread_context,
1887
1925
  sampling_buffer* sampling_buffer,
1888
1926
  long current_cpu_time_ns
1889
1927
  ) {
@@ -1979,40 +2017,62 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
1979
2017
  static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
1980
2018
  ENFORCE_THREAD(thread);
1981
2019
 
2020
+ debug_enter_unsafe_context();
2021
+
1982
2022
  thread_context_collector_on_gvl_waiting(thread_from_thread_object(thread));
2023
+
2024
+ debug_leave_unsafe_context();
2025
+
1983
2026
  return Qnil;
1984
2027
  }
1985
2028
 
1986
2029
  static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
1987
2030
  ENFORCE_THREAD(thread);
1988
2031
 
2032
+ debug_enter_unsafe_context();
2033
+
1989
2034
  intptr_t gvl_waiting_at = gvl_profiling_state_thread_object_get(thread);
2035
+
2036
+ debug_leave_unsafe_context();
2037
+
1990
2038
  return LONG2NUM(gvl_waiting_at);
1991
2039
  }
1992
2040
 
1993
2041
  static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread) {
1994
2042
  ENFORCE_THREAD(thread);
1995
2043
 
1996
- return thread_context_collector_on_gvl_running(thread_from_thread_object(thread)) == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
2044
+ debug_enter_unsafe_context();
2045
+
2046
+ VALUE result = thread_context_collector_on_gvl_running(thread_from_thread_object(thread)) == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
2047
+
2048
+ debug_leave_unsafe_context();
2049
+
2050
+ return result;
1997
2051
  }
1998
2052
 
1999
2053
  static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
2000
2054
  ENFORCE_THREAD(thread);
2001
2055
 
2002
- return thread_context_collector_sample_after_gvl_running(
2056
+ debug_enter_unsafe_context();
2057
+
2058
+ VALUE result = thread_context_collector_sample_after_gvl_running(
2003
2059
  collector_instance,
2004
2060
  thread,
2005
2061
  monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
2006
2062
  );
2063
+
2064
+ debug_leave_unsafe_context();
2065
+
2066
+ return result;
2007
2067
  }
2008
2068
 
2009
2069
  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
2070
  ENFORCE_THREAD(thread);
2011
2071
 
2012
- struct thread_context_collector_state *state;
2013
- TypedData_Get_Struct(collector_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
2072
+ thread_context_collector_state *state;
2073
+ TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
2014
2074
 
2015
- struct per_thread_context *thread_context = get_context_for(thread, state);
2075
+ per_thread_context *thread_context = get_context_for(thread, state);
2016
2076
  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
2077
 
2018
2078
  thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
@@ -2022,11 +2082,45 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
2022
2082
 
2023
2083
  #else
2024
2084
  static bool handle_gvl_waiting(
2025
- DDTRACE_UNUSED struct thread_context_collector_state *state,
2085
+ DDTRACE_UNUSED thread_context_collector_state *state,
2026
2086
  DDTRACE_UNUSED VALUE thread_being_sampled,
2027
2087
  DDTRACE_UNUSED VALUE stack_from_thread,
2028
- DDTRACE_UNUSED struct per_thread_context *thread_context,
2088
+ DDTRACE_UNUSED per_thread_context *thread_context,
2029
2089
  DDTRACE_UNUSED sampling_buffer* sampling_buffer,
2030
2090
  DDTRACE_UNUSED long current_cpu_time_ns
2031
2091
  ) { return false; }
2032
2092
  #endif // NO_GVL_INSTRUMENTATION
2093
+
2094
+ #define MAX_SAFE_LOOKUP_SIZE 16
2095
+
2096
+ typedef struct { VALUE lookup_key; VALUE result; } safe_lookup_hash_state;
2097
+
2098
+ static int safe_lookup_hash_iterate(VALUE key, VALUE value, VALUE state_ptr) {
2099
+ safe_lookup_hash_state *state = (safe_lookup_hash_state *) state_ptr;
2100
+
2101
+ if (key == state->lookup_key) {
2102
+ state->result = value;
2103
+ return ST_STOP;
2104
+ }
2105
+
2106
+ return ST_CONTINUE;
2107
+ }
2108
+
2109
+ // This method exists because we need to look up a hash during sampling, but we don't want to invoke any
2110
+ // Ruby code as a side effect. To be able to look up by hash, `rb_hash_lookup` calls `#hash` on the key,
2111
+ // which we want to avoid.
2112
+ // Thus, instead, we opt to just iterate through the hash and check if we can find what we're looking for.
2113
+ //
2114
+ // To avoid having too much overhead here we only iterate in hashes up to MAX_SAFE_LOOKUP_SIZE.
2115
+ // Note that we don't even try to iterate if the hash is bigger -- this is to avoid flaky behavior where
2116
+ // depending on the internal storage order of the hash we may or not find the key, and instead we always
2117
+ // enforce the size.
2118
+ static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key) {
2119
+ if (!RB_TYPE_P(hash, T_HASH) || RHASH_SIZE(hash) > MAX_SAFE_LOOKUP_SIZE) return Qnil;
2120
+
2121
+ safe_lookup_hash_state state = {.lookup_key = key, .result = Qnil};
2122
+
2123
+ rb_hash_foreach(hash, safe_lookup_hash_iterate, (VALUE) &state);
2124
+
2125
+ return state.result;
2126
+ }