Skip to content

Commit b9e7f5b

Browse files
committed
Fix data race in GC frame recording between signal handler and postponed job
stackprof_record_gc_samples() wrote fake GC frames (garbage collection, marking, sweeping) directly into the shared _stackprof.frames_buffer, then called stackprof_record_sample_for_stack() which reads from that buffer. A signal arriving during this processing calls stackprof_buffer_sample() from the signal handler, invoking rb_profile_frames() which overwrites frames_buffer with real stack frames. This caused the raw sample data to reference fake GC frame IDs that never got added to the frames hash, producing profiles where frame lookups fail for GC-related entries. Fix: stackprof_record_sample_for_stack() now takes explicit frames_buffer and lines_buffer parameters. stackprof_record_gc_samples() passes stack-local arrays for GC frames, making them immune to signal handler interference. stackprof_record_buffer() passes the shared global buffer explicitly (safe because buffer_count guards against re-entry).
1 parent 8d57d8e commit b9e7f5b

1 file changed

Lines changed: 22 additions & 21 deletions

File tree

ext/stackprof/stackprof.c

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,7 @@ st_numtable_increment(st_table *table, st_data_t key, size_t increment)
537537
}
538538

539539
void
540-
stackprof_record_sample_for_stack(int num, uint64_t sample_timestamp, int64_t timestamp_delta)
540+
stackprof_record_sample_for_stack(int num, VALUE *frames_buffer, int *lines_buffer, uint64_t sample_timestamp, int64_t timestamp_delta)
541541
{
542542
int i, n;
543543
VALUE prev_frame = Qnil;
@@ -571,8 +571,8 @@ stackprof_record_sample_for_stack(int num, uint64_t sample_timestamp, int64_t ti
571571
* in the raw buffer are stored in the opposite direction of stacks
572572
* in the frames buffer that came from Ruby. */
573573
for (i = num-1, n = 0; i >= 0; i--, n++) {
574-
VALUE frame = _stackprof.frames_buffer[i];
575-
int line = _stackprof.lines_buffer[i];
574+
VALUE frame = frames_buffer[i];
575+
int line = lines_buffer[i];
576576

577577
// Encode the line in to the upper 16 bits.
578578
uint64_t key = ((uint64_t)line << 48) | (uint64_t)frame;
@@ -594,8 +594,8 @@ stackprof_record_sample_for_stack(int num, uint64_t sample_timestamp, int64_t ti
594594
_stackprof.raw_sample_index = _stackprof.raw_samples_len;
595595
_stackprof.raw_samples[_stackprof.raw_samples_len++] = (VALUE)num;
596596
for (i = num-1; i >= 0; i--) {
597-
VALUE frame = _stackprof.frames_buffer[i];
598-
int line = _stackprof.lines_buffer[i];
597+
VALUE frame = frames_buffer[i];
598+
int line = lines_buffer[i];
599599

600600
// Encode the line in to the upper 16 bits.
601601
uint64_t key = ((uint64_t)line << 48) | (uint64_t)frame;
@@ -626,8 +626,8 @@ stackprof_record_sample_for_stack(int num, uint64_t sample_timestamp, int64_t ti
626626
}
627627

628628
for (i = 0; i < num; i++) {
629-
int line = _stackprof.lines_buffer[i];
630-
VALUE frame = _stackprof.frames_buffer[i];
629+
int line = lines_buffer[i];
630+
VALUE frame = frames_buffer[i];
631631
frame_data_t *frame_data = sample_for(frame);
632632

633633
if (frame_data->seen_at_sample_number != _stackprof.overall_samples) {
@@ -710,27 +710,28 @@ stackprof_record_gc_samples(void)
710710
for (i = 0; i < _stackprof.unrecorded_gc_samples; i++) {
711711
int64_t timestamp_delta = i == 0 ? delta_to_first_unrecorded_gc_sample : NUM2LONG(_stackprof.interval);
712712

713+
/* Use local arrays for GC frames to avoid a data race with the
714+
* signal handler, which can overwrite _stackprof.frames_buffer
715+
* via stackprof_buffer_sample/rb_profile_frames at any time. */
716+
VALUE gc_frames[2];
717+
int gc_lines[2] = {0, 0};
718+
713719
if (_stackprof.unrecorded_gc_marking_samples) {
714-
_stackprof.frames_buffer[0] = FAKE_FRAME_MARK;
715-
_stackprof.lines_buffer[0] = 0;
716-
_stackprof.frames_buffer[1] = FAKE_FRAME_GC;
717-
_stackprof.lines_buffer[1] = 0;
720+
gc_frames[0] = FAKE_FRAME_MARK;
721+
gc_frames[1] = FAKE_FRAME_GC;
718722
_stackprof.unrecorded_gc_marking_samples--;
719723

720-
stackprof_record_sample_for_stack(2, start_timestamp, timestamp_delta);
724+
stackprof_record_sample_for_stack(2, gc_frames, gc_lines, start_timestamp, timestamp_delta);
721725
} else if (_stackprof.unrecorded_gc_sweeping_samples) {
722-
_stackprof.frames_buffer[0] = FAKE_FRAME_SWEEP;
723-
_stackprof.lines_buffer[0] = 0;
724-
_stackprof.frames_buffer[1] = FAKE_FRAME_GC;
725-
_stackprof.lines_buffer[1] = 0;
726+
gc_frames[0] = FAKE_FRAME_SWEEP;
727+
gc_frames[1] = FAKE_FRAME_GC;
726728

727729
_stackprof.unrecorded_gc_sweeping_samples--;
728730

729-
stackprof_record_sample_for_stack(2, start_timestamp, timestamp_delta);
731+
stackprof_record_sample_for_stack(2, gc_frames, gc_lines, start_timestamp, timestamp_delta);
730732
} else {
731-
_stackprof.frames_buffer[0] = FAKE_FRAME_GC;
732-
_stackprof.lines_buffer[0] = 0;
733-
stackprof_record_sample_for_stack(1, start_timestamp, timestamp_delta);
733+
gc_frames[0] = FAKE_FRAME_GC;
734+
stackprof_record_sample_for_stack(1, gc_frames, gc_lines, start_timestamp, timestamp_delta);
734735
}
735736
}
736737
_stackprof.during_gc += _stackprof.unrecorded_gc_samples;
@@ -743,7 +744,7 @@ stackprof_record_gc_samples(void)
743744
static void
744745
stackprof_record_buffer(void)
745746
{
746-
stackprof_record_sample_for_stack(_stackprof.buffer_count, _stackprof.buffer_time.timestamp_usec, _stackprof.buffer_time.delta_usec);
747+
stackprof_record_sample_for_stack(_stackprof.buffer_count, _stackprof.frames_buffer, _stackprof.lines_buffer, _stackprof.buffer_time.timestamp_usec, _stackprof.buffer_time.delta_usec);
747748

748749
// reset the buffer
749750
_stackprof.buffer_count = 0;

0 commit comments

Comments
 (0)