Skip to content

Commit 2703f94

Browse files
authored
Merge pull request #2007 from DataDog/ivoanjo/prof-5473-release-gvl-serialization
[PROF-5473] Release global VM lock during profile serialization
2 parents 7b09123 + c2d00bc commit 2703f94

File tree

2 files changed

+37
-2
lines changed

2 files changed

+37
-2
lines changed

ext/ddtrace_profiling_native_extension/stack_recorder.c

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <ruby.h>
2+
#include <ruby/thread.h>
23
#include "stack_recorder.h"
34
#include "libddprof_helpers.h"
45

@@ -12,10 +13,17 @@ static ID ruby_time_from_id; // id of :ruby_time_from in Ruby
1213

1314
static VALUE stack_recorder_class = Qnil;
1415

16+
struct call_serialize_without_gvl_arguments {
17+
ddprof_ffi_Profile *profile;
18+
ddprof_ffi_SerializeResult result;
19+
bool serialize_ran;
20+
};
21+
1522
static VALUE _native_new(VALUE klass);
1623
static void stack_recorder_typed_data_free(void *data);
1724
static VALUE _native_serialize(VALUE self, VALUE recorder_instance);
1825
static VALUE ruby_time_from(ddprof_ffi_Timespec ddprof_time);
26+
static void *call_serialize_without_gvl(void *call_args);
1927

2028
void stack_recorder_init(VALUE profiling_module) {
2129
stack_recorder_class = rb_define_class_under(profiling_module, "StackRecorder", rb_cObject);
@@ -65,7 +73,22 @@ static VALUE _native_serialize(VALUE self, VALUE recorder_instance) {
6573
ddprof_ffi_Profile *profile;
6674
TypedData_Get_Struct(recorder_instance, ddprof_ffi_Profile, &stack_recorder_typed_data, profile);
6775

68-
ddprof_ffi_SerializeResult serialized_profile = ddprof_ffi_Profile_serialize(profile);
76+
// We'll release the Global VM Lock while we're calling serialize, so that the Ruby VM can continue to work while this
77+
// is pending
78+
struct call_serialize_without_gvl_arguments args = {.profile = profile, .serialize_ran = false};
79+
80+
// We use rb_thread_call_without_gvl2 for similar reasons as in http_transport.c: we don't want pending interrupts
81+
// that cause exceptions to be raised to be processed as otherwise we can leak the serialized profile.
82+
rb_thread_call_without_gvl2(call_serialize_without_gvl, &args, /* No interruption supported */ NULL, NULL);
83+
84+
// This weird corner case can happen if rb_thread_call_without_gvl2 returns immediately due to an interrupt
85+
// without ever calling call_serialize_without_gvl. In this situation, we don't have anything to clean up, we can
86+
// just return.
87+
if (!args.serialize_ran) {
88+
return rb_ary_new_from_args(2, error_symbol, rb_str_new_cstr("Interrupted before call_serialize_without_gvl ran"));
89+
}
90+
91+
ddprof_ffi_SerializeResult serialized_profile = args.result;
6992

7093
if (serialized_profile.tag == DDPROF_FFI_SERIALIZE_RESULT_ERR) {
7194
VALUE err_details = ruby_string_from_vec_u8(serialized_profile.err);
@@ -105,3 +128,12 @@ void record_sample(VALUE recorder_instance, ddprof_ffi_Sample sample) {
105128

106129
ddprof_ffi_Profile_add(profile, sample);
107130
}
131+
132+
static void *call_serialize_without_gvl(void *call_args) {
133+
struct call_serialize_without_gvl_arguments *args = (struct call_serialize_without_gvl_arguments *) call_args;
134+
135+
args->result = ddprof_ffi_Profile_serialize(args->profile);
136+
args->serialize_ran = true;
137+
138+
return NULL; // Unused
139+
}

spec/datadog/profiling/collectors/stack_spec.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,10 @@ def convert_reference_stack(raw_reference_stack)
339339
def sample_and_decode(thread, max_frames: 400, recorder: Datadog::Profiling::StackRecorder.new)
340340
collectors_stack.sample(thread, recorder, metric_values, labels, max_frames: max_frames)
341341

342-
pprof_data = recorder.serialize.last
342+
serialization_result = recorder.serialize
343+
raise 'Unexpected: Serialization failed' unless serialization_result
344+
345+
pprof_data = serialization_result.last
343346
decoded_profile = ::Perftools::Profiles::Profile.decode(pprof_data)
344347

345348
expect(decoded_profile.sample.size).to be 1

0 commit comments

Comments
 (0)