[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 <code@jeremyevans.net>
This commit is contained in:
Masataka Pocke Kuwabara 2025-02-14 16:31:58 +09:00 committed by GitHub
parent b4ed6db096
commit 0cab608d3a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
Notes: git 2025-02-14 07:32:15 +00:00
Merged-By: pocke <p.ck.t22@gmail.com>
4 changed files with 40 additions and 9 deletions

View File

@ -300,6 +300,8 @@ void rb_vm_set_progname(VALUE filename);
VALUE rb_vm_cbase(void); VALUE rb_vm_cbase(void);
/* vm_backtrace.c */ /* 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_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_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); VALUE rb_ec_backtrace_location_ary(const rb_execution_context_t *ec, long lev, long n, bool skip_internal);

View File

@ -1553,4 +1553,36 @@ q.pop
assert_equal(true, t.pending_interrupt?(Exception)) assert_equal(true, t.pending_interrupt?(Exception))
assert_equal(false, t.pending_interrupt?(ArgumentError)) assert_equal(false, t.pending_interrupt?(ArgumentError))
end 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/,
/\* #<Thread:\w+ sleep_forever>\n/,
:*,
/^\s*-:6:in 'Object#frame_for_deadlock_test_2'/,
:*,
/\* #<Thread:\w+ -:10 sleep_forever>\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 end

View File

@ -5588,7 +5588,7 @@ debug_deadlock_check(rb_ractor_t *r, VALUE msg)
} }
} }
rb_str_catf(msg, "\n "); 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"); rb_str_catf(msg, "\n");
} }
} }

View File

@ -31,9 +31,6 @@ id2str(ID id)
} }
#define rb_id2str(id) id2str(id) #define rb_id2str(id) id2str(id)
#define BACKTRACE_START 0
#define ALL_BACKTRACE_LINES -1
inline static int inline static int
calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id) 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 VALUE
rb_ec_backtrace_object(const rb_execution_context_t *ec) 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 static VALUE
@ -1191,7 +1188,7 @@ rb_backtrace_each(VALUE (*iter)(VALUE recv, VALUE str), VALUE output)
VALUE VALUE
rb_make_backtrace(void) 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 static long
@ -1210,7 +1207,7 @@ ec_backtrace_range(const rb_execution_context_t *ec, int argc, const VALUE *argv
switch (argc) { switch (argc) {
case 0: case 0:
lev = lev_default + lev_plus; lev = lev_default + lev_plus;
n = ALL_BACKTRACE_LINES; n = RUBY_ALL_BACKTRACE_LINES;
break; break;
case 1: 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); rb_raise(rb_eArgError, "negative level (%ld)", lev);
} }
lev += lev_plus; lev += lev_plus;
n = ALL_BACKTRACE_LINES; n = RUBY_ALL_BACKTRACE_LINES;
break; break;
case Qnil: case Qnil:
return -1; return -1;
@ -1626,7 +1623,7 @@ rb_debug_inspector_open(rb_debug_inspector_func_t func, void *data)
dbg_context.ec = ec; dbg_context.ec = ec;
dbg_context.cfp = dbg_context.ec->cfp; 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.backtrace_size = RARRAY_LEN(dbg_context.backtrace);
dbg_context.contexts = collect_caller_bindings(ec); dbg_context.contexts = collect_caller_bindings(ec);