datadog 2.7.1 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +69 -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 +259 -132
  10. data/ext/datadog_profiling_native_extension/extconf.rb +0 -8
  11. data/ext/datadog_profiling_native_extension/heap_recorder.c +11 -89
  12. data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
  13. data/ext/datadog_profiling_native_extension/http_transport.c +4 -4
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +4 -1
  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 +54 -88
  19. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
  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/ext/libdatadog_extconf_helpers.rb +1 -1
  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 +1 -8
  28. data/lib/datadog/appsec/context.rb +54 -0
  29. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +73 -0
  30. data/lib/datadog/appsec/contrib/active_record/integration.rb +41 -0
  31. data/lib/datadog/appsec/contrib/active_record/patcher.rb +53 -0
  32. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +6 -6
  33. data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +4 -4
  34. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +19 -28
  35. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +5 -5
  36. data/lib/datadog/appsec/contrib/rack/gateway/response.rb +3 -3
  37. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +64 -96
  38. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +10 -10
  39. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +5 -5
  40. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +6 -6
  41. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +10 -11
  42. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -49
  43. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +21 -32
  44. data/lib/datadog/appsec/contrib/rails/patcher.rb +1 -1
  45. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +6 -6
  46. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +41 -63
  47. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +2 -2
  48. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +5 -5
  49. data/lib/datadog/appsec/event.rb +6 -6
  50. data/lib/datadog/appsec/ext.rb +3 -1
  51. data/lib/datadog/appsec/monitor/gateway/watcher.rb +22 -32
  52. data/lib/datadog/appsec/monitor/reactive/set_user.rb +5 -5
  53. data/lib/datadog/appsec/processor/context.rb +2 -2
  54. data/lib/datadog/appsec/processor/rule_loader.rb +0 -3
  55. data/lib/datadog/appsec/remote.rb +1 -3
  56. data/lib/datadog/appsec/response.rb +7 -11
  57. data/lib/datadog/appsec.rb +6 -5
  58. data/lib/datadog/auto_instrument.rb +3 -0
  59. data/lib/datadog/core/configuration/agent_settings_resolver.rb +39 -11
  60. data/lib/datadog/core/configuration/components.rb +20 -2
  61. data/lib/datadog/core/configuration/settings.rb +10 -0
  62. data/lib/datadog/core/configuration.rb +10 -2
  63. data/lib/datadog/{tracing → core}/contrib/rails/utils.rb +1 -3
  64. data/lib/datadog/core/crashtracking/component.rb +1 -3
  65. data/lib/datadog/core/remote/client/capabilities.rb +6 -0
  66. data/lib/datadog/core/remote/client.rb +65 -59
  67. data/lib/datadog/core/telemetry/component.rb +9 -3
  68. data/lib/datadog/core/telemetry/event.rb +87 -3
  69. data/lib/datadog/core/telemetry/ext.rb +1 -0
  70. data/lib/datadog/core/telemetry/logging.rb +2 -2
  71. data/lib/datadog/core/telemetry/metric.rb +22 -0
  72. data/lib/datadog/core/telemetry/worker.rb +33 -0
  73. data/lib/datadog/di/base.rb +115 -0
  74. data/lib/datadog/di/code_tracker.rb +11 -7
  75. data/lib/datadog/di/component.rb +21 -11
  76. data/lib/datadog/di/configuration/settings.rb +11 -1
  77. data/lib/datadog/di/contrib/active_record.rb +1 -0
  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 +111 -20
  82. data/lib/datadog/di/preload.rb +18 -0
  83. data/lib/datadog/di/probe.rb +11 -1
  84. data/lib/datadog/di/probe_builder.rb +1 -0
  85. data/lib/datadog/di/probe_manager.rb +8 -5
  86. data/lib/datadog/di/probe_notification_builder.rb +27 -7
  87. data/lib/datadog/di/probe_notifier_worker.rb +5 -6
  88. data/lib/datadog/di/remote.rb +124 -0
  89. data/lib/datadog/di/serializer.rb +14 -7
  90. data/lib/datadog/di/transport.rb +3 -5
  91. data/lib/datadog/di/utils.rb +7 -0
  92. data/lib/datadog/di.rb +23 -62
  93. data/lib/datadog/kit/appsec/events.rb +3 -3
  94. data/lib/datadog/kit/identity.rb +4 -4
  95. data/lib/datadog/profiling/component.rb +59 -69
  96. data/lib/datadog/profiling/http_transport.rb +1 -26
  97. data/lib/datadog/tracing/configuration/settings.rb +4 -8
  98. data/lib/datadog/tracing/contrib/action_cable/integration.rb +5 -2
  99. data/lib/datadog/tracing/contrib/action_mailer/integration.rb +6 -2
  100. data/lib/datadog/tracing/contrib/action_pack/integration.rb +5 -2
  101. data/lib/datadog/tracing/contrib/action_view/integration.rb +5 -2
  102. data/lib/datadog/tracing/contrib/active_job/integration.rb +5 -2
  103. data/lib/datadog/tracing/contrib/active_record/integration.rb +6 -2
  104. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +3 -1
  105. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +3 -1
  106. data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +16 -4
  107. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +10 -0
  108. data/lib/datadog/tracing/contrib/active_support/integration.rb +5 -2
  109. data/lib/datadog/tracing/contrib/auto_instrument.rb +2 -2
  110. data/lib/datadog/tracing/contrib/aws/integration.rb +3 -0
  111. data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +3 -0
  112. data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +4 -0
  113. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
  114. data/lib/datadog/tracing/contrib/httprb/integration.rb +3 -0
  115. data/lib/datadog/tracing/contrib/kafka/integration.rb +3 -0
  116. data/lib/datadog/tracing/contrib/mongodb/integration.rb +3 -0
  117. data/lib/datadog/tracing/contrib/opensearch/integration.rb +3 -0
  118. data/lib/datadog/tracing/contrib/presto/integration.rb +3 -0
  119. data/lib/datadog/tracing/contrib/rack/integration.rb +2 -2
  120. data/lib/datadog/tracing/contrib/rails/framework.rb +2 -2
  121. data/lib/datadog/tracing/contrib/rails/patcher.rb +1 -1
  122. data/lib/datadog/tracing/contrib/rest_client/integration.rb +3 -0
  123. data/lib/datadog/tracing/span.rb +12 -4
  124. data/lib/datadog/tracing/span_event.rb +123 -3
  125. data/lib/datadog/tracing/span_operation.rb +6 -0
  126. data/lib/datadog/tracing/transport/serializable_trace.rb +24 -6
  127. data/lib/datadog/version.rb +2 -2
  128. data/lib/datadog.rb +3 -0
  129. metadata +30 -17
  130. data/lib/datadog/appsec/processor/actions.rb +0 -49
  131. data/lib/datadog/appsec/reactive/operation.rb +0 -68
  132. data/lib/datadog/appsec/scope.rb +0 -58
  133. 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,70 +204,77 @@ 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,
