From 71efd950d17faabc4025acb460c4000bf97978f7 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 25 Jul 2021 14:08:12 +0200 Subject: [PATCH] also ignore 'thread leaks' with -Zmiri-ignore-leaks --- README.md | 3 ++- src/eval.rs | 6 +++++ src/thread.rs | 21 +++++++++------- .../libc_pthread_create_main_terminate.rs | 4 ++-- tests/run-pass/threadleak_ignored.rs | 24 +++++++++++++++++++ tests/run-pass/threadleak_ignored.stderr | 3 +++ 6 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 tests/run-pass/threadleak_ignored.rs create mode 100644 tests/run-pass/threadleak_ignored.stderr diff --git a/README.md b/README.md index cbac48db5ea..7cd802762bf 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,8 @@ environment variable: the host so that it cannot be accessed by the program. Can be used multiple times to exclude several variables. On Windows, the `TERM` environment variable is excluded by default. -* `-Zmiri-ignore-leaks` disables the memory leak checker. +* `-Zmiri-ignore-leaks` disables the memory leak checker, and also allows some + remaining threads to exist when the main thread exits. * `-Zmiri-measureme=` enables `measureme` profiling for the interpreted program. This can be used to find which parts of your program are executing slowly under Miri. The profile is written out to a file with the prefix ``, and can be processed diff --git a/src/eval.rs b/src/eval.rs index ae9ff9c1f5a..9531a2d78ab 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -300,6 +300,12 @@ pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) -> match res { Ok(return_code) => { if !ignore_leaks { + // Check for thread leaks. + if !ecx.have_all_terminated() { + tcx.sess.err("the main thread terminated without waiting for all remaining threads"); + return None; + } + // Check for memory leaks. info!("Additonal static roots: {:?}", ecx.machine.static_roots); let leaks = ecx.memory.leak_report(&ecx.machine.static_roots); if leaks != 0 { diff --git a/src/thread.rs b/src/thread.rs index de8e41224b2..a5deceb6e71 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -302,6 +302,11 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { self.threads[thread_id].state == ThreadState::Terminated } + /// Have all threads terminated? + fn have_all_terminated(&self) -> bool { + self.threads.iter().all(|thread| thread.state == ThreadState::Terminated) + } + /// Enable the thread for execution. The thread must be terminated. fn enable_thread(&mut self, thread_id: ThreadId) { assert!(self.has_terminated(thread_id)); @@ -491,15 +496,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { // If we get here again and the thread is *still* terminated, there are no more dtors to run. if self.threads[MAIN_THREAD].state == ThreadState::Terminated { // The main thread terminated; stop the program. - if self.threads.iter().any(|thread| thread.state != ThreadState::Terminated) { - // FIXME: This check should be either configurable or just emit - // a warning. For example, it seems normal for a program to - // terminate without waiting for its detached threads to - // terminate. However, this case is not trivial to support - // because we also probably do not want to consider the memory - // owned by these threads as leaked. - throw_unsup_format!("the main thread terminated without waiting for other threads"); - } + // We do *not* run TLS dtors of remaining threads, which seems to match rustc behavior. return Ok(SchedulingAction::Stop); } // This thread and the program can keep going. @@ -645,6 +642,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx this.machine.threads.has_terminated(thread_id) } + #[inline] + fn have_all_terminated(&self) -> bool { + let this = self.eval_context_ref(); + this.machine.threads.have_all_terminated() + } + #[inline] fn enable_thread(&mut self, thread_id: ThreadId) { let this = self.eval_context_mut(); diff --git a/tests/compile-fail/concurrency/libc_pthread_create_main_terminate.rs b/tests/compile-fail/concurrency/libc_pthread_create_main_terminate.rs index 35ee03242d1..9b576bbb086 100644 --- a/tests/compile-fail/concurrency/libc_pthread_create_main_terminate.rs +++ b/tests/compile-fail/concurrency/libc_pthread_create_main_terminate.rs @@ -1,5 +1,5 @@ // ignore-windows: No libc on Windows -// error-pattern: unsupported operation: the main thread terminated without waiting for other threads +// error-pattern: the main thread terminated without waiting for all remaining threads // Check that we terminate the program when the main thread terminates. @@ -10,7 +10,7 @@ extern crate libc; use std::{mem, ptr}; extern "C" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void { - ptr::null_mut() + loop {} } fn main() { diff --git a/tests/run-pass/threadleak_ignored.rs b/tests/run-pass/threadleak_ignored.rs new file mode 100644 index 00000000000..14a7449f339 --- /dev/null +++ b/tests/run-pass/threadleak_ignored.rs @@ -0,0 +1,24 @@ +// compile-flags: -Zmiri-ignore-leaks + +//! Test that leaking threads works, and that their destructors are not executed. + +use std::cell::RefCell; + +struct LoudDrop(i32); +impl Drop for LoudDrop { + fn drop(&mut self) { + eprintln!("Dropping {}", self.0); + } +} + +thread_local! { + static X: RefCell> = RefCell::new(None); +} + +fn main() { + X.with(|x| *x.borrow_mut() = Some(LoudDrop(0))); + + let _detached = std::thread::spawn(|| { + X.with(|x| *x.borrow_mut() = Some(LoudDrop(1))); + }); +} diff --git a/tests/run-pass/threadleak_ignored.stderr b/tests/run-pass/threadleak_ignored.stderr new file mode 100644 index 00000000000..aa037511853 --- /dev/null +++ b/tests/run-pass/threadleak_ignored.stderr @@ -0,0 +1,3 @@ +warning: thread support is experimental and incomplete: weak memory effects are not emulated. + +Dropping 0