From 0cab608d3a7791c229eea2ebe276494f063c8176 Mon Sep 17 00:00:00 2001 From: Masataka Pocke Kuwabara Date: Fri, 14 Feb 2025 16:31:58 +0900 Subject: [PATCH] [Bug #21127] Thread deadlock does not display backtraces (#12721) Previously, Ruby displayed backtraces for each thread on deadlock. However, it has not been shown since Ruby 3.0. It should display the backtrace for debugging. Co-authored-by: Jeremy Evans --- eval_intern.h | 2 ++ test/ruby/test_thread.rb | 32 ++++++++++++++++++++++++++++++++ thread.c | 2 +- vm_backtrace.c | 13 +++++-------- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/eval_intern.h b/eval_intern.h index 9a551120ff..ab0577e8ed 100644 --- a/eval_intern.h +++ b/eval_intern.h @@ -300,6 +300,8 @@ void rb_vm_set_progname(VALUE filename); VALUE rb_vm_cbase(void); /* vm_backtrace.c */ +#define RUBY_BACKTRACE_START 0 +#define RUBY_ALL_BACKTRACE_LINES -1 VALUE rb_ec_backtrace_object(const rb_execution_context_t *ec); VALUE rb_ec_backtrace_str_ary(const rb_execution_context_t *ec, long lev, long n); VALUE rb_ec_backtrace_location_ary(const rb_execution_context_t *ec, long lev, long n, bool skip_internal); diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index 19f39705bd..7784e0bdae 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -1553,4 +1553,36 @@ q.pop assert_equal(true, t.pending_interrupt?(Exception)) assert_equal(false, t.pending_interrupt?(ArgumentError)) end + + def test_deadlock_backtrace + bug21127 = '[ruby-core:120930] [Bug #21127]' + + expected_stderr = [ + /-:12:in 'Thread#join': No live threads left. Deadlock\? \(fatal\)\n/, + /2 threads, 2 sleeps current:\w+ main thread:\w+\n/, + /\* #\n/, + :*, + /^\s*-:6:in 'Object#frame_for_deadlock_test_2'/, + :*, + /\* #\n/, + :*, + /^\s*-:2:in 'Object#frame_for_deadlock_test_1'/, + :*, + ] + + assert_in_out_err([], <<-INPUT, [], expected_stderr, bug21127) + def frame_for_deadlock_test_1 + yield + end + + def frame_for_deadlock_test_2 + yield + end + + q = Thread::Queue.new + t = Thread.new { frame_for_deadlock_test_1 { q.pop } } + + frame_for_deadlock_test_2 { t.join } + INPUT + end end diff --git a/thread.c b/thread.c index c4e304231d..359671ba9e 100644 --- a/thread.c +++ b/thread.c @@ -5588,7 +5588,7 @@ debug_deadlock_check(rb_ractor_t *r, VALUE msg) } } rb_str_catf(msg, "\n "); - rb_str_concat(msg, rb_ary_join(rb_ec_backtrace_str_ary(th->ec, 0, 0), sep)); + rb_str_concat(msg, rb_ary_join(rb_ec_backtrace_str_ary(th->ec, RUBY_BACKTRACE_START, RUBY_ALL_BACKTRACE_LINES), sep)); rb_str_catf(msg, "\n"); } } diff --git a/vm_backtrace.c b/vm_backtrace.c index 56a0d861d6..26e0a6fb76 100644 --- a/vm_backtrace.c +++ b/vm_backtrace.c @@ -31,9 +31,6 @@ id2str(ID id) } #define rb_id2str(id) id2str(id) -#define BACKTRACE_START 0 -#define ALL_BACKTRACE_LINES -1 - inline static int calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id) { @@ -758,7 +755,7 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram VALUE rb_ec_backtrace_object(const rb_execution_context_t *ec) { - return rb_ec_partial_backtrace_object(ec, BACKTRACE_START, ALL_BACKTRACE_LINES, NULL, FALSE, FALSE); + return rb_ec_partial_backtrace_object(ec, RUBY_BACKTRACE_START, RUBY_ALL_BACKTRACE_LINES, NULL, FALSE, FALSE); } static VALUE @@ -1191,7 +1188,7 @@ rb_backtrace_each(VALUE (*iter)(VALUE recv, VALUE str), VALUE output) VALUE rb_make_backtrace(void) { - return rb_ec_backtrace_str_ary(GET_EC(), BACKTRACE_START, ALL_BACKTRACE_LINES); + return rb_ec_backtrace_str_ary(GET_EC(), RUBY_BACKTRACE_START, RUBY_ALL_BACKTRACE_LINES); } static long @@ -1210,7 +1207,7 @@ ec_backtrace_range(const rb_execution_context_t *ec, int argc, const VALUE *argv switch (argc) { case 0: lev = lev_default + lev_plus; - n = ALL_BACKTRACE_LINES; + n = RUBY_ALL_BACKTRACE_LINES; break; case 1: { @@ -1222,7 +1219,7 @@ ec_backtrace_range(const rb_execution_context_t *ec, int argc, const VALUE *argv rb_raise(rb_eArgError, "negative level (%ld)", lev); } lev += lev_plus; - n = ALL_BACKTRACE_LINES; + n = RUBY_ALL_BACKTRACE_LINES; break; case Qnil: return -1; @@ -1626,7 +1623,7 @@ rb_debug_inspector_open(rb_debug_inspector_func_t func, void *data) dbg_context.ec = ec; dbg_context.cfp = dbg_context.ec->cfp; - dbg_context.backtrace = rb_ec_backtrace_location_ary(ec, BACKTRACE_START, ALL_BACKTRACE_LINES, FALSE); + dbg_context.backtrace = rb_ec_backtrace_location_ary(ec, RUBY_BACKTRACE_START, RUBY_ALL_BACKTRACE_LINES, FALSE); dbg_context.backtrace_size = RARRAY_LEN(dbg_context.backtrace); dbg_context.contexts = collect_caller_bindings(ec);