227
228
  ddog_CharSlice *ruby_vm_type,
228
229
  ddog_CharSlice *class_name,
229
- bool is_gvl_waiting_state
230
+ bool is_gvl_waiting_state,
231
+ bool is_safe_to_allocate_objects
230
232
  );
231
233
  static VALUE _native_thread_list(VALUE self);
232
- static struct per_thread_context *get_or_create_context_for(VALUE thread, struct thread_context_collector_state *state);
233
- static struct per_thread_context *get_context_for(VALUE thread, struct thread_context_collector_state *state);
234
- static void initialize_context(VALUE thread, struct per_thread_context *thread_context, struct thread_context_collector_state *state);
235
- 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);
236
238
  static VALUE _native_inspect(VALUE self, VALUE collector_instance);
237
- 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);
238
240
  static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value_context, st_data_t result_hash);
239
- static VALUE stats_as_ruby_hash(struct thread_context_collector_state *state);
240
- static VALUE gc_tracking_as_ruby_hash(struct thread_context_collector_state *state);
241
- 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);
242
244
  static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, st_data_t _argument);
243
245
  static VALUE _native_per_thread_context(VALUE self, VALUE collector_instance);
244
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);
245
- static long cpu_time_now_ns(struct per_thread_context *thread_context);
247
+ static long cpu_time_now_ns(per_thread_context *thread_context);
246
248
  static long thread_id_for(VALUE thread);
247
249
  static VALUE _native_stats(VALUE self, VALUE collector_instance);
248
250
  static VALUE _native_gc_tracking(VALUE self, VALUE collector_instance);
249
- static void trace_identifiers_for(struct thread_context_collector_state *state, VALUE thread, struct trace_identifiers *trace_identifiers_result);
251
+ static void trace_identifiers_for(
252
+ thread_context_collector_state *state,
253
+ VALUE thread,
254
+ trace_identifiers *trace_identifiers_result,
255
+ bool is_safe_to_allocate_objects
256
+ );
250
257
  static bool should_collect_resource(VALUE root_span);
251
258
  static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
252
- static VALUE thread_list(struct thread_context_collector_state *state);
259
+ static VALUE thread_list(thread_context_collector_state *state);
253
260
  static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object);
254
261
  static VALUE _native_new_empty_thread(VALUE self);
255
262
  static ddog_CharSlice ruby_value_type_to_class_name(enum ruby_value_type type);
256
263
  static void ddtrace_otel_trace_identifiers_for(
257
- struct thread_context_collector_state *state,
264
+ thread_context_collector_state *state,
258
265
  VALUE *active_trace,
259
266
  VALUE *root_span,
260
267
  VALUE *numeric_span_id,
261
268
  VALUE active_span,
262
- VALUE otel_values
269
+ VALUE otel_values,
270
+ bool is_safe_to_allocate_objects
263
271
  );
264
272
  static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples);
265
273
  static bool handle_gvl_waiting(
266
- struct thread_context_collector_state *state,
274
+ thread_context_collector_state *state,
267
275
  VALUE thread_being_sampled,
268
276
  VALUE stack_from_thread,
269
- struct per_thread_context *thread_context,
277
+ per_thread_context *thread_context,
270
278
  sampling_buffer* sampling_buffer,
271
279
  long current_cpu_time_ns
272
280
  );
@@ -276,12 +284,14 @@ static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread);
276
284
  static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
277
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);
278
286
  static void otel_without_ddtrace_trace_identifiers_for(
279
- struct thread_context_collector_state *state,
287
+ thread_context_collector_state *state,
280
288
  VALUE thread,
281
- struct trace_identifiers *trace_identifiers_result
289
+ trace_identifiers *trace_identifiers_result,
290
+ bool is_safe_to_allocate_objects
282
291
  );
283
- 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);
284
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);
285
295
 
