Skip to content

Commit 71efd95

Browse files
committed
also ignore 'thread leaks' with -Zmiri-ignore-leaks
1 parent e2872a3 commit 71efd95

File tree

6 files changed

+49
-12
lines changed

6 files changed

+49
-12
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,8 @@ environment variable:
230230
the host so that it cannot be accessed by the program. Can be used multiple
231231
times to exclude several variables. On Windows, the `TERM` environment
232232
variable is excluded by default.
233-
* `-Zmiri-ignore-leaks` disables the memory leak checker.
233+
* `-Zmiri-ignore-leaks` disables the memory leak checker, and also allows some
234+
remaining threads to exist when the main thread exits.
234235
* `-Zmiri-measureme=<name>` enables `measureme` profiling for the interpreted program.
235236
This can be used to find which parts of your program are executing slowly under Miri.
236237
The profile is written out to a file with the prefix `<name>`, and can be processed

src/eval.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,12 @@ pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) ->
300300
match res {
301301
Ok(return_code) => {
302302
if !ignore_leaks {
303+
// Check for thread leaks.
304+
if !ecx.have_all_terminated() {
305+
tcx.sess.err("the main thread terminated without waiting for all remaining threads");
306+
return None;
307+
}
308+
// Check for memory leaks.
303309
info!("Additonal static roots: {:?}", ecx.machine.static_roots);
304310
let leaks = ecx.memory.leak_report(&ecx.machine.static_roots);
305311
if leaks != 0 {

src/thread.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,11 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
302302
self.threads[thread_id].state == ThreadState::Terminated
303303
}
304304

305+
/// Have all threads terminated?
306+
fn have_all_terminated(&self) -> bool {
307+
self.threads.iter().all(|thread| thread.state == ThreadState::Terminated)
308+
}
309+
305310
/// Enable the thread for execution. The thread must be terminated.
306311
fn enable_thread(&mut self, thread_id: ThreadId) {
307312
assert!(self.has_terminated(thread_id));
@@ -491,15 +496,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
491496
// If we get here again and the thread is *still* terminated, there are no more dtors to run.
492497
if self.threads[MAIN_THREAD].state == ThreadState::Terminated {
493498
// The main thread terminated; stop the program.
494-
if self.threads.iter().any(|thread| thread.state != ThreadState::Terminated) {
495-
// FIXME: This check should be either configurable or just emit
496-
// a warning. For example, it seems normal for a program to
497-
// terminate without waiting for its detached threads to
498-
// terminate. However, this case is not trivial to support
499-
// because we also probably do not want to consider the memory
500-
// owned by these threads as leaked.
501-
throw_unsup_format!("the main thread terminated without waiting for other threads");
502-
}
499+
// We do *not* run TLS dtors of remaining threads, which seems to match rustc behavior.
503500
return Ok(SchedulingAction::Stop);
504501
}
505502
// This thread and the program can keep going.
@@ -645,6 +642,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
645642
this.machine.threads.has_terminated(thread_id)
646643
}
647644

645+
#[inline]
646+
fn have_all_terminated(&self) -> bool {
647+
let this = self.eval_context_ref();
648+
this.machine.threads.have_all_terminated()
649+
}
650+
648651
#[inline]
649652
fn enable_thread(&mut self, thread_id: ThreadId) {
650653
let this = self.eval_context_mut();

tests/compile-fail/concurrency/libc_pthread_create_main_terminate.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ignore-windows: No libc on Windows
2-
// error-pattern: unsupported operation: the main thread terminated without waiting for other threads
2+
// error-pattern: the main thread terminated without waiting for all remaining threads
33

44
// Check that we terminate the program when the main thread terminates.
55

@@ -10,7 +10,7 @@ extern crate libc;
1010
use std::{mem, ptr};
1111

1212
extern "C" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void {
13-
ptr::null_mut()
13+
loop {}
1414
}
1515

1616
fn main() {

tests/run-pass/threadleak_ignored.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// compile-flags: -Zmiri-ignore-leaks
2+
3+
//! Test that leaking threads works, and that their destructors are not executed.
4+
5+
use std::cell::RefCell;
6+
7+
struct LoudDrop(i32);
8+
impl Drop for LoudDrop {
9+
fn drop(&mut self) {
10+
eprintln!("Dropping {}", self.0);
11+
}
12+
}
13+
14+
thread_local! {
15+
static X: RefCell<Option<LoudDrop>> = RefCell::new(None);
16+
}
17+
18+
fn main() {
19+
X.with(|x| *x.borrow_mut() = Some(LoudDrop(0)));
20+
21+
let _detached = std::thread::spawn(|| {
22+
X.with(|x| *x.borrow_mut() = Some(LoudDrop(1)));
23+
});
24+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
warning: thread support is experimental and incomplete: weak memory effects are not emulated.
2+
3+
Dropping 0

0 commit comments

Comments
 (0)