datadog 2.28.0 → 2.30.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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +82 -12
  4. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +32 -11
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -1
  6. data/ext/datadog_profiling_native_extension/extconf.rb +9 -24
  7. data/ext/datadog_profiling_native_extension/heap_recorder.c +186 -55
  8. data/ext/datadog_profiling_native_extension/heap_recorder.h +12 -1
  9. data/ext/datadog_profiling_native_extension/http_transport.c +51 -64
  10. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -13
  11. data/ext/datadog_profiling_native_extension/profiling.c +3 -1
  12. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +24 -8
  13. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -3
  14. data/ext/datadog_profiling_native_extension/stack_recorder.c +63 -48
  15. data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -1
  16. data/ext/libdatadog_api/crashtracker.c +5 -0
  17. data/ext/libdatadog_api/crashtracker_report_exception.c +126 -0
  18. data/ext/libdatadog_api/extconf.rb +4 -21
  19. data/ext/libdatadog_extconf_helpers.rb +49 -11
  20. data/lib/datadog/ai_guard/configuration/settings.rb +3 -0
  21. data/lib/datadog/appsec/assets/blocked.html +2 -1
  22. data/lib/datadog/appsec/configuration/settings.rb +14 -0
  23. data/lib/datadog/appsec/context.rb +44 -9
  24. data/lib/datadog/appsec/contrib/active_record/patcher.rb +3 -0
  25. data/lib/datadog/appsec/contrib/devise/integration.rb +1 -1
  26. data/lib/datadog/appsec/contrib/excon/patcher.rb +2 -0
  27. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +55 -6
  28. data/lib/datadog/appsec/contrib/faraday/integration.rb +1 -1
  29. data/lib/datadog/appsec/contrib/faraday/patcher.rb +1 -1
  30. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +60 -7
  31. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +11 -6
  32. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
  33. data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -1
  34. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +6 -10
  35. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +4 -4
  36. data/lib/datadog/appsec/contrib/rack/integration.rb +1 -1
  37. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +26 -5
  38. data/lib/datadog/appsec/contrib/rack/response_body.rb +36 -0
  39. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +2 -2
  40. data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
  41. data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +2 -0
  42. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +2 -0
  43. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +72 -7
  44. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +5 -3
  45. data/lib/datadog/appsec/counter_sampler.rb +25 -0
  46. data/lib/datadog/appsec/event.rb +1 -17
  47. data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +2 -3
  48. data/lib/datadog/appsec/instrumentation/gateway.rb +2 -2
  49. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +18 -0
  50. data/lib/datadog/appsec/monitor/gateway/watcher.rb +2 -2
  51. data/lib/datadog/appsec/security_engine/engine.rb +23 -2
  52. data/lib/datadog/appsec/utils/http/body.rb +38 -0
  53. data/lib/datadog/appsec/utils/http/media_range.rb +2 -1
  54. data/lib/datadog/appsec/utils/http/media_type.rb +28 -35
  55. data/lib/datadog/appsec/utils/http/url_encoded.rb +52 -0
  56. data/lib/datadog/core/configuration/components.rb +29 -4
  57. data/lib/datadog/core/configuration/option.rb +2 -1
  58. data/lib/datadog/core/configuration/options.rb +1 -1
  59. data/lib/datadog/core/configuration/settings.rb +27 -3
  60. data/lib/datadog/core/configuration/supported_configurations.rb +19 -0
  61. data/lib/datadog/core/configuration.rb +2 -2
  62. data/lib/datadog/core/crashtracking/component.rb +71 -19
  63. data/lib/datadog/core/environment/agent_info.rb +65 -1
  64. data/lib/datadog/core/logger.rb +1 -1
  65. data/lib/datadog/core/metrics/logging.rb +1 -1
  66. data/lib/datadog/core/process_discovery.rb +20 -19
  67. data/lib/datadog/core/rate_limiter.rb +2 -0
  68. data/lib/datadog/core/remote/component.rb +16 -5
  69. data/lib/datadog/core/remote/transport/config.rb +5 -11
  70. data/lib/datadog/core/runtime/metrics.rb +1 -2
  71. data/lib/datadog/core/telemetry/component.rb +0 -13
  72. data/lib/datadog/core/telemetry/transport/telemetry.rb +5 -6
  73. data/lib/datadog/core/transport/ext.rb +1 -0
  74. data/lib/datadog/core/transport/http/response.rb +4 -0
  75. data/lib/datadog/core/transport/parcel.rb +61 -9
  76. data/lib/datadog/core/utils/base64.rb +1 -1
  77. data/lib/datadog/core/utils/fnv.rb +26 -0
  78. data/lib/datadog/core/workers/interval_loop.rb +13 -6
  79. data/lib/datadog/core/workers/queue.rb +0 -4
  80. data/lib/datadog/core/workers/runtime_metrics.rb +9 -1
  81. data/lib/datadog/core.rb +6 -1
  82. data/lib/datadog/data_streams/processor.rb +35 -33
  83. data/lib/datadog/data_streams/transport/http/stats.rb +6 -0
  84. data/lib/datadog/data_streams/transport/http.rb +0 -4
  85. data/lib/datadog/data_streams/transport/stats.rb +5 -12
  86. data/lib/datadog/di/boot.rb +1 -0
  87. data/lib/datadog/di/component.rb +17 -5
  88. data/lib/datadog/di/configuration/settings.rb +9 -0
  89. data/lib/datadog/di/context.rb +6 -0
  90. data/lib/datadog/di/instrumenter.rb +183 -134
  91. data/lib/datadog/di/probe.rb +10 -1
  92. data/lib/datadog/di/probe_file_loader.rb +2 -2
  93. data/lib/datadog/di/probe_manager.rb +86 -64
  94. data/lib/datadog/di/probe_notification_builder.rb +46 -18
  95. data/lib/datadog/di/probe_notifier_worker.rb +65 -9
  96. data/lib/datadog/di/probe_repository.rb +198 -0
  97. data/lib/datadog/di/proc_responder.rb +4 -0
  98. data/lib/datadog/di/remote.rb +6 -7
  99. data/lib/datadog/di/serializer.rb +127 -9
  100. data/lib/datadog/di/transport/diagnostics.rb +5 -7
  101. data/lib/datadog/di/transport/http/diagnostics.rb +3 -1
  102. data/lib/datadog/di/transport/http/input.rb +1 -1
  103. data/lib/datadog/di/transport/http.rb +12 -3
  104. data/lib/datadog/di/transport/input.rb +51 -14
  105. data/lib/datadog/kit/tracing/method_tracer.rb +132 -0
  106. data/lib/datadog/open_feature/configuration.rb +2 -0
  107. data/lib/datadog/open_feature/transport.rb +8 -11
  108. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +13 -0
  109. data/lib/datadog/profiling/component.rb +20 -6
  110. data/lib/datadog/profiling/http_transport.rb +5 -6
  111. data/lib/datadog/profiling/profiler.rb +15 -8
  112. data/lib/datadog/tracing/contrib/dalli/integration.rb +4 -1
  113. data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +5 -1
  114. data/lib/datadog/tracing/contrib/ethon/ext.rb +1 -0
  115. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +5 -2
  116. data/lib/datadog/tracing/contrib/excon/ext.rb +1 -0
  117. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +5 -2
  118. data/lib/datadog/tracing/contrib/faraday/ext.rb +1 -0
  119. data/lib/datadog/tracing/contrib/grape/endpoint.rb +2 -2
  120. data/lib/datadog/tracing/contrib/grape/instrumentation.rb +13 -8
  121. data/lib/datadog/tracing/contrib/grape/patcher.rb +6 -1
  122. data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +5 -2
  123. data/lib/datadog/tracing/contrib/grpc/ext.rb +1 -0
  124. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +5 -2
  125. data/lib/datadog/tracing/contrib/http/ext.rb +1 -0
  126. data/lib/datadog/tracing/contrib/http/integration.rb +0 -2
  127. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +5 -2
  128. data/lib/datadog/tracing/contrib/httpclient/ext.rb +1 -0
  129. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +5 -2
  130. data/lib/datadog/tracing/contrib/httprb/ext.rb +1 -0
  131. data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +5 -1
  132. data/lib/datadog/tracing/contrib/karafka/ext.rb +1 -0
  133. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +6 -0
  134. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +2 -1
  135. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +6 -0
  136. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +2 -1
  137. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +10 -0
  138. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
  139. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +24 -0
  140. data/lib/datadog/tracing/contrib/que/configuration/settings.rb +5 -2
  141. data/lib/datadog/tracing/contrib/que/ext.rb +1 -0
  142. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +5 -1
  143. data/lib/datadog/tracing/contrib/rack/ext.rb +1 -0
  144. data/lib/datadog/tracing/contrib/rack/route_inference.rb +18 -6
  145. data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +5 -2
  146. data/lib/datadog/tracing/contrib/rails/ext.rb +1 -0
  147. data/lib/datadog/tracing/contrib/registerable.rb +11 -0
  148. data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +5 -2
  149. data/lib/datadog/tracing/contrib/rest_client/ext.rb +1 -0
  150. data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +5 -1
  151. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +1 -0
  152. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +5 -1
  153. data/lib/datadog/tracing/contrib/sinatra/ext.rb +1 -0
  154. data/lib/datadog/tracing/contrib/sneakers/integration.rb +15 -4
  155. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +6 -0
  156. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +3 -1
  157. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +5 -1
  158. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +1 -0
  159. data/lib/datadog/tracing/metadata/ext.rb +4 -0
  160. data/lib/datadog/tracing/sync_writer.rb +0 -1
  161. data/lib/datadog/tracing/transport/io/client.rb +5 -8
  162. data/lib/datadog/tracing/transport/io/traces.rb +28 -34
  163. data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
  164. data/lib/datadog/tracing/transport/traces.rb +4 -10
  165. data/lib/datadog/tracing/writer.rb +0 -1
  166. data/lib/datadog/version.rb +1 -1
  167. metadata +14 -8
  168. data/lib/datadog/appsec/contrib/rails/ext.rb +0 -13
  169. data/lib/datadog/tracing/workers/trace_writer.rb +0 -204