286
296
  void collectors_thread_context_init(VALUE profiling_module) {
287
297
  VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
@@ -302,11 +312,11 @@ void collectors_thread_context_init(VALUE profiling_module) {
302
312
  rb_define_singleton_method(collectors_thread_context_class, "_native_initialize", _native_initialize, -1);
303
313
  rb_define_singleton_method(collectors_thread_context_class, "_native_inspect", _native_inspect, 1);
304
314
  rb_define_singleton_method(collectors_thread_context_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
305
- rb_define_singleton_method(testing_module, "_native_sample", _native_sample, 2);
315
+ rb_define_singleton_method(testing_module, "_native_sample", _native_sample, 3);
306
316
  rb_define_singleton_method(testing_module, "_native_sample_allocation", _native_sample_allocation, 3);
307
317
  rb_define_singleton_method(testing_module, "_native_on_gc_start", _native_on_gc_start, 1);
308
318
  rb_define_singleton_method(testing_module, "_native_on_gc_finish", _native_on_gc_finish, 1);
309
- 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);
310
320
  rb_define_singleton_method(testing_module, "_native_thread_list", _native_thread_list, 0);
311
321
  rb_define_singleton_method(testing_module, "_native_per_thread_context", _native_per_thread_context, 1);
312
322
  rb_define_singleton_method(testing_module, "_native_stats", _native_stats, 1);
@@ -347,7 +357,7 @@ void collectors_thread_context_init(VALUE profiling_module) {
347
357
  gc_profiling_init();
348
358
  }
349
359
 
350
- // 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
351
361
  // See also https://github.com/ruby/ruby/blob/master/doc/extension.rdoc for how this works
352
362
  static const rb_data_type_t thread_context_collector_typed_data = {
353
363
  .wrap_struct_name = "Datadog::Profiling::Collectors::ThreadContext",
@@ -363,7 +373,7 @@ static const rb_data_type_t thread_context_collector_typed_data = {
363
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,
364
374
  // so that they don't get garbage collected
365
375
  static void thread_context_collector_typed_data_mark(void *state_ptr) {
366
- 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;
367
377
 
368
378
  // Update this when modifying state struct
369
379
  rb_gc_mark(state->recorder_instance);
@@ -374,7 +384,7 @@ static void thread_context_collector_typed_data_mark(void *state_ptr) {
374
384
  }
375
385
 
376
386
  static void thread_context_collector_typed_data_free(void *state_ptr) {
377
- 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;
378
388
 
379
389
  // Update this when modifying state struct
380
390
 
@@ -399,13 +409,13 @@ static int hash_map_per_thread_context_mark(st_data_t key_thread, DDTRACE_UNUSED
399
409
 
400
410
  // Used to clear each of the per_thread_contexts inside the hash_map_per_thread_context
401
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) {
402
- 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;
403
413
  free_context(thread_context);
404
414
  return ST_CONTINUE;
405
415
  }
406
416
 
407
417
  static VALUE _native_new(VALUE klass) {
408
- 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));
409
419
 
410
420
  // Note: Any exceptions raised from this note until the TypedData_Wrap_Struct call will lead to the state memory
411
421
  // being leaked.
@@ -461,8 +471,8 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
461
471
  ENFORCE_BOOLEAN(timeline_enabled);
462
472
  ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
463
473
 
464
- struct thread_context_collector_state *state;
465
- 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);
466
476
 
467
477
  // Update this when modifying state struct
468
478
  state->max_frames = sampling_buffer_check_max_frames(NUM2INT(max_frames));
@@ -496,40 +506,63 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
496
506
 
497
507
  // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
498
508
  // It SHOULD NOT be used for other purposes.
499
- 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
+
500
512
  if (!is_thread_alive(profiler_overhead_stack_thread)) rb_raise(rb_eArgError, "Unexpected: profiler_overhead_stack_thread is not alive");
501
513
 
514
+ if (allow_exception == Qfalse) debug_enter_unsafe_context();
515
+
502
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
+
503
520
  return Qtrue;
504
521
  }
505
522
 
506
523
  // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
507
524
  // It SHOULD NOT be used for other purposes.
508
525
  static VALUE _native_on_gc_start(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
526
+ debug_enter_unsafe_context();
527
+
509
528
  thread_context_collector_on_gc_start(collector_instance);
529
+
530
+ debug_leave_unsafe_context();
531
+
510
532
  return Qtrue;
511
533
  }
512
534
 
513
535
  // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
514
536
  // It SHOULD NOT be used for other purposes.
515
537
  static VALUE _native_on_gc_finish(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
538
+ debug_enter_unsafe_context();
539
+
516
540
  (void) !thread_context_collector_on_gc_finish(collector_instance);
541
+
542
+ debug_leave_unsafe_context();
543
+
517
544
  return Qtrue;
518
545
  }
519
546
 
520
547
  // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
521
548
  // It SHOULD NOT be used for other purposes.
522
- 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) {
523
550
  ENFORCE_BOOLEAN(reset_monotonic_to_system_state);
551
+ ENFORCE_BOOLEAN(allow_exception);
524
552
 
525
- struct thread_context_collector_state *state;
526
- 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);
527
555
 
528
556
  if (reset_monotonic_to_system_state == Qtrue) {
529
557
  state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
530
558
  }
531
559
 
560
+ if (allow_exception == Qfalse) debug_enter_unsafe_context();
561
+
532
562
  thread_context_collector_sample_after_gc(collector_instance);
563
+
564
+ if (allow_exception == Qfalse) debug_leave_unsafe_context();
565
+
533
566
  return Qtrue;
534
567
  }
535
568
 
@@ -544,11 +577,11 @@ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_
544
577
  // The `profiler_overhead_stack_thread` is used to attribute the profiler overhead to a stack borrowed from a different thread
545
578
  // (belonging to ddtrace), so that the overhead is visible in the profile rather than blamed on user code.
546
579
  void thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns, VALUE profiler_overhead_stack_thread) {
547
- struct thread_context_collector_state *state;
548
- 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);
549
582
 
550
583
  VALUE current_thread = rb_thread_current();
551
- 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);
552
585
  long cpu_time_at_sample_start_for_current_thread = cpu_time_now_ns(current_thread_context);
553
586
 
554
587
  VALUE threads = thread_list(state);
@@ -556,7 +589,7 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
556
589
  const long thread_count = RARRAY_LEN(threads);
557
590
  for (long i = 0; i < thread_count; i++) {
558
591
  VALUE thread = RARRAY_AREF(threads, i);
559
- 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);
560
593
 
561
594
  // We account for cpu-time for the current thread in a different way -- we use the cpu-time at sampling start, to avoid
562
595
  // blaming the time the profiler took on whatever's running on the thread right now
@@ -592,10 +625,10 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
592
625
  }
593
626
 
