Skip to content

deadlock: show backtrace for all threads #3472

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/borrow_tracker/stacked_borrows/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
.machine
.threads
.all_stacks()
.flatten()
.flat_map(|(_id, stack)| stack)
.map(|frame| {
frame.extra.borrow_tracker.as_ref().expect("we should have borrow tracking data")
})
Expand Down
5 changes: 2 additions & 3 deletions src/concurrency/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,11 +430,10 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
) -> &mut Vec<Frame<'mir, 'tcx, Provenance, FrameExtra<'tcx>>> {
&mut self.threads[self.active_thread].stack
}

pub fn all_stacks(
&self,
) -> impl Iterator<Item = &[Frame<'mir, 'tcx, Provenance, FrameExtra<'tcx>>]> {
self.threads.iter().map(|t| &t.stack[..])
) -> impl Iterator<Item = (ThreadId, &[Frame<'mir, 'tcx, Provenance, FrameExtra<'tcx>>])> {
self.threads.iter_enumerated().map(|(id, t)| (id, &t.stack[..]))
}

/// Create a new thread and returns its id.
Expand Down
70 changes: 58 additions & 12 deletions src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,9 +361,12 @@ pub fn report_error<'tcx, 'mir>(
};

let stacktrace = ecx.generate_stacktrace();
let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine);
let (stacktrace, mut any_pruned) = prune_stacktrace(stacktrace, &ecx.machine);

// We want to dump the allocation if this is `InvalidUninitBytes`. Since `format_error` consumes `e`, we compute the outut early.
let mut show_all_threads = false;

// We want to dump the allocation if this is `InvalidUninitBytes`.
// Since `format_interp_error` consumes `e`, we compute the outut early.
let mut extra = String::new();
match e.kind() {
UndefinedBehavior(InvalidUninitBytes(Some((alloc_id, access)))) => {
Expand All @@ -375,6 +378,15 @@ pub fn report_error<'tcx, 'mir>(
.unwrap();
writeln!(extra, "{:?}", ecx.dump_alloc(*alloc_id)).unwrap();
}
MachineStop(info) => {
let info = info.downcast_ref::<TerminationInfo>().expect("invalid MachineStop payload");
match info {
TerminationInfo::Deadlock => {
show_all_threads = true;
}
_ => {}
}
}
_ => {}
}

Expand All @@ -387,18 +399,39 @@ pub fn report_error<'tcx, 'mir>(
vec![],
helps,
&stacktrace,
Some(ecx.get_active_thread()),
&ecx.machine,
);

eprint!("{extra}"); // newlines are already in the string

if show_all_threads {
for (thread, stack) in ecx.machine.threads.all_stacks() {
if thread != ecx.get_active_thread() {
let stacktrace = Frame::generate_stacktrace_from_stack(stack);
let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine);
any_pruned |= was_pruned;
report_msg(
DiagLevel::Error,
format!("deadlock: the evaluated program deadlocked"),
vec![format!("the evaluated program deadlocked")],
vec![],
vec![],
&stacktrace,
Some(thread),
&ecx.machine,
)
}
}
}

// Include a note like `std` does when we omit frames from a backtrace
if was_pruned {
if any_pruned {
ecx.tcx.dcx().note(
"some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace",
);
}

eprint!("{extra}"); // newlines are already in the string

// Debug-dump all locals.
for (i, frame) in ecx.active_thread_stack().iter().enumerate() {
trace!("-------------------");
Expand Down Expand Up @@ -435,6 +468,7 @@ pub fn report_leaks<'mir, 'tcx>(
vec![],
vec![],
&backtrace,
None, // we don't know the thread this is from
&ecx.machine,
);
}
Expand All @@ -457,6 +491,7 @@ pub fn report_msg<'tcx>(
notes: Vec<(Option<SpanData>, String)>,
helps: Vec<(Option<SpanData>, String)>,
stacktrace: &[FrameInfo<'tcx>],
thread: Option<ThreadId>,
machine: &MiriMachine<'_, 'tcx>,
) {
let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span);
Expand Down Expand Up @@ -506,12 +541,13 @@ pub fn report_msg<'tcx>(
if extra_span {
write!(backtrace_title, " (of the first span)").unwrap();
}
let thread_name =
machine.threads.get_thread_display_name(machine.threads.get_active_thread_id());
if thread_name != "main" {
// Only print thread name if it is not `main`.
write!(backtrace_title, " on thread `{thread_name}`").unwrap();
};
if let Some(thread) = thread {
let thread_name = machine.threads.get_thread_display_name(thread);
if thread_name != "main" {
// Only print thread name if it is not `main`.
write!(backtrace_title, " on thread `{thread_name}`").unwrap();
};
}
write!(backtrace_title, ":").unwrap();
err.note(backtrace_title);
for (idx, frame_info) in stacktrace.iter().enumerate() {
Expand Down Expand Up @@ -628,7 +664,16 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
_ => vec![],
};

report_msg(diag_level, title, vec![msg], notes, helps, &stacktrace, self);
report_msg(
diag_level,
title,
vec![msg],
notes,
helps,
&stacktrace,
Some(self.threads.get_active_thread_id()),
self,
);
}
}

Expand All @@ -654,6 +699,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
vec![],
vec![],
&stacktrace,
Some(this.get_active_thread()),
&this.machine,
);
}
Expand Down
1 change: 1 addition & 0 deletions tests/fail-dep/concurrency/windows_join_main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//@only-target-windows: Uses win32 api functions
// We are making scheduler assumptions here.
//@compile-flags: -Zmiri-preemption-rate=0
//@error-in-other-file: deadlock