@@ -79,6 +79,16 @@ static void object_record_free(heap_recorder*, object_record*);
79
79
  static VALUE object_record_inspect(heap_recorder*, object_record*);
80
80
  static object_record SKIPPED_RECORD = {0};
81
81
 
82
+ // A pending recording is used to defer the object_id call on Ruby 4+
83
+ // where calling rb_obj_id during on_newobj_event is unsafe.
84
+ typedef struct {
85
+ VALUE object_ref;
86
+ heap_record *heap_record;
87
+ live_object_data object_data;
88
+ } pending_recording;
89
+
90
+ #define MAX_PENDING_RECORDINGS 256
91
+
82
92
  struct heap_recorder {
83
93
  // Config
84
94
  // Whether the recorder should try to determine approximate sizes for tracked objects.
@@ -130,6 +140,15 @@ struct heap_recorder {
130
140
  // Data for a heap recording that was started but not yet ended
131
141
  object_record *active_recording;
132
142
 
143
+ // Pending recordings that need to be finalized after on_newobj_event completes.
144
+ // On Ruby 4+, we can't call rb_obj_id during the newobj event, so we store the
145
+ // VALUE reference here and finalize it via a postponed job.
146
+ pending_recording pending_recordings[MAX_PENDING_RECORDINGS];
147
+ // Temporary storage for the recording in progress, used between start and end
148
+ VALUE active_deferred_object;
149
+ live_object_data active_deferred_object_data;
150
+ uint16_t pending_recordings_count;
151
+
133
152
  // Reusable arrays, implementing a flyweight pattern for things like iteration
134
153
  #define REUSABLE_LOCATIONS_SIZE MAX_FRAMES_LIMIT
135
154
  ddog_prof_Location *reusable_locations;
@@ -163,6 +182,9 @@ struct heap_recorder {
163
182
  double ewma_objects_alive;
164
183
  double ewma_objects_dead;
165
184
  double ewma_objects_skipped;
185
+
186
+ unsigned long deferred_recordings_skipped_buffer_full;
187
+ unsigned long deferred_recordings_finalized;
166
188
  } stats_lifetime;
167
189
  };
168
190
 
@@ -180,6 +202,7 @@ static int st_object_record_update(st_data_t, st_data_t, st_data_t);
180
202
  static int st_object_records_iterate(st_data_t, st_data_t, st_data_t);
181
203
  static int st_object_records_debug(st_data_t key, st_data_t value, st_data_t extra);
182
204
  static int update_object_record_entry(st_data_t*, st_data_t*, st_data_t, int);
205
+ static void inc_tracked_objects_or_fail(heap_record *heap_record);
183
206
  static void commit_recording(heap_recorder *, heap_record *, object_record *active_recording);
184
207
  static VALUE end_heap_allocation_recording(VALUE end_heap_allocation_args);
185
208
  static void heap_recorder_update(heap_recorder *heap_recorder, bool full_update);
@@ -187,6 +210,7 @@ static inline double ewma_stat(double previous, double current);
187
210
  static void unintern_or_raise(heap_recorder *, ddog_prof_ManagedStringId);
188
211
  static void unintern_all_or_raise(heap_recorder *recorder, ddog_prof_Slice_ManagedStringId ids);
189
212
  static VALUE get_ruby_string_or_raise(heap_recorder*, ddog_prof_ManagedStringId);
213
+ static long obj_id_or_fail(VALUE obj);
190
214
 
191
215
  // ==========================
192
216
  // Heap Recorder External API
@@ -210,6 +234,7 @@ heap_recorder* heap_recorder_new(ddog_prof_ManagedStringStorage string_storage)
210
234
  recorder->size_enabled = true;
211
235
  recorder->sample_rate = 1; // By default do no sampling on top of what allocation profiling already does
212
236
  recorder->string_storage = string_storage;
237
+ recorder->active_deferred_object = Qnil;
213
238
 
214
239
  return recorder;
215
240
  }