594
627
  static void update_metrics_and_sample(
595
- struct thread_context_collector_state *state,
628
+ thread_context_collector_state *state,
596
629
  VALUE thread_being_sampled,
597
630
  VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
598
- struct per_thread_context *thread_context,
631
+ per_thread_context *thread_context,
599
632
  sampling_buffer* sampling_buffer,
600
633
  long current_cpu_time_ns,
601
634
  long current_monotonic_wall_time_ns
@@ -647,7 +680,8 @@ static void update_metrics_and_sample(
647
680
  current_monotonic_wall_time_ns,
648
681
  NULL,
649
682
  NULL,
650
- is_gvl_waiting_state
683
+ is_gvl_waiting_state,
684
+ /* is_safe_to_allocate_objects: */ true // We called from a context that's safe to run any regular code, including allocations
651
685
  );
652
686
  }
653
687
 
@@ -662,12 +696,12 @@ static void update_metrics_and_sample(
662
696
  // Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
663
697
  // Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
664
698
  void thread_context_collector_on_gc_start(VALUE self_instance) {
665
- struct thread_context_collector_state *state;
699
+ thread_context_collector_state *state;
666
700
  if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return;
667
701
  // This should never fail the the above check passes
668
- 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);
669
703
 
670
- 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);
671
705
 
672
706
  // If there was no previously-existing context for this thread, we won't allocate one (see safety). For now we just drop
673
707
  // the GC sample, under the assumption that "a thread that is so new that we never sampled it even once before it triggers
@@ -695,12 +729,12 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
695
729
  // Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
696
730
  __attribute__((warn_unused_result))
697
731
  bool thread_context_collector_on_gc_finish(VALUE self_instance) {
698
- struct thread_context_collector_state *state;
732
+ thread_context_collector_state *state;
699
733
  if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
700
734
  // This should never fail the the above check passes
701
- 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);
702
736
 
703
- 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);
704
738
 
705
739
  // If there was no previously-existing context for this thread, we won't allocate one (see safety). We keep a metric for
706
740
  // how often this happens -- see on_gc_start.
@@ -773,8 +807,8 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
773
807
  // Assumption 3: Unlike `on_gc_start` and `on_gc_finish`, this method is allowed to allocate memory as needed.
774
808
  // Assumption 4: This function is called from the main Ractor (if Ruby has support for Ractors).
775
809
  VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
776
- struct thread_context_collector_state *state;
777
- 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);
778
812
 
779
813
  if (state->gc_tracking.wall_time_at_previous_gc_ns == INVALID_TIME) {
780
814
  rb_raise(rb_eRuntimeError, "BUG: Unexpected call to sample_after_gc without valid GC information available");
@@ -823,17 +857,20 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
823
857
  }
824
858
 
825
859
  static void trigger_sample_for_thread(
826
- struct thread_context_collector_state *state,
860
+ thread_context_collector_state *state,
827
861
  VALUE thread,
828
862
  VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
829
- struct per_thread_context *thread_context,
863
+ per_thread_context *thread_context,
830
864
  sampling_buffer* sampling_buffer,
831
865
  sample_values values,
832
866
  long current_monotonic_wall_time_ns,
833
867
  // These two labels are only used for allocation profiling; @ivoanjo: may want to refactor this at some point?
834
868
  ddog_CharSlice *ruby_vm_type,
835
869
  ddog_CharSlice *class_name,
836
- bool is_gvl_waiting_state
870
+ bool is_gvl_waiting_state,
871
+ // If the Ruby VM is at a state that can allocate objects safely, or not. Added for allocation profiling: we're not
872
+ // allowed to allocate objects (or raise exceptions) when inside the NEWOBJ tracepoint.
873
+ bool is_safe_to_allocate_objects
837
874
  ) {
838
875
  int max_label_count =
839
876
  1 + // thread id
@@ -871,12 +908,12 @@ static void trigger_sample_for_thread(
871
908
  };
872
909
  }
873
910
 
874
- struct trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
875
- trace_identifiers_for(state, thread, &trace_identifiers_result);
911
+ trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
912
+ trace_identifiers_for(state, thread, &trace_identifiers_result, is_safe_to_allocate_objects);
876
913
 
877
914
  if (!trace_identifiers_result.valid && state->otel_context_enabled != OTEL_CONTEXT_ENABLED_FALSE) {
878
915
  // If we couldn't get something with ddtrace, let's see if we can get some trace identifiers from opentelemetry directly
879
- otel_without_ddtrace_trace_identifiers_for(state, thread, &trace_identifiers_result);
916
+ otel_without_ddtrace_trace_identifiers_for(state, thread, &trace_identifiers_result, is_safe_to_allocate_objects);
880
917
  }
881
918
 
