Skip to content
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

Allow backtraces to cross thread boundaries #4093

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
39 changes: 31 additions & 8 deletions src/concurrency/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ pub struct Thread<'tcx> {
/// The join status.
join_status: ThreadJoinStatus,

// ThreadId that spawned this thread and backtrace to where this thread was spawned
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// ThreadId that spawned this thread and backtrace to where this thread was spawned
/// ThreadId that spawned this thread and backtrace to where this thread was spawned.

thread_spawn_context: Option<(ThreadId, Vec<FrameInfo<'tcx>>)>,

/// Stack of active panic payloads for the current thread. Used for storing
/// the argument of the call to `miri_start_unwind` (the panic payload) when unwinding.
/// This is pointer-sized, and matches the `Payload` type in `src/libpanic_unwind/miri.rs`.
Expand Down Expand Up @@ -321,13 +324,18 @@ impl<'tcx> std::fmt::Debug for Thread<'tcx> {
}

impl<'tcx> Thread<'tcx> {
fn new(name: Option<&str>, on_stack_empty: Option<StackEmptyCallback<'tcx>>) -> Self {
fn new(
name: Option<&str>,
on_stack_empty: Option<StackEmptyCallback<'tcx>>,
thread_spawn_context: Option<(ThreadId, Vec<FrameInfo<'tcx>>)>,
) -> Self {
Self {
state: ThreadState::Enabled,
thread_name: name.map(|name| Vec::from(name.as_bytes())),
stack: Vec::new(),
top_user_relevant_frame: None,
join_status: ThreadJoinStatus::Joinable,
thread_spawn_context,
panic_payloads: Vec::new(),
last_error: None,
on_stack_empty,
Expand All @@ -345,6 +353,7 @@ impl VisitProvenance for Thread<'_> {
state: _,
thread_name: _,
join_status: _,
thread_spawn_context: _,
on_stack_empty: _, // we assume the closure captures no GC-relevant state
} = self;

Expand Down Expand Up @@ -475,7 +484,7 @@ impl<'tcx> Default for ThreadManager<'tcx> {
fn default() -> Self {
let mut threads = IndexVec::new();
// Create the main thread and add it to the list of threads.
threads.push(Thread::new(Some("main"), None));
threads.push(Thread::new(Some("main"), None, None));
Self {
active_thread: ThreadId::MAIN_THREAD,
threads,
Expand Down Expand Up @@ -542,9 +551,13 @@ impl<'tcx> ThreadManager<'tcx> {
}

/// Create a new thread and returns its id.
fn create_thread(&mut self, on_stack_empty: StackEmptyCallback<'tcx>) -> ThreadId {
fn create_thread(
&mut self,
on_stack_empty: StackEmptyCallback<'tcx>,
thread_spawn_context: Option<(ThreadId, Vec<FrameInfo<'tcx>>)>,
) -> ThreadId {
let new_thread_id = ThreadId::new(self.threads.len());
self.threads.push(Thread::new(None, Some(on_stack_empty)));
self.threads.push(Thread::new(None, Some(on_stack_empty), thread_spawn_context));
new_thread_id
}

Expand Down Expand Up @@ -711,6 +724,13 @@ impl<'tcx> ThreadManager<'tcx> {
self.threads[thread].thread_display_name(thread)
}

pub fn get_thread_spawn_context(
&self,
thread: ThreadId,
) -> Option<&(ThreadId, Vec<FrameInfo<'tcx>>)> {
self.threads[thread].thread_spawn_context.as_ref()
}

/// Put the thread into the blocked state.
fn block_thread(
&mut self,
Expand Down Expand Up @@ -924,10 +944,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let this = self.eval_context_mut();

// Create the new thread
let new_thread_id = this.machine.threads.create_thread({
let mut state = tls::TlsDtorsState::default();
Box::new(move |m| state.on_stack_empty(m))
});
let new_thread_id = this.machine.threads.create_thread(
{
let mut state = tls::TlsDtorsState::default();
Box::new(move |m| state.on_stack_empty(m))
},
Some((this.machine.threads.active_thread(), this.generate_stacktrace())),
);
let current_span = this.machine.current_span();
if let Some(data_race) = &mut this.machine.data_race {
data_race.thread_created(&this.machine.threads, new_thread_id, current_span);
Expand Down
30 changes: 30 additions & 0 deletions src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,36 @@ pub fn report_msg<'tcx>(
}
}

// Traverse thread spawns to display thread backtraces up to main thread
if let Some(mut thread) = thread {
let mut thread_spawn_context = machine.threads.get_thread_spawn_context(thread);

while let Some((spawning_thread, thread_spawn_backtrace)) = thread_spawn_context {
err.note(format!(
"thread `{}` was spawned by thread `{}`",
machine.threads.get_thread_display_name(thread),
machine.threads.get_thread_display_name(*spawning_thread)
oli-obk marked this conversation as resolved.
Show resolved Hide resolved
));
let (pruned_stacktrace, _was_pruned) =
prune_stacktrace(thread_spawn_backtrace.to_vec(), machine);

for (idx, frame_info) in pruned_stacktrace.iter().enumerate() {
let is_local = machine.is_local(frame_info);
// No span for non-local frames except the first frame (which is the error site).
if is_local && idx > 0 {
err.subdiagnostic(frame_info.as_note(machine.tcx));
Comment on lines +606 to +608
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is not right, the first frame is not the error site.

} else {
let sm = sess.source_map();
let span = sm.span_to_embeddable_string(frame_info.span);
err.note(format!("{frame_info} at {span}"));
}
}
Comment on lines +604 to +614
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is duplicating existing logic. Can you move this into a shared helper fn?


thread = *spawning_thread;
thread_spawn_context = machine.threads.get_thread_spawn_context(thread);
}
}

err.emit();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ error: Undefined Behavior: calling a function with more arguments than it expect
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `unnamed-ID`:
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `main` at tests/fail-dep/concurrency/libc_pthread_create_too_few_args.rs:LL:CC

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ error: Undefined Behavior: calling a function with fewer arguments than it requi
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `unnamed-ID`:
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `main` at tests/fail-dep/concurrency/libc_pthread_create_too_many_args.rs:LL:CC

error: aborting due to 1 previous error

16 changes: 16 additions & 0 deletions tests/fail-dep/concurrency/libc_pthread_join_main.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ LL | assert_eq!(libc::pthread_join(thread_id, ptr::null_mut()), 0);
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_join_main.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_join_main.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_join_main.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_join_main.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_join_main.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> tests/fail-dep/concurrency/libc_pthread_join_main.rs:LL:CC
|
LL | let handle = thread::spawn(move || {
| __________________^
LL | | unsafe {
LL | | assert_eq!(libc::pthread_join(thread_id, ptr::null_mut()), 0);
LL | | }
LL | | });
| |______^

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

Expand Down
14 changes: 14 additions & 0 deletions tests/fail-dep/concurrency/libc_pthread_join_multiple.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ LL | ... assert_eq!(libc::pthread_join(native_copy, ptr::null_mut()), 0);
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_join_multiple.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_join_multiple.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_join_multiple.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_join_multiple.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_join_multiple.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> tests/fail-dep/concurrency/libc_pthread_join_multiple.rs:LL:CC
|
LL | ... let handle = thread::spawn(move || {
| ____________________^
LL | | ... assert_eq!(libc::pthread_join(native_copy, ptr::null_mut()), 0);
LL | | ... });
| |________^

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

Expand Down
17 changes: 17 additions & 0 deletions tests/fail-dep/concurrency/libc_pthread_join_self.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@ LL | assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_join_self.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_join_self.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_join_self.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_join_self.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_join_self.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> tests/fail-dep/concurrency/libc_pthread_join_self.rs:LL:CC
|
LL | let handle = thread::spawn(|| {
| __________________^
LL | | unsafe {
LL | | let native: libc::pthread_t = libc::pthread_self();
LL | | assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
LL | | }
LL | | });
| |______^

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

Expand Down
13 changes: 13 additions & 0 deletions tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ LL | assert_eq!(libc::pthread_mutex_lock(lock_copy.0.get() as *mut _
|
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_mutex_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
Comment on lines +10 to +14
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not pretty to show this for every backtrace. We spent a bunch of effort making backtraces nice, i.e. they usually stop at main and don't go all the way to the start lang item. We should do something similar here to avoid drowning the user in noise.

I could imagine two options:

  • we mark these frames as #[caller_location] in std (we have done this for a bunch of other functions to make backtraces prettier)
  • when pruning the stack for another thread, we also remove all non-local frames at the top

I am leaning towards the first option since the second option might prune too much when the thread is spawned deep inside some other library.

note: inside `main`
--> tests/fail-dep/concurrency/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 | | })
| |__________^

error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
Expand Down
13 changes: 13 additions & 0 deletions tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ LL | ...t_eq!(libc::pthread_mutex_unlock(lock_copy.0.get() as *mut _), 0);
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> tests/fail-dep/concurrency/libc_pthread_mutex_wrong_owner.rs:LL:CC
|
LL | / ... thread::spawn(move || {
LL | | ... assert_eq!(libc::pthread_mutex_unlock(lock_copy.0.get() as *mut _), 0);
LL | | ... })
| |________^

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ LL | ... assert_eq!(libc::pthread_rwlock_unlock(lock_copy.0.get() as *mut _),
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> tests/fail-dep/concurrency/libc_pthread_rwlock_read_wrong_owner.rs:LL:CC
|
LL | / ... thread::spawn(move || {
LL | | ... assert_eq!(libc::pthread_rwlock_unlock(lock_copy.0.get() as *mut _), 0);
LL | | ... })
| |________^

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ LL | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mu
|
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_read_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> tests/fail-dep/concurrency/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 | | })
| |__________^

error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ LL | assert_eq!(libc::pthread_rwlock_wrlock(lock_copy.0.get() as *mu
|
= note: BACKTRACE on thread `unnamed-ID`:
= note: inside closure at tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC
= note: thread `unnamed-ID` was spawned by thread `main`
= note: inside `std::sys::pal::PLATFORM::thread::Thread::new` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked_::<'_, {closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn_unchecked::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::Builder::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
= note: inside `std::thread::spawn::<{closure@tests/fail-dep/concurrency/libc_pthread_rwlock_write_write_deadlock.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/mod.rs:LL:CC
note: inside `main`
--> tests/fail-dep/concurrency/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 | | })
| |__________^

error: deadlock: the evaluated program deadlocked
--> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC
Expand Down
Loading
Loading