// On windows, joining main is not UB, but it will block a thread forever.

Expand Down
23 changes: 22 additions & 1 deletion tests/fail-dep/concurrency/windows_join_main.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,28 @@ LL | assert_eq!(WaitForSingleObject(MAIN_THREAD, INFINITE), WAIT_OBJ
= note: inside closure at RUSTLIB/core/src/macros/mod.rs:LL:CC
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)

error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
|
LL | let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE) };
| ^ the evaluated program deadlocked
|
= note: BACKTRACE:
= note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> $DIR/windows_join_main.rs:LL:CC
|
LL | / thread::spawn(|| {
LL | | unsafe {
LL | | assert_eq!(WaitForSingleObject(MAIN_THREAD, INFINITE), WAIT_OBJECT_0);
LL | | }
LL | | })
LL | | .join()
| |___________^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error
error: aborting due to 2 previous errors

1 change: 1 addition & 0 deletions tests/fail-dep/concurrency/windows_join_self.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//@only-target-windows: Uses win32 api functions
// We are making scheduler assumptions here.
//@compile-flags: -Zmiri-preemption-rate=0
//@error-in-other-file: deadlock

// On windows, a thread joining itself is not UB, but it will deadlock.

Expand Down
24 changes: 23 additions & 1 deletion tests/fail-dep/concurrency/windows_join_self.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,29 @@ LL | assert_eq!(WaitForSingleObject(native, INFINITE), WAIT_OBJECT_0
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at $DIR/windows_join_self.rs:LL:CC

error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
|
LL | let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE) };
| ^ the evaluated program deadlocked
|
= note: BACKTRACE:
= note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> $DIR/windows_join_self.rs:LL:CC
|
LL | / thread::spawn(|| {
LL | | unsafe {
LL | | let native = GetCurrentThread();
LL | | assert_eq!(WaitForSingleObject(native, INFINITE), WAIT_OBJECT_0);
LL | | }
LL | | })
LL | | .join()
| |___________^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error
error: aborting due to 2 previous errors

1 change: 1 addition & 0 deletions tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//@ignore-target-windows: No libc on Windows
//@error-in-other-file: deadlock

use std::cell::UnsafeCell;
use std::sync::Arc;
Expand Down
21 changes: 20 additions & 1 deletion tests/fail-dep/shims/sync/libc_pthread_mutex_deadlock.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,26 @@ LL | assert_eq!(libc::pthread_mutex_lock(lock_copy.0.get() as *mut _
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at $DIR/libc_pthread_mutex_deadlock.rs:LL:CC

error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
|
LL | let ret = libc::pthread_join(self.id, ptr::null_mut());
| ^ the evaluated program deadlocked
|
= note: BACKTRACE:
= note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> $DIR/libc_pthread_mutex_deadlock.rs:LL:CC
|
LL | / thread::spawn(move || {
LL | | assert_eq!(libc::pthread_mutex_lock(lock_copy.0.get() as *mut _), 0);
LL | | })
LL | | .join()
| |_______________^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error
error: aborting due to 2 previous errors

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//@ignore-target-windows: No libc on Windows
//@error-in-other-file: deadlock

use std::cell::UnsafeCell;
use std::sync::Arc;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,26 @@ LL | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mu
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at $DIR/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC

error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
|
LL | let ret = libc::pthread_join(self.id, ptr::null_mut());
| ^ the evaluated program deadlocked
|
= note: BACKTRACE:
= note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> $DIR/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC
|
LL | / thread::spawn(move || {
LL | | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0);
LL | | })
LL | | .join()
| |_______________^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error
error: aborting due to 2 previous errors

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//@ignore-target-windows: No libc on Windows
//@error-in-other-file: deadlock

use std::cell::UnsafeCell;
use std::sync::Arc;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,26 @@ LL | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mu
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at $DIR/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC

error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
|
LL | let ret = libc::pthread_join(self.id, ptr::null_mut());
| ^ the evaluated program deadlocked
|
= note: BACKTRACE:
= note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> $DIR/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC
|
LL | / thread::spawn(move || {
LL | | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mut _), 0);
LL | | })
LL | | .join()
| |_______________^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error
error: aborting due to 2 previous errors

4 changes: 2 additions & 2 deletions tests/fail/uninit/uninit_alloc_diagnostic.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ note: inside `main`
LL | drop(slice1.cmp(slice2));
| ^^^^^^^^^^^^^^^^^^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

Uninitialized memory occurred at ALLOC[0x4..0x10], in this allocation:
ALLOC (Rust heap, size: 32, align: 8) {
0x00 │ 41 42 43 44 __ __ __ __ __ __ __ __ __ __ __ __ │ ABCD░░░░░░░░░░░░
0x10 │ 00 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ │ .░░░░░░░░░░░░░░░
}

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ note: inside `main`
LL | drop(slice1.cmp(slice2));
| ^^^^^^^^^^^^^^^^^^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

Uninitialized memory occurred at ALLOC[0x4..0x8], in this allocation:
ALLOC (Rust heap, size: 16, align: 8) {
╾42[ALLOC]<TAG> (1 ptr byte)╼ 12 13 ╾43[ALLOC]<TAG> (1 ptr byte)╼ __ __ __ __ __ __ __ __ __ __ __ __ │ ━..━░░░░░░░░░░░░
Expand All @@ -28,5 +26,7 @@ ALLOC (global (static or const), size: 1, align: 1) {
00 │ .
}

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error