882
919
  if (trace_identifiers_result.valid) {
@@ -970,18 +1007,24 @@ static void trigger_sample_for_thread(
970
1007
  // It SHOULD NOT be used for other purposes.
971
1008
  static VALUE _native_thread_list(DDTRACE_UNUSED VALUE _self) {
972
1009
  VALUE result = rb_ary_new();
1010
+
1011
+ debug_enter_unsafe_context();
1012
+
973
1013
  ddtrace_thread_list(result);
1014
+
1015
+ debug_leave_unsafe_context();
1016
+
974
1017
  return result;
975
1018
  }
976
1019
 
977
- static struct per_thread_context *get_or_create_context_for(VALUE thread, struct thread_context_collector_state *state) {
978
- 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;
979
1022
  st_data_t value_context = 0;
980
1023
 
981
1024
  if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
982
- thread_context = (struct per_thread_context*) value_context;
1025
+ thread_context = (per_thread_context*) value_context;
983
1026
  } else {
984
- thread_context = ruby_xcalloc(1, sizeof(struct per_thread_context));
1027
+ thread_context = ruby_xcalloc(1, sizeof(per_thread_context));
985
1028
  initialize_context(thread, thread_context, state);
986
1029
  st_insert(state->hash_map_per_thread_context, (st_data_t) thread, (st_data_t) thread_context);
987
1030
  }
@@ -989,12 +1032,12 @@ static struct per_thread_context *get_or_create_context_for(VALUE thread, struct
989
1032
  return thread_context;
990
1033
  }
991
1034
 
992
- static struct per_thread_context *get_context_for(VALUE thread, struct thread_context_collector_state *state) {
993
- 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;
994
1037
  st_data_t value_context = 0;
995
1038
 
996
1039
  if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
997
- thread_context = (struct per_thread_context*) value_context;
1040
+ thread_context = (per_thread_context*) value_context;
998
1041
  }
999
1042
 
1000
1043
  return thread_context;
@@ -1021,7 +1064,7 @@ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
1021
1064
  return strncmp(invoke_file + invoke_file_len - logging_gem_path_len, LOGGING_GEM_PATH, logging_gem_path_len) == 0;
1022
1065
  }
1023
1066
 
1024
- 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) {
1025
1068
  thread_context->sampling_buffer = sampling_buffer_new(state->max_frames, state->locations);
1026
1069
 
1027
1070
  snprintf(thread_context->thread_id, THREAD_ID_LIMIT_CHARS, "%"PRIu64" (%lu)", native_thread_id_for(thread), (unsigned long) thread_id_for(thread));
@@ -1078,14 +1121,14 @@ static void initialize_context(VALUE thread, struct per_thread_context *thread_c
1078
1121
  #endif
1079
1122
  }
1080
1123
 
1081
- static void free_context(struct per_thread_context* thread_context) {
1124
+ static void free_context(per_thread_context* thread_context) {
1082
1125
  sampling_buffer_free(thread_context->sampling_buffer);
1083
1126
  ruby_xfree(thread_context);
1084
1127
  }
1085
1128
 
1086
1129
  static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
1087
- struct thread_context_collector_state *state;
1088
- 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);
1089
1132
 
1090
1133
  VALUE result = rb_str_new2(" (native state)");
1091
1134
 
@@ -1113,7 +1156,7 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
1113
1156
  return result;
1114
1157
  }
1115
1158
 
