[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);
/* 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);

View File

@ -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/,
/\* #<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

View File

@ -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");
}
}

View File

@ -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);