@@ -294,9 +319,9 @@ void heap_recorder_after_fork(heap_recorder *heap_recorder) {
294
319
  heap_recorder->stats_lifetime = (struct stats_lifetime) {0};
295
320
  }
296
321
 
297
- void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj, unsigned int weight, ddog_CharSlice alloc_class) {
322
+ bool start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj, unsigned int weight, ddog_CharSlice alloc_class) {
298
323
  if (heap_recorder == NULL) {
299
- return;
324
+ return false;
300
325
  }
301
326
 
302
327
  if (heap_recorder->active_recording != NULL) {
@@ -316,25 +341,49 @@ void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj
316
341
  || heap_recorder->updating
317
342
  ) {
318
343
  heap_recorder->active_recording = &SKIPPED_RECORD;
319
- return;
344
+ return false;
320
345
  }
321
346
 
322
- heap_recorder->num_recordings_skipped = 0;
347
+ bool needs_after_allocation = false;
323
348
 
324
- VALUE ruby_obj_id = rb_obj_id(new_obj);
325
- if (!FIXNUM_P(ruby_obj_id)) {
326
- raise_error(rb_eRuntimeError, "Detected a bignum object id. These are not supported by heap profiling.");
349
+ #ifdef USE_DEFERRED_HEAP_ALLOCATION_RECORDING
350
+ // Skip if we've hit the pending recordings limit or if there's already a deferred object being recorded
351
+ if (heap_recorder->pending_recordings_count >= MAX_PENDING_RECORDINGS) {
352
+ heap_recorder->stats_lifetime.deferred_recordings_skipped_buffer_full++;
353
+ heap_recorder->active_recording = &SKIPPED_RECORD;
354
+ return true; // If the buffer is full, we keep asking for a callback (see `needs_after_allocation` below)
355
+ } else {
356
+ // The intuition here is: We start by asking for an `after_allocation` callback when the buffer is about to go
357
+ // from empty -> non-empty, because this is going to be mapped onto a postponed job, so after it gets queued once
358
+ // it doesn't seem worth it to keep spamming requests.
359
+ //
360
+ // Yet, if for some reason the postponed job doesn't flush the pending list (or if e.g. it ran with `during_sample == true` and thus
361
+ // was skipped) we need to have some mechanism to recover -- and so if the buffer starts accumulating too much we
362
+ // start always requesting the callback to happen so that we eventually flush the buffer.
363
+ needs_after_allocation =
364
+ heap_recorder->pending_recordings_count == 0 || heap_recorder->pending_recordings_count >= (MAX_PENDING_RECORDINGS / 2);
327
365
  }
366
+ #endif
328
367
 
329
- heap_recorder->active_recording = object_record_new(
330
- FIX2LONG(ruby_obj_id),
331
- NULL,
332
- (live_object_data) {
333
- .weight = weight * heap_recorder->sample_rate,
334
- .class = intern_or_raise(heap_recorder->string_storage, alloc_class),
335
- .alloc_gen = rb_gc_count(),
336
- }
337
- );
368
+ heap_recorder->num_recordings_skipped = 0;
369
+
370
+ live_object_data object_data = (live_object_data) {
371
+ .weight = weight * heap_recorder->sample_rate,
372
+ .class = intern_or_raise(heap_recorder->string_storage, alloc_class),
373
+ .alloc_gen = rb_gc_count(),
374
+ };
375
+
376
+ #ifdef USE_DEFERRED_HEAP_ALLOCATION_RECORDING
377
+ // On Ruby 4+, we can't call rb_obj_id during on_newobj_event as it mutates the object.
378
+ // Instead, we store the VALUE reference and will get the object_id later via a postponed job.
379
+ // active_deferred_object != Qnil indicates we're in deferred mode.
380
+ heap_recorder->active_deferred_object = new_obj;
381
+ heap_recorder->active_deferred_object_data = object_data;
382
+ #else
383
+ heap_recorder->active_recording = object_record_new(obj_id_or_fail(new_obj), NULL, object_data);
384
+ #endif
385
+
386
+ return needs_after_allocation;
338
387
  }
339
388
 
340
389
  // end_heap_allocation_recording_with_rb_protect gets called while the stack_recorder is holding one of the profile
@@ -351,7 +400,6 @@ int end_heap_allocation_recording_with_rb_protect(heap_recorder *heap_recorder,
351
400
  return 0;
352
401
  }
353
402
 
354
-
355
403
  int exception_state;
356
404
  end_heap_allocation_args args = {
357
405
  .heap_recorder = heap_recorder,
@@ -367,26 +415,48 @@ static VALUE end_heap_allocation_recording(VALUE protect_args) {
367
415
  heap_recorder *heap_recorder = args->heap_recorder;
368
416
  ddog_prof_Slice_Location locations = args->locations;
369
417
 
370
- object_record *active_recording = heap_recorder->active_recording;
418
+ #ifdef USE_DEFERRED_HEAP_ALLOCATION_RECORDING
419
+ if (heap_recorder->active_deferred_object == Qnil) {
420
+ // Recording ended without having been started?
421
+ raise_error(rb_eRuntimeError, "Ended a heap recording that was not started");
422
+ }
423
+ #else
424
+ object_record *active_recording = heap_recorder->active_recording;
371
425
 
372
- if (active_recording == NULL) {
373
- // Recording ended without having been started?
374
- raise_error(rb_eRuntimeError, "Ended a heap recording that was not started");
375
- }
376
- // From now on, mark the global active recording as invalid so we can short-circuit at any point
377
- // and not end up with a still active recording. the local active_recording still holds the
378
- // data required for committing though.
379
- heap_recorder->active_recording = NULL;
426
+ if (active_recording == NULL) {
427
+ // Recording ended without having been started?
428
+ raise_error(rb_eRuntimeError, "Ended a heap recording that was not started");
429
+ }
430
+ // From now on, mark the global active recording as invalid so we can short-circuit at any point
431
+ // and not end up with a still active recording. the local active_recording still holds the
432
+ // data required for committing though.
433
+ heap_recorder->active_recording = NULL;
380
434
 
381
- if (active_recording == &SKIPPED_RECORD) { // special marker when we decided to skip due to sampling
382
- // Note: Remember to update the short circuit in end_heap_allocation_recording_with_rb_protect if this logic changes
383
- return Qnil;
384
- }
435
+ if (active_recording == &SKIPPED_RECORD) {
436
+ raise_error(
437
+ rb_eRuntimeError,
438
+ "BUG: end_heap_allocation_recording should never observe SKIPPED_RECORDING because " \
439
+ "end_heap_allocation_recording_with_rb_protect is supposed to test for it directly"
440
+ );
441
+ }
442
+ #endif
385
443
 
386
444
  heap_record *heap_record = get_or_create_heap_record(heap_recorder, locations);
387
-
388
- // And then commit the new allocation.
389
- commit_recording(heap_recorder, heap_record, active_recording);
445
+ inc_tracked_objects_or_fail(heap_record);
446
+
447
+ #ifdef USE_DEFERRED_HEAP_ALLOCATION_RECORDING
448
+ // Commit is delayed, so we need to record all we'll need for it
449
+ pending_recording *pending = &heap_recorder->pending_recordings[heap_recorder->pending_recordings_count++];
450
+ pending->object_ref = heap_recorder->active_deferred_object;
451
+ pending->heap_record = heap_record;
452
+ pending->object_data = heap_recorder->active_deferred_object_data;
453
+
454
+ heap_recorder->active_deferred_object = Qnil;
455
+ heap_recorder->active_deferred_object_data = (live_object_data) {0};
456
+ #else
457
+ // And then commit the new allocation
458
+ commit_recording(heap_recorder, heap_record, active_recording);
459
+ #endif
390
460
 
391
461
  return Qnil;
392
462
  }
@@ -399,6 +469,48 @@ void heap_recorder_update_young_objects(heap_recorder *heap_recorder) {
399
469
  heap_recorder_update(heap_recorder, /* full_update: */ false);
400
470
  }
401
471
 
472
+ void heap_recorder_finalize_pending_recordings(heap_recorder *heap_recorder) {
473
+ if (heap_recorder == NULL) {
474
+ return; // Nothing to do
475
+ }
476
+
477
+ uint count = heap_recorder->pending_recordings_count;
478
+ if (count == 0) {
479
+ return; // Nothing to do
480
+ }
481
+
482
+ heap_recorder->stats_lifetime.deferred_recordings_finalized += count;
483
+
484
+ for (uint i = 0; i < count; i++) {
485
+ pending_recording *pending = &heap_recorder->pending_recordings[i];
486
+
487
+ // This is the step we couldn't do during the original sample call -- we're now expected to be called in a context
488
+ // where it's finally safe to call this
489
+ long obj_id = obj_id_or_fail(pending->object_ref);
490
+
491
+ // Create the object record now that we have the object_id
492
+ object_record *record = object_record_new(obj_id, pending->heap_record, pending->object_data);
493
+
494
+ commit_recording(heap_recorder, pending->heap_record, record);
495
+ }
496
+
497
+ heap_recorder->pending_recordings_count = 0;
498
+ }
499
+
500
+ // Mark pending recordings to prevent GC from collecting the objects
501
+ // while they're waiting to be finalized
502
+ void heap_recorder_mark_pending_recordings(heap_recorder *heap_recorder) {
503
+ if (heap_recorder == NULL) {
504
+ return;
505
+ }
506
+
507
+ for (uint i = 0; i < heap_recorder->pending_recordings_count; i++) {
508
+ rb_gc_mark(heap_recorder->pending_recordings[i].object_ref);
509
+ }
510
+
511
+ rb_gc_mark(heap_recorder->active_deferred_object);
512
+ }
513
+
402
514
  // NOTE: This function needs and assumes it gets called with the GVL being held.
403
515
  // But importantly **some of the operations inside `st_object_record_update` may cause a thread switch**,
404
516
  // so we can't assume a single update happens in a single "atomic" step -- other threads may get some running time
@@ -547,20 +659,21 @@ bool heap_recorder_for_each_live_object(
547
659
 
548
660
  VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder) {
549
661
  VALUE arguments[] = {
550
- ID2SYM(rb_intern("num_object_records")), /* => */ LONG2NUM(heap_recorder->object_records->num_entries),
551
- ID2SYM(rb_intern("num_heap_records")), /* => */ LONG2NUM(heap_recorder->heap_records->num_entries),
662
+ ID2SYM(rb_intern("num_object_records")), /* => */ ULONG2NUM(heap_recorder->object_records->num_entries),
663
+ ID2SYM(rb_intern("num_heap_records")), /* => */ ULONG2NUM(heap_recorder->heap_records->num_entries),
664
+ ID2SYM(rb_intern("pending_recordings_count")), /* => */ ULONG2NUM(heap_recorder->pending_recordings_count),
552
665
 
553
666
  // Stats as of last update
554
- ID2SYM(rb_intern("last_update_objects_alive")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_alive),
555
- ID2SYM(rb_intern("last_update_objects_dead")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_dead),
556
- ID2SYM(rb_intern("last_update_objects_skipped")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_skipped),
557
- ID2SYM(rb_intern("last_update_objects_frozen")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_frozen),
667
+ ID2SYM(rb_intern("last_update_objects_alive")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_alive),
668
+ ID2SYM(rb_intern("last_update_objects_dead")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_dead),
669
+ ID2SYM(rb_intern("last_update_objects_skipped")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_skipped),
670
+ ID2SYM(rb_intern("last_update_objects_frozen")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_frozen),
558
671
 
559
672
  // Lifetime stats
560
- ID2SYM(rb_intern("lifetime_updates_successful")), /* => */ LONG2NUM(heap_recorder->stats_lifetime.updates_successful),
561
- ID2SYM(rb_intern("lifetime_updates_skipped_concurrent")), /* => */ LONG2NUM(heap_recorder->stats_lifetime.updates_skipped_concurrent),
562
- ID2SYM(rb_intern("lifetime_updates_skipped_gcgen")), /* => */ LONG2NUM(heap_recorder->stats_lifetime.updates_skipped_gcgen),
563
- ID2SYM(rb_intern("lifetime_updates_skipped_time")), /* => */ LONG2NUM(heap_recorder->stats_lifetime.updates_skipped_time),
673
+ ID2SYM(rb_intern("lifetime_updates_successful")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_successful),
674
+ ID2SYM(rb_intern("lifetime_updates_skipped_concurrent")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_skipped_concurrent),
675
+ ID2SYM(rb_intern("lifetime_updates_skipped_gcgen")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_skipped_gcgen),
676
+ ID2SYM(rb_intern("lifetime_updates_skipped_time")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_skipped_time),
564
677
  ID2SYM(rb_intern("lifetime_ewma_young_objects_alive")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_young_objects_alive),
565
678
  ID2SYM(rb_intern("lifetime_ewma_young_objects_dead")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_young_objects_dead),
566
679
  // Note: Here "young" refers to the young update; objects skipped includes non-young objects
@@ -568,15 +681,19 @@ VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder) {
568
681
  ID2SYM(rb_intern("lifetime_ewma_objects_alive")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_objects_alive),
569
682
  ID2SYM(rb_intern("lifetime_ewma_objects_dead")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_objects_dead),
570
683
  ID2SYM(rb_intern("lifetime_ewma_objects_skipped")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_objects_skipped),
684
+
685
+ ID2SYM(rb_intern("lifetime_deferred_recordings_skipped_buffer_full")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.deferred_recordings_skipped_buffer_full),
686
+ ID2SYM(rb_intern("lifetime_deferred_recordings_finalized")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.deferred_recordings_finalized),
571
687
  };
572
688
  VALUE hash = rb_hash_new();
573
689
  for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
690
+
574
691
  return hash;
575
692
  }
576
693
 
577
694
  typedef struct {
578
695
  heap_recorder *recorder;
579
- VALUE debug_str;
696
+ VALUE debug_ary;
580
697
  } debug_context;
581
698
 
582
699
  VALUE heap_recorder_testonly_debug(heap_recorder *heap_recorder) {
@@ -584,13 +701,14 @@ VALUE heap_recorder_testonly_debug(heap_recorder *heap_recorder) {
584
701
  raise_error(rb_eArgError, "heap_recorder is NULL");
585
702
  }
586
703
 
587
- VALUE debug_str = rb_str_new2("object records:\n");
588
- debug_context context = (debug_context) {.recorder = heap_recorder, .debug_str = debug_str};
704
+ VALUE debug_ary = rb_ary_new();
705
+ debug_context context = (debug_context) {.recorder = heap_recorder, .debug_ary = debug_ary};
589
706
  st_foreach(heap_recorder->object_records, st_object_records_debug, (st_data_t) &context);
590
707
 
591
- rb_str_catf(debug_str, "state snapshot: %"PRIsVALUE"\n------\n", heap_recorder_state_snapshot(heap_recorder));
592
-
593
- return debug_str;
708
+ return rb_ary_new_from_args(2,
709
+ rb_ary_new_from_args(2, ID2SYM(rb_intern("records")), debug_ary),
710
+ rb_ary_new_from_args(2, ID2SYM(rb_intern("state")), heap_recorder_state_snapshot(heap_recorder))
711
+ );
594
712
  }
595
713
 
596
714
  // ==========================
@@ -710,11 +828,10 @@ static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t val
710
828
 
711
829
  static int st_object_records_debug(DDTRACE_UNUSED st_data_t key, st_data_t value, st_data_t extra) {
712
830
  debug_context *context = (debug_context*) extra;
713
- VALUE debug_str = context->debug_str;
714
831
 
715
832
  object_record *record = (object_record*) value;
716
833
 
717
- rb_str_catf(debug_str, "%"PRIsVALUE"\n", object_record_inspect(context->recorder, record));
834
+ rb_ary_push(context->debug_ary, object_record_inspect(context->recorder, record));
718
835
 
719
836
  return ST_CONTINUE;
720
837
  }
@@ -728,14 +845,17 @@ static int update_object_record_entry(DDTRACE_UNUSED st_data_t *key, st_data_t *
728
845
  return ST_CONTINUE;
729
846
  }
730
847
 
731
- static void commit_recording(heap_recorder *heap_recorder, heap_record *heap_record, object_record *active_recording) {
732
- // Link the object record with the corresponding heap record. This was the last remaining thing we
733
- // needed to fully build the object_record.
734
- active_recording->heap_record = heap_record;
848
+ static void inc_tracked_objects_or_fail(heap_record *heap_record) {
735
849
  if (heap_record->num_tracked_objects == UINT32_MAX) {
736
850
  raise_error(rb_eRuntimeError, "Reached maximum number of tracked objects for heap record");
737
851
  }
738
852
  heap_record->num_tracked_objects++;
853
+ }
854
+
855
+ static void commit_recording(heap_recorder *heap_recorder, heap_record *heap_record, object_record *active_recording) {
856
+ // Link the object record with the corresponding heap record. This was the last remaining thing we
857
+ // needed to fully build the object_record.
858
+ active_recording->heap_record = heap_record;
739
859
 
740
860
  int existing_error = st_update(heap_recorder->object_records, active_recording->obj_id, update_object_record_entry, (st_data_t) active_recording);
741
861
  if (existing_error) {
@@ -955,6 +1075,17 @@ static VALUE get_ruby_string_or_raise(heap_recorder *recorder, ddog_prof_Managed
955
1075
  return ruby_string;
956
1076
  }
957
1077
 
1078
+ static long obj_id_or_fail(VALUE obj) {
1079
+ VALUE ruby_obj_id = rb_obj_id(obj);
1080
+ if (!FIXNUM_P(ruby_obj_id)) {
1081
+ // Bignum object ids indicate the fixnum range is exhausted - all future IDs will also be bignums.
1082
+ // Heap profiling cannot continue.
1083
+ raise_error(rb_eRuntimeError, "Heap profiling: bignum object id detected. Heap profiling cannot continue.");
1084
+ }
1085
+
1086
+ return FIX2LONG(ruby_obj_id);
1087
+ }
1088
+
958
1089
  static inline double ewma_stat(double previous, double current) {
959
1090
  double alpha = 0.3;
960
1091
  return (1 - alpha) * previous + alpha * current;
@@ -105,7 +105,9 @@ void heap_recorder_after_fork(heap_recorder *heap_recorder);
105
105
  // The sampling weight of this object.
106
106
  //
107
107
  // WARN: It needs to be paired with a ::end_heap_allocation_recording call.
108
- void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj, unsigned int weight, ddog_CharSlice alloc_class);
108
+ // Returns needs_after_allocation: true whenever the pending_recordings buffer goes from empty to non-empty and thus
109
+ // a after_sample callback is required to flush it
110
+ bool start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj, unsigned int weight, ddog_CharSlice alloc_class);
109
111
 
110
112
  // End a previously started heap allocation recording on the heap recorder.
111
113
  //
@@ -123,6 +125,15 @@ int end_heap_allocation_recording_with_rb_protect(heap_recorder *heap_recorder,
123
125
  // these objects quicker) and hopefully reduces tail latency (because there's less objects at serialization time to check).
124
126
  void heap_recorder_update_young_objects(heap_recorder *heap_recorder);
125
127
 
128
+ // Finalize any pending heap allocation recordings by getting their object IDs.
129
+ // This should be called via a postponed job, after the on_newobj_event has completed.
130
+ // Raises an exception if a fatal error occurs (e.g., bignum object ID detected).
131
+ void heap_recorder_finalize_pending_recordings(heap_recorder *heap_recorder);
132
+
133
+ // Mark pending recordings to prevent GC from collecting the objects
134
+ // while they're waiting for the recordings to be finalized.
135
+ void heap_recorder_mark_pending_recordings(heap_recorder *heap_recorder);
136
+
126
137
  // Update the heap recorder to reflect the latest state of the VM and prepare internal structures
127
138
  // for efficient iteration.
128
139
  //
@@ -16,7 +16,11 @@ static VALUE library_version_string = Qnil;
16
16
 
17
17
  typedef struct {
18
18
  ddog_prof_ProfileExporter *exporter;
19
- ddog_prof_Request_Result *build_result;
19
+ ddog_prof_EncodedProfile *profile;
20
+ ddog_prof_Exporter_Slice_File files_to_compress_and_export;
21
+ ddog_CharSlice internal_metadata;
22
+ ddog_CharSlice info;
23
+ ddog_CharSlice *process_tags;
20
24
  ddog_CancellationToken *cancel_token;
21
25
  ddog_prof_Result_HttpStatus result;
22
26
  bool send_ran;
@@ -29,7 +33,6 @@ static VALUE handle_exporter_failure(ddog_prof_ProfileExporter_Result exporter_r
29
33
  static VALUE _native_do_export(
30
34
  VALUE self,
31
35
  VALUE exporter_configuration,
32
- VALUE upload_timeout_milliseconds,
33
36
  VALUE flush
34
37
  );
35
38
  static void *call_exporter_without_gvl(void *call_args);
@@ -39,7 +42,7 @@ void http_transport_init(VALUE profiling_module) {
39
42
  VALUE http_transport_class = rb_define_class_under(profiling_module, "HttpTransport", rb_cObject);
40
43
 
41
44
  rb_define_singleton_method(http_transport_class, "_native_validate_exporter", _native_validate_exporter, 1);
42
- rb_define_singleton_method(http_transport_class, "_native_do_export", _native_do_export, 3);
45
+ rb_define_singleton_method(http_transport_class, "_native_do_export", _native_do_export, 2);
43
46
 
44
47
  ok_symbol = ID2SYM(rb_intern_const("ok"));
45
48
  error_symbol = ID2SYM(rb_intern_const("error"));
@@ -75,15 +78,31 @@ static ddog_prof_Endpoint endpoint_from(VALUE exporter_configuration) {
75
78
  ENFORCE_TYPE(exporter_working_mode, T_SYMBOL);
76
79
  ID working_mode = SYM2ID(exporter_working_mode);
77
80
 
78
- if (working_mode == rb_intern("agentless")) {
79
- VALUE site = rb_ary_entry(exporter_configuration, 1);
80
- VALUE api_key = rb_ary_entry(exporter_configuration, 2);
81
+ VALUE timeout_milliseconds_value = rb_ary_entry(exporter_configuration, 1);
82
+ ENFORCE_TYPE(timeout_milliseconds_value, T_FIXNUM);
83
+ uint64_t timeout_milliseconds = NUM2ULONG(timeout_milliseconds_value);
84
+
85
+ VALUE use_system_dns = rb_ary_entry(exporter_configuration, 2);
86
+ ENFORCE_BOOLEAN(use_system_dns);
81
87
 
82
- return ddog_prof_Endpoint_agentless(char_slice_from_ruby_string(site), char_slice_from_ruby_string(api_key));
88
+ if (working_mode == rb_intern("agentless")) {
89
+ VALUE site = rb_ary_entry(exporter_configuration, 3);
90
+ VALUE api_key = rb_ary_entry(exporter_configuration, 4);
91
+
92
+ return ddog_prof_Endpoint_agentless(
93
+ char_slice_from_ruby_string(site),
94
+ char_slice_from_ruby_string(api_key),
95
+ timeout_milliseconds,
96
+ use_system_dns == Qtrue
97
+ );
83
98
  } else if (working_mode == rb_intern("agent")) {
84
- VALUE base_url = rb_ary_entry(exporter_configuration, 1);
99
+ VALUE base_url = rb_ary_entry(exporter_configuration, 3);
85
100
 
86
- return ddog_prof_Endpoint_agent(char_slice_from_ruby_string(base_url));
101
+ return ddog_prof_Endpoint_agent(
102
+ char_slice_from_ruby_string(base_url),
103
+ timeout_milliseconds,
104
+ use_system_dns == Qtrue
105
+ );
87
106
  } else {
88
107
  raise_error(rb_eArgError, "Failed to initialize transport: Unexpected working mode, expected :agentless or :agent");
89
108
  }
@@ -123,41 +142,18 @@ static VALUE handle_exporter_failure(ddog_prof_ProfileExporter_Result exporter_r
123
142
 
124
143
  // Note: This function handles a bunch of libdatadog dynamically-allocated objects, so it MUST not use any Ruby APIs
125
144
  // which can raise exceptions, otherwise the objects will be leaked.
126
- static VALUE perform_export(
127
- ddog_prof_ProfileExporter *exporter,
128
- ddog_prof_EncodedProfile *profile,
129
- ddog_prof_Exporter_Slice_File files_to_compress_and_export,
130
- ddog_CharSlice internal_metadata,
131
- ddog_CharSlice info,
132
- ddog_CharSlice *process_tags
133
- ) {
134
- ddog_prof_Request_Result build_result = ddog_prof_Exporter_Request_build(
135
- exporter,
136
- profile,
137
- files_to_compress_and_export,
138
- /* files_to_export_unmodified: */ ddog_prof_Exporter_Slice_File_empty(),
139
- /* optional_additional_tags: */ NULL,
140
- /* optional_process_tags: */ process_tags,
141
- &internal_metadata,
142
- &info
143
- );
144
-
145
- if (build_result.tag == DDOG_PROF_REQUEST_RESULT_ERR_HANDLE_REQUEST) {
146
- ddog_prof_Exporter_drop(exporter);
147
- return rb_ary_new_from_args(2, error_symbol, get_error_details_and_drop(&build_result.err));
148
- }
149
-
145
+ static VALUE perform_export(call_exporter_without_gvl_arguments args) {
150
146
  ddog_CancellationToken cancel_token_request = ddog_CancellationToken_new();
151
147
  ddog_CancellationToken cancel_token_interrupt = ddog_CancellationToken_clone(&cancel_token_request);
152
148
 
153
149
  validate_token(cancel_token_request, __FILE__, __LINE__);
154
150
  validate_token(cancel_token_interrupt, __FILE__, __LINE__);
155
151
 
152
+ args.cancel_token = &cancel_token_request;
153
+ args.send_ran = false;
154
+
156
155
  // We'll release the Global VM Lock while we're calling send, so that the Ruby VM can continue to work while this
157
156
  // is pending
158
- call_exporter_without_gvl_arguments args =
159
- {.exporter = exporter, .build_result = &build_result, .cancel_token = &cancel_token_request, .send_ran = false};
160
-
161
157
  // We use rb_thread_call_without_gvl2 instead of rb_thread_call_without_gvl as the gvl2 variant never raises any
162
158
  // exceptions.
163
159
  //
@@ -182,18 +178,13 @@ static VALUE perform_export(
182
178
  // Cleanup exporter and token, no longer needed
183
179
  ddog_CancellationToken_drop(&cancel_token_request);
184
180
  ddog_CancellationToken_drop(&cancel_token_interrupt);
185
- ddog_prof_Exporter_drop(exporter);
181
+ ddog_prof_Exporter_drop(args.exporter);
186
182
 
187
183
  if (pending_exception) {
188
- // If we got here send did not run, so we need to explicitly dispose of the request
189
- ddog_prof_Exporter_Request_drop(&build_result.ok);
190
-
191
184
  // Let Ruby propagate the exception. This will not return.
192
185
  rb_jump_tag(pending_exception);
193
186
  }
194
187
 
195
- // The request itself does not need to be freed as libdatadog takes ownership of it as part of sending.
196
-
197
188
  ddog_prof_Result_HttpStatus result = args.result;
198
189
 
199
190
  return result.tag == DDOG_PROF_RESULT_HTTP_STATUS_OK_HTTP_STATUS ?
@@ -204,7 +195,6 @@ static VALUE perform_export(
204
195
  static VALUE _native_do_export(
205
196
  DDTRACE_UNUSED VALUE _self,
206
197
  VALUE exporter_configuration,
207
- VALUE upload_timeout_milliseconds,
208
198
  VALUE flush
209
199
  ) {
210
200
  VALUE encoded_profile = rb_funcall(flush, rb_intern("encoded_profile"), 0);
@@ -215,7 +205,6 @@ static VALUE _native_do_export(
215
205
  VALUE info_json = rb_funcall(flush, rb_intern("info_json"), 0);
216
206
  VALUE process_tags = rb_funcall(flush, rb_intern("process_tags"), 0);
217
207
 
218
- ENFORCE_TYPE(upload_timeout_milliseconds, T_FIXNUM);
219
208
  enforce_encoded_profile_instance(encoded_profile);
220
209
  ENFORCE_TYPE(code_provenance_file_name, T_STRING);
221
210
  ENFORCE_TYPE(tags_as_array, T_ARRAY);
@@ -227,8 +216,6 @@ static VALUE _native_do_export(
227
216
  bool have_code_provenance = !NIL_P(code_provenance_data);
228
217
  if (have_code_provenance) ENFORCE_TYPE(code_provenance_data, T_STRING);
229
218
 
230
- uint64_t timeout_milliseconds = NUM2ULONG(upload_timeout_milliseconds);
231
-
232
219
  int to_compress_length = have_code_provenance ? 1 : 0;
233
220
  ddog_prof_Exporter_File to_compress[to_compress_length];
234
221
  ddog_prof_Exporter_Slice_File files_to_compress_and_export = {.ptr = to_compress, .len = to_compress_length};
@@ -250,29 +237,29 @@ static VALUE _native_do_export(
250
237
  VALUE failure_tuple = handle_exporter_failure(exporter_result);
251
238
  if (!NIL_P(failure_tuple)) return failure_tuple;
252
239
 
253
- ddog_VoidResult timeout_result = ddog_prof_Exporter_set_timeout(&exporter_result.ok, timeout_milliseconds);
254
- if (timeout_result.tag == DDOG_VOID_RESULT_ERR) {
255
- // NOTE: Seems a bit harsh to fail the upload if we can't set a timeout. OTOH, this is only expected to fail
256
- // if the exporter is not well built. Because such a situation should already be caught above I think it's
257
- // preferable to leave this here as a virtually unreachable exception rather than ignoring it.
258
- ddog_prof_Exporter_drop(&exporter_result.ok);
259
- return rb_ary_new_from_args(2, error_symbol, get_error_details_and_drop(&timeout_result.err));
260
- }
261
-
262
- return perform_export(
263
- &exporter_result.ok,
264
- to_ddog_prof_EncodedProfile(encoded_profile),
265
- files_to_compress_and_export,
266
- internal_metadata,
267
- info,
268
- &process_tags_slice
269
- );
240
+ return perform_export((call_exporter_without_gvl_arguments) {
241
+ .exporter = &exporter_result.ok,
242
+ .profile = to_ddog_prof_EncodedProfile(encoded_profile),
243
+ .files_to_compress_and_export = files_to_compress_and_export,
244
+ .internal_metadata = internal_metadata,
245
+ .info = info,
246
+ .process_tags = &process_tags_slice
247
+ });
270
248
  }
271
249
 
272
250
  static void *call_exporter_without_gvl(void *call_args) {
273
251
  call_exporter_without_gvl_arguments *args = (call_exporter_without_gvl_arguments*) call_args;
274
252
 
275
- args->result = ddog_prof_Exporter_send(args->exporter, &args->build_result->ok, args->cancel_token);
253
+ args->result = ddog_prof_Exporter_send_blocking(
254
+ args->exporter,
255
+ args->profile,
256
+ args->files_to_compress_and_export,
257
+ /* optional_additional_tags: */ NULL,
258
+ /* optional_process_tags: */ args->process_tags,
259
+ &args->internal_metadata,
260
+ &args->info,
261
+ args->cancel_token
262
+ );
276
263
  args->send_ran = true;
277
264
 
278
265
  return NULL; // Unused
@@ -104,19 +104,6 @@ module Datadog
104
104
  suggested: CONTACT_SUPPORT,
105
105
  )
106
106
 
107
- # Validation for this check is done in extconf.rb because it relies on mkmf
108
- PKG_CONFIG_IS_MISSING = explain_issue(
109
- # ----------------------------------------------------------------------------+
110
- "the `pkg-config` system tool is missing.",
111
- "This issue can usually be fixed by installing one of the following:",
112
- "the `pkg-config` package on Homebrew and Debian/Ubuntu-based Linux;",
113
- "the `pkgconf` package on Arch and Alpine-based Linux;",
114
- "the `pkgconf-pkg-config` package on Fedora/Red Hat-based Linux.",
115
- "(Tip: When fixing this, ensure `pkg-config` is installed **before**",
116
- "running `bundle install`, and remember to clear any installed gems cache).",
117
- suggested: CONTACT_SUPPORT,
118
- )
119
-
120
107
  # Validation for this check is done in extconf.rb because it relies on mkmf
121
108
  COMPILER_ATOMIC_MISSING = explain_issue(
122
109
  "your C compiler is missing support for the <stdatomic.h> header.",
@@ -25,6 +25,7 @@ void encoded_profile_init(VALUE profiling_module);
25
25
  void http_transport_init(VALUE profiling_module);
26
26
  void stack_recorder_init(VALUE profiling_module);
27
27
  void crashtracking_runtime_stacks_init(void);
28
+ void setup_signal_handler_init(VALUE profiling_module);
28
29
 
29
30
  static VALUE native_working_p(VALUE self);
30
31
  static VALUE _native_grab_gvl_and_raise(DDTRACE_UNUSED VALUE _self, VALUE exception_class, VALUE test_message, VALUE test_message_arg, VALUE release_gvl);
@@ -62,6 +63,7 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
62
63
  rb_funcall(native_extension_module, rb_intern("private_class_method"), 1, ID2SYM(rb_intern("native_working?")));
63
64
 
64
65
  ruby_helpers_init();
66
+ setup_signal_handler_init(profiling_module);
65
67
  collectors_cpu_and_wall_time_worker_init(profiling_module);
66
68
  collectors_discrete_dynamic_sampler_init(profiling_module);
67
69
  collectors_dynamic_sampling_rate_init(profiling_module);
@@ -251,7 +253,7 @@ static VALUE _native_trigger_holding_the_gvl_signal_handler_on(DDTRACE_UNUSED VA
251
253
 
252
254
  ENFORCE_SUCCESS_GVL(pthread_mutex_unlock(&holding_the_gvl_signal_handler_mutex));
253
255
 
254
- replace_sigprof_signal_handler_with_empty_handler(holding_the_gvl_signal_handler);
256
+ replace_sigprof_signal_handler_with_empty_handler(holding_the_gvl_signal_handler, true);
255
257
 
256
258
  if (holding_the_gvl_signal_handler_result[0] == Qfalse) raise_error(rb_eRuntimeError, "Could not signal background_thread");
257
259