1116
- 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) {
1117
1160
  VALUE result = rb_hash_new();
1118
1161
  st_foreach(state->hash_map_per_thread_context, per_thread_context_as_ruby_hash, result);
1119
1162
  return result;
@@ -1121,7 +1164,7 @@ static VALUE per_thread_context_st_table_as_ruby_hash(struct thread_context_coll
1121
1164
 
1122
1165
  static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value_context, st_data_t result_hash) {
1123
1166
  VALUE thread = (VALUE) key_thread;
1124
- struct per_thread_context *thread_context = (struct per_thread_context*) value_context;
1167
+ per_thread_context *thread_context = (per_thread_context*) value_context;
1125
1168
  VALUE result = (VALUE) result_hash;
1126
1169
  VALUE context_as_hash = rb_hash_new();
1127
1170
  rb_hash_aset(result, thread, context_as_hash);
@@ -1146,7 +1189,7 @@ static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value
1146
1189
  return ST_CONTINUE;
1147
1190
  }
1148
1191
 
1149
- static VALUE stats_as_ruby_hash(struct thread_context_collector_state *state) {
1192
+ static VALUE stats_as_ruby_hash(thread_context_collector_state *state) {
1150
1193
  // Update this when modifying state struct (stats inner struct)
1151
1194
  VALUE stats_as_hash = rb_hash_new();
1152
1195
  VALUE arguments[] = {
@@ -1157,7 +1200,7 @@ static VALUE stats_as_ruby_hash(struct thread_context_collector_state *state) {
1157
1200
  return stats_as_hash;
1158
1201
  }
1159
1202
 
1160
- 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) {
1161
1204
  // Update this when modifying state struct (gc_tracking inner struct)
1162
1205
  VALUE result = rb_hash_new();
1163
1206
  VALUE arguments[] = {
@@ -1170,13 +1213,13 @@ static VALUE gc_tracking_as_ruby_hash(struct thread_context_collector_state *sta
1170
1213
  return result;
1171
1214
  }
1172
1215
 
1173
- 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) {
1174
1217
  st_foreach(state->hash_map_per_thread_context, remove_if_dead_thread, 0 /* unused */);
1175
1218
  }
1176
1219
 
1177
1220
  static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, DDTRACE_UNUSED st_data_t _argument) {
1178
1221
  VALUE thread = (VALUE) key_thread;
1179
- struct per_thread_context* thread_context = (struct per_thread_context*) value_context;
1222
+ per_thread_context* thread_context = (per_thread_context*) value_context;
1180
1223
 
1181
1224
  if (is_thread_alive(thread)) return ST_CONTINUE;
1182
1225
 
@@ -1189,8 +1232,8 @@ static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context,
1189
1232
  //
1190
1233
  // Returns the whole contents of the per_thread_context structs being tracked.
1191
1234
  static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
1192
- struct thread_context_collector_state *state;
1193
- 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);
1194
1237
 
1195
1238
  return per_thread_context_st_table_as_ruby_hash(state);
1196
1239
  }
@@ -1235,7 +1278,7 @@ static long update_time_since_previous_sample(long *time_at_previous_sample_ns,
1235
1278
  }
1236
1279
 
1237
1280
  // Safety: This function is assumed never to raise exceptions by callers
1238
- static long cpu_time_now_ns(struct per_thread_context *thread_context) {
1281
+ static long cpu_time_now_ns(per_thread_context *thread_context) {
1239
1282
  thread_cpu_time cpu_time = thread_cpu_time_for(thread_context->thread_cpu_time_id);
1240
1283
 
1241
1284
  if (!cpu_time.valid) {
@@ -1273,8 +1316,8 @@ VALUE enforce_thread_context_collector_instance(VALUE object) {
1273
1316
  // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
1274
1317
  // It SHOULD NOT be used for other purposes.
1275
1318
  static VALUE _native_stats(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
1276
- struct thread_context_collector_state *state;
1277
- 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);
1278
1321
 
1279
1322
  return stats_as_ruby_hash(state);
1280
1323
  }
@@ -1282,14 +1325,19 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE _self, VALUE collector_instance)
1282
1325
  // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
1283
1326
  // It SHOULD NOT be used for other purposes.
1284
1327
  static VALUE _native_gc_tracking(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
1285
- struct thread_context_collector_state *state;
1286
- 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);
1287
1330
 
1288
1331
  return gc_tracking_as_ruby_hash(state);
1289
1332
  }
1290
1333
 
1291
1334
  // Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
1292
- static void trace_identifiers_for(struct thread_context_collector_state *state, VALUE thread, struct trace_identifiers *trace_identifiers_result) {
1335
+ static void trace_identifiers_for(
1336
+ thread_context_collector_state *state,
1337
+ VALUE thread,
1338
+ trace_identifiers *trace_identifiers_result,
1339
+ bool is_safe_to_allocate_objects
1340
+ ) {
1293
1341
  if (state->otel_context_enabled == OTEL_CONTEXT_ENABLED_ONLY) return;
1294
1342
  if (state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY) return;
1295
1343
 
@@ -1308,7 +1356,9 @@ static void trace_identifiers_for(struct thread_context_collector_state *state,
1308
1356
 
1309
1357
  VALUE numeric_span_id = Qnil;
1310
1358
 
1311
- if (otel_values != Qnil) ddtrace_otel_trace_identifiers_for(state, &active_trace, &root_span, &numeric_span_id, active_span, otel_values);
1359
+ if (otel_values != Qnil) {
1360
+ ddtrace_otel_trace_identifiers_for(state, &active_trace, &root_span, &numeric_span_id, active_span, otel_values, is_safe_to_allocate_objects);
1361
+ }
1312
1362
 
1313
1363
  if (root_span == Qnil || (active_span == Qnil && numeric_span_id == Qnil)) return;
1314
1364
 
@@ -1365,8 +1415,8 @@ static bool should_collect_resource(VALUE root_span) {
1365
1415
  // Assumption: This method gets called BEFORE restarting profiling -- e.g. there are no components attempting to
1366
1416
  // trigger samples at the same time.
1367
1417
  static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
1368
- struct thread_context_collector_state *state;
1369
- 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);
1370
1420
 
1371
1421
  // Release all context memory before clearing the existing context
1372
1422
  st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
@@ -1380,7 +1430,7 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector
1380
1430
  return Qtrue;
1381
1431
  }
1382
1432
 
1383
- static VALUE thread_list(struct thread_context_collector_state *state) {
1433
+ static VALUE thread_list(thread_context_collector_state *state) {
1384
1434
  VALUE result = state->thread_list_buffer;
1385
1435
  rb_ary_clear(result);
1386
1436
  ddtrace_thread_list(result);
@@ -1388,8 +1438,8 @@ static VALUE thread_list(struct thread_context_collector_state *state) {
1388
1438
  }
1389
1439
 
1390
1440
  void thread_context_collector_sample_allocation(VALUE self_instance, unsigned int sample_weight, VALUE new_object) {
1391
- struct thread_context_collector_state *state;
1392
- 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);
1393
1443
 
1394
1444
  VALUE current_thread = rb_thread_current();
1395
1445
 
@@ -1462,7 +1512,7 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
1462
1512
 
1463
1513
  track_object(state->recorder_instance, new_object, sample_weight, optional_class_name);
1464
1514
 
1465
- 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);
1466
1516
 
1467
1517
  trigger_sample_for_thread(
1468
1518
  state,
@@ -1474,14 +1524,20 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
1474
1524
  INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
1475
1525
  &ruby_vm_type,
1476
1526
  optional_class_name,
1477
- false
1527
+ /* is_gvl_waiting_state: */ false,
1528
+ /* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
1478
1529
  );
1479
1530
  }
1480
1531
 
1481
1532
  // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
1482
1533
  // It SHOULD NOT be used for other purposes.
1483
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
+
1484
1537
  thread_context_collector_sample_allocation(collector_instance, NUM2UINT(sample_weight), new_object);
1538
+
1539
+ debug_leave_unsafe_context();
1540
+
1485
1541
  return Qtrue;
1486
1542
  }
1487
1543
 
@@ -1529,11 +1585,18 @@ static VALUE read_otel_current_span_key_const(DDTRACE_UNUSED VALUE _unused) {
1529
1585
  return rb_const_get(trace_module, rb_intern("CURRENT_SPAN_KEY"));
1530
1586
  }
1531
1587
 
1532
- static VALUE get_otel_current_span_key(struct thread_context_collector_state *state) {
1588
+ static VALUE get_otel_current_span_key(thread_context_collector_state *state, bool is_safe_to_allocate_objects) {
1533
1589
  if (state->otel_current_span_key == Qtrue) { // Qtrue means we haven't tried to extract it yet
1590
+ if (!is_safe_to_allocate_objects) {
1591
+ // Calling read_otel_current_span_key_const below can trigger exceptions and arbitrary Ruby code running (e.g.
1592
+ // `const_missing`, etc). Not safe to call in this situation, so we just skip otel info for this sample.
1593
+ return Qnil;
1594
+ }
1595
+
1534
1596
  // If this fails, we want to fail gracefully, rather than raise an exception (e.g. if the opentelemetry gem
1535
1597
  // gets refactored, we should not fall on our face)
1536
1598
  VALUE span_key = rb_protect(read_otel_current_span_key_const, Qnil, NULL);
1599
+ rb_set_errinfo(Qnil); // **Clear any pending exception after ignoring it**
1537
1600
 
1538
1601
  // Note that this gets set to Qnil if we failed to extract the correct value, and thus we won't try to extract it again
1539
1602
  state->otel_current_span_key = span_key;
@@ -1545,12 +1608,13 @@ static VALUE get_otel_current_span_key(struct thread_context_collector_state *st
1545
1608
  // This method gets used when ddtrace is being used indirectly via the opentelemetry APIs. Information gets stored slightly
1546
1609
  // differently, and this codepath handles it.
1547
1610
  static void ddtrace_otel_trace_identifiers_for(
1548
- struct thread_context_collector_state *state,
1611
+ thread_context_collector_state *state,
1549
1612
  VALUE *active_trace,
1550
1613
  VALUE *root_span,
1551
1614
  VALUE *numeric_span_id,
1552
1615
  VALUE active_span,
1553
- VALUE otel_values
1616
+ VALUE otel_values,
1617
+ bool is_safe_to_allocate_objects
1554
1618
  ) {
1555
1619
  VALUE resolved_numeric_span_id =
1556
1620
  active_span == Qnil ?
@@ -1561,7 +1625,7 @@ static void ddtrace_otel_trace_identifiers_for(
1561
1625
 
1562
1626
  if (resolved_numeric_span_id == Qnil) return;
1563
1627
 
1564
- VALUE otel_current_span_key = get_otel_current_span_key(state);
1628
+ VALUE otel_current_span_key = get_otel_current_span_key(state, is_safe_to_allocate_objects);
1565
1629
  if (otel_current_span_key == Qnil) return;
1566
1630
  VALUE current_trace = *active_trace;
1567
1631
 
@@ -1569,7 +1633,7 @@ static void ddtrace_otel_trace_identifiers_for(
1569
1633
  // trace and span representing it. Each ddtrace trace is then connected to the previous otel span, forming a linked
1570
1634
  // list. The local root span is going to be the trace/span we find at the end of this linked list.
1571
1635
  while (otel_values != Qnil) {
1572
- 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);
1573
1637
  if (otel_span == Qnil) break;
1574
1638
  VALUE next_trace = rb_ivar_get(otel_span, at_datadog_trace_id);
1575
1639
  if (next_trace == Qnil) break;
@@ -1588,8 +1652,8 @@ static void ddtrace_otel_trace_identifiers_for(
1588
1652
  }
1589
1653
 
1590
1654
  void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples) {
1591
- struct thread_context_collector_state *state;
1592
- 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);
1593
1657
 
1594
1658
  ddog_prof_Label labels[] = {
1595
1659
  // Providing .num = 0 should not be needed but the tracer-2.7 docker image ships a buggy gcc that complains about this
@@ -1612,7 +1676,12 @@ void thread_context_collector_sample_skipped_allocation_samples(VALUE self_insta
1612
1676
  }
1613
1677
 
1614
1678
  static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples) {
1679
+ debug_enter_unsafe_context();
1680
+
1615
1681
  thread_context_collector_sample_skipped_allocation_samples(collector_instance, NUM2UINT(skipped_samples));
1682
+
1683
+ debug_leave_unsafe_context();
1684
+
1616
1685
  return Qtrue;
1617
1686
  }
1618
1687
 
@@ -1638,29 +1707,30 @@ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self
1638
1707
  // root span id.
1639
1708
  // This matches the semantics of how ddtrace tracing creates a TraceOperation and assigns a local root span to it.
1640
1709
  static void otel_without_ddtrace_trace_identifiers_for(
1641
- struct thread_context_collector_state *state,
1710
+ thread_context_collector_state *state,
1642
1711
  VALUE thread,
1643
- struct trace_identifiers *trace_identifiers_result
1712
+ trace_identifiers *trace_identifiers_result,
1713
+ bool is_safe_to_allocate_objects
1644
1714
  ) {
1645
1715
  VALUE context_storage = rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
1646
1716
 
1647
1717
  // If it exists, context_storage is expected to be an Array[OpenTelemetry::Context]
1648
1718
  if (context_storage == Qnil || !RB_TYPE_P(context_storage, T_ARRAY)) return;
1649
1719
 
1650
- VALUE otel_current_span_key = get_otel_current_span_key(state);
1720
+ VALUE otel_current_span_key = get_otel_current_span_key(state, is_safe_to_allocate_objects);
1651
1721
  if (otel_current_span_key == Qnil) return;
1652
1722
 
1653
1723
  int active_context_index = RARRAY_LEN(context_storage) - 1;
1654
1724
  if (active_context_index < 0) return;
1655
1725
 
1656
- 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);
1657
1727
  if (active_span.span == Qnil) return;
1658
1728
 
1659
- struct otel_span local_root_span = active_span;
1729
+ otel_span local_root_span = active_span;
1660
1730
 
1661
1731
  // Now find the oldest span starting from the active span that still has the same trace id as the active span
1662
1732
  for (int i = active_context_index - 1; i >= 0; i--) {
1663
- 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);
1664
1734
  if (checking_span.span == Qnil) return;
1665
1735
 
1666
1736
  if (rb_str_equal(active_span.trace_id, checking_span.trace_id) == Qfalse) break;
@@ -1680,7 +1750,7 @@ static void otel_without_ddtrace_trace_identifiers_for(
1680
1750
 
1681
1751
  VALUE root_span_type = rb_ivar_get(local_root_span.span, at_kind_id /* @kind */);
1682
1752
  // We filter out spans that don't have `kind: :server`
1683
- 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;
1684
1754
 
1685
1755
  VALUE trace_resource = rb_ivar_get(local_root_span.span, at_name_id /* @name */);
1686
1756
  if (!RB_TYPE_P(trace_resource, T_STRING)) return;
@@ -1688,8 +1758,8 @@ static void otel_without_ddtrace_trace_identifiers_for(
1688
1758
  trace_identifiers_result->trace_endpoint = trace_resource;
1689
1759
  }
1690
1760
 
1691
- static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key) {
1692
- 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};
1693
1763
 
1694
1764
  if (otel_context == Qnil) return failed;
1695
1765
 
@@ -1697,7 +1767,7 @@ static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_sp
1697
1767
  if (context_entries == Qnil || !RB_TYPE_P(context_entries, T_HASH)) return failed;
1698
1768
 
1699
1769
  // If it exists, context_entries is expected to be a Hash[OpenTelemetry::Context::Key, OpenTelemetry::Trace::Span]
1700
- 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);
1701
1771
  if (span == Qnil) return failed;
1702
1772
 
1703
1773
  // If it exists, span_context is expected to be a OpenTelemetry::Trace::SpanContext (don't confuse it with OpenTelemetry::Context)
@@ -1708,7 +1778,7 @@ static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_sp
1708
1778
  VALUE trace_id = rb_ivar_get(span_context, at_trace_id_id /* @trace_id */);
1709
1779
  if (span_id == Qnil || trace_id == Qnil || !RB_TYPE_P(span_id, T_STRING) || !RB_TYPE_P(trace_id, T_STRING)) return failed;
1710
1780
 
1711
- 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};
1712
1782
  }
1713
1783
 
1714
1784
  // Otel span ids are represented as a big-endian 8-byte string
@@ -1810,8 +1880,8 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
1810
1880
  // NOTE: In normal use, current_thread is expected to be == rb_thread_current(); the `current_thread` parameter only
1811
1881
  // exists to enable testing.
1812
1882
  VALUE thread_context_collector_sample_after_gvl_running(VALUE self_instance, VALUE current_thread, long current_monotonic_wall_time_ns) {
1813
- struct thread_context_collector_state *state;
1814
- 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);
1815
1885
 
1816
1886
  if (!state->timeline_enabled) rb_raise(rb_eRuntimeError, "GVL profiling requires timeline to be enabled");
1817
1887
 
@@ -1825,7 +1895,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
1825
1895
  return Qfalse;
1826
1896
  }
1827
1897
 
1828
- 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);
1829
1899
 
