Skip to content

Commit 02f78b0

Browse files
committed
Auto merge of #1858 - RalfJung:thread-leaks, r=oli-obk
also ignore 'thread leaks' with -Zmiri-ignore-leaks This is a step towards #1371. The remaining hard part would be supporting checking for memory leaks when there are threads still running. For now we elegantly avoid this problem by using the same flag to control both of these checks. :)
2 parents e445f78 + 78bcd12 commit 02f78b0

File tree

6 files changed

+66
-12
lines changed

6 files changed

+66
-12
lines changed

README.md

+2-1
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

+10
Original file line numberDiff line numberDiff line change
@@ -306,10 +306,20 @@ pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) ->
306306
match res {
307307
Ok(return_code) => {
308308
if !ignore_leaks {
309+
// Check for thread leaks.
310+
if !ecx.have_all_terminated() {
311+
tcx.sess.err(
312+
"the main thread terminated without waiting for all remaining threads",
313+
);
314+
tcx.sess.note_without_error("pass `-Zmiri-ignore-leaks` to disable this check");
315+
return None;
316+
}
317+
// Check for memory leaks.
309318
info!("Additonal static roots: {:?}", ecx.machine.static_roots);
310319
let leaks = ecx.memory.leak_report(&ecx.machine.static_roots);
311320
if leaks != 0 {
312321
tcx.sess.err("the evaluated program leaked memory");
322+
tcx.sess.note_without_error("pass `-Zmiri-ignore-leaks` to disable this check");
313323
// Ignore the provided return code - let the reported error
314324
// determine the return code.
315325
return None;

src/thread.rs

+12-9
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

+2-2
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

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// ignore-windows: Concurrency on Windows is not supported yet.
2+
// compile-flags: -Zmiri-ignore-leaks
3+
4+
//! Test that leaking threads works, and that their destructors are not executed.
5+
6+
use std::cell::RefCell;
7+
8+
struct LoudDrop(i32);
9+
impl Drop for LoudDrop {
10+
fn drop(&mut self) {
11+
eprintln!("Dropping {}", self.0);
12+
}
13+
}
14+
15+
thread_local! {
16+
static X: RefCell<Option<LoudDrop>> = RefCell::new(None);
17+
}
18+
19+
fn main() {
20+
X.with(|x| *x.borrow_mut() = Some(LoudDrop(0)));
21+
22+
// Set up a channel so that we can learn when the other thread initialized `X`
23+
// (so that we are sure there is something to drop).
24+
let (send, recv) = std::sync::mpsc::channel::<()>();
25+
26+
let _detached = std::thread::spawn(move || {
27+
X.with(|x| *x.borrow_mut() = Some(LoudDrop(1)));
28+
send.send(()).unwrap();
29+
std::thread::yield_now();
30+
loop {}
31+
});
32+
33+
std::thread::yield_now();
34+
35+
// Wait until child thread has initialized its `X`.
36+
let () = recv.recv().unwrap();
37+
}
+3
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)