|
# Reproducer: rb_profile_frames() called from a signal handler (as profilers
|
|
# like Datadog's do) segfaults if the signal arrives while vm_make_env_each()
|
|
# is between escaping a parent frame and updating the child frame's prev EP.
|
|
#
|
|
# Usage:
|
|
# ruby rb_profile_frames_signal_crash.rb # MODE=class (default)
|
|
# MODE=define_method ruby rb_profile_frames_signal_crash.rb
|
|
# MODE=thread ruby rb_profile_frames_signal_crash.rb
|
|
# INTERVAL_US=1000 ruby rb_profile_frames_signal_crash.rb # 1 signal per ms instead of back-to-back
|
|
|
|
require 'tmpdir'
|
|
require 'rbconfig'
|
|
|
|
def inline_c_extension(c_code)
|
|
Dir.mktmpdir('inline_c_extension') do |dir|
|
|
File.write("#{dir}/cext.c", c_code)
|
|
File.write("#{dir}/extconf.rb", <<~RUBY)
|
|
require 'mkmf'
|
|
create_makefile('cext')
|
|
RUBY
|
|
|
|
out = IO.popen([RbConfig.ruby, 'extconf.rb'], chdir: dir, &:read)
|
|
raise "ruby extconf.rb failed: #{$?.inspect}\n#{out}" unless $?.success?
|
|
|
|
out = IO.popen(['make'], chdir: dir, &:read)
|
|
raise "make failed: #{$?.inspect}\n#{out}" unless $?.success?
|
|
|
|
require "#{dir}/cext.#{RbConfig::CONFIG['DLEXT']}"
|
|
end
|
|
end
|
|
|
|
inline_c_extension <<~C
|
|
#include <ruby.h>
|
|
#include <ruby/debug.h>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
|
|
#define MAX_FRAMES 400
|
|
|
|
static pthread_t target_thread;
|
|
static pthread_t sampler_thread;
|
|
static volatile int running = 0;
|
|
static useconds_t interval_us;
|
|
static volatile unsigned long samples = 0;
|
|
|
|
static void sigprof_handler(int sig, siginfo_t *info, void *ucontext) {
|
|
int saved_errno = errno;
|
|
VALUE frames[MAX_FRAMES];
|
|
int lines[MAX_FRAMES];
|
|
rb_profile_frames(0, MAX_FRAMES, frames, lines);
|
|
samples++;
|
|
errno = saved_errno;
|
|
}
|
|
|
|
static void *sampler_loop(void *arg) {
|
|
while (running) {
|
|
pthread_kill(target_thread, SIGPROF);
|
|
if (interval_us) usleep(interval_us);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Start sending SIGPROF to the calling (main) thread every interval_us microseconds
|
|
static VALUE pf_start(VALUE self, VALUE interval) {
|
|
struct sigaction sa = {0};
|
|
sa.sa_sigaction = sigprof_handler;
|
|
sa.sa_flags = SA_SIGINFO | SA_RESTART;
|
|
sigemptyset(&sa.sa_mask);
|
|
if (sigaction(SIGPROF, &sa, NULL) != 0) rb_sys_fail("sigaction");
|
|
|
|
interval_us = NUM2UINT(interval);
|
|
target_thread = pthread_self();
|
|
running = 1;
|
|
if (pthread_create(&sampler_thread, NULL, sampler_loop, NULL) != 0) rb_sys_fail("pthread_create");
|
|
return Qnil;
|
|
}
|
|
|
|
static VALUE pf_stop(VALUE self) {
|
|
running = 0;
|
|
pthread_join(sampler_thread, NULL);
|
|
return Qnil;
|
|
}
|
|
|
|
static VALUE pf_samples(VALUE self) {
|
|
return ULONG2NUM(samples);
|
|
}
|
|
|
|
void Init_cext(void) {
|
|
VALUE mod = rb_define_module("ProfileFramesSigprof");
|
|
rb_define_module_function(mod, "start", pf_start, 1);
|
|
rb_define_module_function(mod, "stop", pf_stop, 0);
|
|
rb_define_module_function(mod, "samples", pf_samples, 0);
|
|
}
|
|
C
|
|
|
|
interval_us = Integer(ENV.fetch('INTERVAL_US', '0'))
|
|
iterations = Integer(ENV.fetch('ITERATIONS', '10_000_000'))
|
|
mode = ENV.fetch('MODE', 'class')
|
|
|
|
ProfileFramesSigprof.start(interval_us)
|
|
|
|
# Each class body execution is a fresh "local" frame whose ME_CREF is a cref
|
|
# (not a method entry) and which has no block handler (SPECVAL == 0).
|
|
# The block passed to #times is the frame that creates the Proc, whose prev EP is
|
|
# the class body EP, so vm_make_env_each() recurses into the class body frame.
|
|
case mode
|
|
when 'class'
|
|
iterations.times do
|
|
class Foo
|
|
1.times { proc {} }
|
|
end
|
|
end
|
|
when 'define_method'
|
|
iterations.times do
|
|
class Foo
|
|
1.times { define_method(:foo) {} }
|
|
end
|
|
end
|
|
when 'thread'
|
|
iterations.times do
|
|
class Foo
|
|
1.times { Thread.new {}.join }
|
|
end
|
|
end
|
|
else
|
|
abort "Unknown MODE: #{mode}"
|
|
end
|
|
|
|
ProfileFramesSigprof.stop
|
|
puts "No crash after #{iterations} iterations, #{ProfileFramesSigprof.samples} samples"
|