1830
1900
  // We don't actually account for cpu-time during Waiting for GVL. BUT, we may chose to push an
1831
1901
  // extra sample to represent the period prior to Waiting for GVL. To support that, we retrieve the current
@@ -1851,10 +1921,10 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
1851
1921
  // need to take when sampling cpu/wall-time for a thread that's in the "Waiting for GVL" state.
1852
1922
  __attribute__((warn_unused_result))
1853
1923
  static bool handle_gvl_waiting(
1854
- struct thread_context_collector_state *state,
1924
+ thread_context_collector_state *state,
1855
1925
  VALUE thread_being_sampled,
1856
1926
  VALUE stack_from_thread,
1857
- struct per_thread_context *thread_context,
1927
+ per_thread_context *thread_context,
1858
1928
  sampling_buffer* sampling_buffer,
1859
1929
  long current_cpu_time_ns
1860
1930
  ) {
@@ -1939,7 +2009,8 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
1939
2009
  gvl_waiting_started_wall_time_ns,
1940
2010
  NULL,
1941
2011
  NULL,
1942
- false // This is the extra sample before the wait begun; only the next sample will be in the gvl waiting state
2012
+ /* is_gvl_waiting_state: */ false, // This is the extra sample before the wait begun; only the next sample will be in the gvl waiting state
2013
+ /* is_safe_to_allocate_objects: */ true // This is similar to a regular cpu/wall sample, so it's also safe
1943
2014
  );
