Skip to content

Commit

Permalink
Merge pull request #226 from froydnj/froydnj-fix-timing-crash
Browse files Browse the repository at this point in the history
fix crashes with not-yet fully-initialized stackprof global state
  • Loading branch information
tenderlove authored Dec 18, 2024
2 parents 078f365 + 4e504d3 commit d90ad35
Showing 1 changed file with 41 additions and 11 deletions.
52 changes: 41 additions & 11 deletions ext/stackprof/stackprof.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,19 @@ typedef struct {
int64_t delta_usec;
} sample_time_t;

/* We need to ensure that various memory operations are visible across
* threads. Ruby doesn't offer a portable way to do this sort of detection
* across all the Ruby versions we support, so we use something that casts a
* wide net (Clang, along with ICC, defines __GNUC__). */
#if defined(__GNUC__) && defined(__ATOMIC_SEQ_CST)
#define STACKPROF_HAVE_ATOMICS 1
#else
#define STACKPROF_HAVE_ATOMICS 0
#endif

static struct {
/* Access this field with the `STACKPROF_RUNNING` macro, below, since we
* can't properly express that this field has an atomic type. */
int running;
int raw;
int aggregate;
Expand Down Expand Up @@ -133,6 +145,12 @@ static struct {
pthread_t target_thread;
} _stackprof;

#if STACKPROF_HAVE_ATOMICS
#define STACKPROF_RUNNING() __atomic_load_n(&_stackprof.running, __ATOMIC_ACQUIRE)
#else
#define STACKPROF_RUNNING() _stackprof.running
#endif

static VALUE sym_object, sym_wall, sym_cpu, sym_custom, sym_name, sym_file, sym_line;
static VALUE sym_samples, sym_total_samples, sym_missed_samples, sym_edges, sym_lines;
static VALUE sym_version, sym_mode, sym_interval, sym_raw, sym_raw_lines, sym_metadata, sym_frames, sym_ignore_gc, sym_out;
Expand All @@ -154,7 +172,7 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
int raw = 0, aggregate = 1;
VALUE metadata_val;

if (_stackprof.running)
if (STACKPROF_RUNNING())
return Qfalse;

rb_scan_args(argc, argv, "0:", &opts);
Expand Down Expand Up @@ -217,7 +235,6 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
rb_raise(rb_eArgError, "unknown profiler mode");
}

_stackprof.running = 1;
_stackprof.raw = raw;
_stackprof.aggregate = aggregate;
_stackprof.mode = mode;
Expand All @@ -226,6 +243,13 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
_stackprof.metadata = metadata;
_stackprof.out = out;
_stackprof.target_thread = pthread_self();
/* We need to ensure previous initialization stores are visible across
* threads. */
#if STACKPROF_HAVE_ATOMICS
__atomic_store_n(&_stackprof.running, 1, __ATOMIC_SEQ_CST);
#else
_stackprof.running = 1;
#endif

if (raw) {
capture_timestamp(&_stackprof.last_sample_at);
Expand All @@ -240,9 +264,15 @@ stackprof_stop(VALUE self)
struct sigaction sa;
struct itimerval timer;

#if STACKPROF_HAVE_ATOMICS
int was_running = __atomic_exchange_n(&_stackprof.running, 0, __ATOMIC_SEQ_CST);
if (!was_running)
return Qfalse;
#else
if (!_stackprof.running)
return Qfalse;
_stackprof.running = 0;
#endif

if (_stackprof.mode == sym_object) {
rb_tracepoint_disable(objtracer);
Expand Down Expand Up @@ -351,7 +381,7 @@ stackprof_results(int argc, VALUE *argv, VALUE self)
{
VALUE results, frames;

if (!_stackprof.frames || _stackprof.running)
if (!_stackprof.frames || STACKPROF_RUNNING())
return Qnil;

results = rb_hash_new();
Expand Down Expand Up @@ -455,7 +485,7 @@ stackprof_run(int argc, VALUE *argv, VALUE self)
static VALUE
stackprof_running_p(VALUE self)
{
return _stackprof.running ? Qtrue : Qfalse;
return STACKPROF_RUNNING() ? Qtrue : Qfalse;
}

static inline frame_data_t *
Expand Down Expand Up @@ -719,23 +749,23 @@ stackprof_sample_and_record(void)
static void
stackprof_job_record_gc(void *data)
{
if (!_stackprof.running) return;
if (!STACKPROF_RUNNING()) return;

stackprof_record_gc_samples();
}

static void
stackprof_job_sample_and_record(void *data)
{
if (!_stackprof.running) return;
if (!STACKPROF_RUNNING()) return;

stackprof_sample_and_record();
}

static void
stackprof_job_record_buffer(void *data)
{
if (!_stackprof.running) return;
if (!STACKPROF_RUNNING()) return;

stackprof_record_buffer();
}
Expand All @@ -747,7 +777,7 @@ stackprof_signal_handler(int sig, siginfo_t *sinfo, void *ucontext)

_stackprof.overall_signals++;

if (!_stackprof.running) return;
if (!STACKPROF_RUNNING()) return;

// There's a possibility that the signal handler is invoked *after* the Ruby
// VM has been shut down (e.g. after ruby_cleanup(0)). In this case, things
Expand Down Expand Up @@ -810,7 +840,7 @@ stackprof_newobj_handler(VALUE tpval, void *data)
static VALUE
stackprof_sample(VALUE self)
{
if (!_stackprof.running)
if (!STACKPROF_RUNNING())
return Qfalse;

_stackprof.overall_signals++;
Expand Down Expand Up @@ -854,7 +884,7 @@ static void
stackprof_atfork_prepare(void)
{
struct itimerval timer;
if (_stackprof.running) {
if (STACKPROF_RUNNING()) {
if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
memset(&timer, 0, sizeof(timer));
setitimer(_stackprof.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
Expand All @@ -866,7 +896,7 @@ static void
stackprof_atfork_parent(void)
{
struct itimerval timer;
if (_stackprof.running) {
if (STACKPROF_RUNNING()) {
if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = NUM2LONG(_stackprof.interval);
Expand Down

0 comments on commit d90ad35

Please sign in to comment.