1944
2015
  }
1945
2016
 
@@ -1949,40 +2020,62 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
1949
2020
  static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
1950
2021
  ENFORCE_THREAD(thread);
1951
2022
 
2023
+ debug_enter_unsafe_context();
2024
+
1952
2025
  thread_context_collector_on_gvl_waiting(thread_from_thread_object(thread));
2026
+
2027
+ debug_leave_unsafe_context();
2028
+
1953
2029
  return Qnil;
1954
2030
  }
1955
2031
 
1956
2032
  static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
1957
2033
  ENFORCE_THREAD(thread);
1958
2034
 
2035
+ debug_enter_unsafe_context();
2036
+
1959
2037
  intptr_t gvl_waiting_at = gvl_profiling_state_thread_object_get(thread);
2038
+
2039
+ debug_leave_unsafe_context();
2040
+
1960
2041
  return LONG2NUM(gvl_waiting_at);
1961
2042
  }
1962
2043
 
1963
2044
  static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread) {
1964
2045
  ENFORCE_THREAD(thread);
1965
2046
 
1966
- 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;
1967
2054
  }
1968
2055
 
1969
2056
  static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
1970
2057
  ENFORCE_THREAD(thread);
1971
2058
 
1972
- return thread_context_collector_sample_after_gvl_running(
2059
+ debug_enter_unsafe_context();
2060
+
2061
+ VALUE result = thread_context_collector_sample_after_gvl_running(
1973
2062
  collector_instance,
1974
2063
  thread,
1975
2064
  monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
1976
2065
  );
2066
+
2067
+ debug_leave_unsafe_context();
2068
+
2069
+ return result;
1977
2070
  }
1978
2071
 
1979
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) {
1980
2073
  ENFORCE_THREAD(thread);
1981
2074
 
1982
- struct thread_context_collector_state *state;
1983
- 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);
1984
2077
 
1985
- struct per_thread_context *thread_context = get_context_for(thread, state);
2078
+ per_thread_context *thread_context = get_context_for(thread, state);
1986
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");
1987
2080
 
1988
2081
  thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
@@ -1992,11 +2085,45 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
1992
2085
 
1993
2086
  #else
1994
2087
  static bool handle_gvl_waiting(
1995
- DDTRACE_UNUSED struct thread_context_collector_state *state,
2088
+ DDTRACE_UNUSED thread_context_collector_state *state,
1996
2089
  DDTRACE_UNUSED VALUE thread_being_sampled,
1997
2090
  DDTRACE_UNUSED VALUE stack_from_thread,
1998
- DDTRACE_UNUSED struct per_thread_context *thread_context,
2091
+ DDTRACE_UNUSED per_thread_context *thread_context,
1999
2092
  DDTRACE_UNUSED sampling_buffer* sampling_buffer,
2000
2093
  DDTRACE_UNUSED long current_cpu_time_ns
2001
2094
  ) { return false; }
2002
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
+ }