diff --git a/src/libstd/sync/mpsc/oneshot.rs b/src/libstd/sync/mpsc/oneshot.rs index 36928c428e331..2c74cfc3e3287 100644 --- a/src/libstd/sync/mpsc/oneshot.rs +++ b/src/libstd/sync/mpsc/oneshot.rs @@ -255,8 +255,11 @@ impl Packet { // to stay valid while we take the data). DATA => unsafe { (&mut *self.data.get()).take().unwrap(); }, - // We're the only ones that can block on this port - _ => unreachable!() + // We're the only ones that can block on this port, but we may be + // unwinding + ptr => unsafe { + drop(SignalToken::cast_from_usize(ptr)); + } } } diff --git a/src/libstd/sync/mpsc/sync.rs b/src/libstd/sync/mpsc/sync.rs index bce42bc69cda4..216e4307d9fd1 100644 --- a/src/libstd/sync/mpsc/sync.rs +++ b/src/libstd/sync/mpsc/sync.rs @@ -399,7 +399,12 @@ impl Packet { *guard.canceled.take().unwrap() = true; Some(token) } - BlockedReceiver(..) => unreachable!(), + // We're the only ones that can block on this port, but we may be + // unwinding + BlockedReceiver(token) => { + drop(token); + None + } }; mem::drop(guard); diff --git a/src/test/run-fail/mpsc-recv-unwind/common.rs b/src/test/run-fail/mpsc-recv-unwind/common.rs new file mode 100644 index 0000000000000..ea755ed794366 --- /dev/null +++ b/src/test/run-fail/mpsc-recv-unwind/common.rs @@ -0,0 +1,27 @@ +// ignore-test + +// Common code used by the other tests in this directory + +extern crate libc; + +use std::sync::{Arc, Barrier, mpsc}; +use std::time::Duration; +use std::{env, thread, process}; + +pub fn panic_inside_mpsc_recv(r: mpsc::Receiver<()>) { + env::set_var("LIBC_FATAL_STDERR_", "1"); + let barrier = Arc::new(Barrier::new(2)); + let main_thread = unsafe { libc::pthread_self() }; + let barrier2 = barrier.clone(); + thread::spawn(move || { + barrier2.wait(); + // try to make sure main thread proceeds into recv + thread::sleep(Duration::from_millis(100)); + unsafe { libc::pthread_cancel(main_thread); } + thread::sleep(Duration::from_millis(2000)); + println!("Deadlock detected"); + process::exit(1); + }); + barrier.wait(); + r.recv().unwrap() +} diff --git a/src/test/run-fail/mpsc-recv-unwind/oneshot.rs b/src/test/run-fail/mpsc-recv-unwind/oneshot.rs new file mode 100644 index 0000000000000..f99ab28aec94e --- /dev/null +++ b/src/test/run-fail/mpsc-recv-unwind/oneshot.rs @@ -0,0 +1,22 @@ +// Test that unwinding while an MPSC channel receiver is blocking doesn't panic +// or deadlock inside the MPSC implementation. +// +// Various platforms may trigger unwinding while a thread is blocked, due to an +// error condition. It can be tricky to trigger such unwinding. The test here +// uses pthread_cancel on Linux. If at some point in the future, pthread_cancel +// no longer unwinds through the MPSC code, that doesn't mean this test should +// be removed. Instead, another way should be found to trigger unwinding in a +// blocked MPSC channel receiver. + +// only-linux +// error-pattern:FATAL: exception not rethrown +// failure-status:signal:6 + +#![feature(rustc_private)] + +mod common; + +fn main() { + let (_s, r) = std::sync::mpsc::channel(); + common::panic_inside_mpsc_recv(r); +} diff --git a/src/test/run-fail/mpsc-recv-unwind/shared.rs b/src/test/run-fail/mpsc-recv-unwind/shared.rs new file mode 100644 index 0000000000000..4b895a3ea510e --- /dev/null +++ b/src/test/run-fail/mpsc-recv-unwind/shared.rs @@ -0,0 +1,23 @@ +// Test that unwinding while an MPSC channel receiver is blocking doesn't panic +// or deadlock inside the MPSC implementation. +// +// Various platforms may trigger unwinding while a thread is blocked, due to an +// error condition. It can be tricky to trigger such unwinding. The test here +// uses pthread_cancel on Linux. If at some point in the future, pthread_cancel +// no longer unwinds through the MPSC code, that doesn't mean this test should +// be removed. Instead, another way should be found to trigger unwinding in a +// blocked MPSC channel receiver. + +// only-linux +// error-pattern:FATAL: exception not rethrown +// failure-status:signal:6 + +#![feature(rustc_private)] + +mod common; + +fn main() { + let (s, r) = std::sync::mpsc::channel(); + s.clone(); + common::panic_inside_mpsc_recv(r); +} diff --git a/src/test/run-fail/mpsc-recv-unwind/stream.rs b/src/test/run-fail/mpsc-recv-unwind/stream.rs new file mode 100644 index 0000000000000..438a9eca4aaf5 --- /dev/null +++ b/src/test/run-fail/mpsc-recv-unwind/stream.rs @@ -0,0 +1,26 @@ +// Test that unwinding while an MPSC channel receiver is blocking doesn't panic +// or deadlock inside the MPSC implementation. +// +// Various platforms may trigger unwinding while a thread is blocked, due to an +// error condition. It can be tricky to trigger such unwinding. The test here +// uses pthread_cancel on Linux. If at some point in the future, pthread_cancel +// no longer unwinds through the MPSC code, that doesn't mean this test should +// be removed. Instead, another way should be found to trigger unwinding in a +// blocked MPSC channel receiver. + +// only-linux +// error-pattern:FATAL: exception not rethrown +// failure-status:signal:6 + +#![feature(rustc_private)] + +mod common; + +fn main() { + let (s, r) = std::sync::mpsc::channel(); + s.send(()).unwrap(); + r.recv().unwrap(); + s.send(()).unwrap(); + r.recv().unwrap(); + common::panic_inside_mpsc_recv(r); +} diff --git a/src/test/run-fail/mpsc-recv-unwind/sync.rs b/src/test/run-fail/mpsc-recv-unwind/sync.rs new file mode 100644 index 0000000000000..b447084aa69bf --- /dev/null +++ b/src/test/run-fail/mpsc-recv-unwind/sync.rs @@ -0,0 +1,22 @@ +// Test that unwinding while an MPSC channel receiver is blocking doesn't panic +// or deadlock inside the MPSC implementation. +// +// Various platforms may trigger unwinding while a thread is blocked, due to an +// error condition. It can be tricky to trigger such unwinding. The test here +// uses pthread_cancel on Linux. If at some point in the future, pthread_cancel +// no longer unwinds through the MPSC code, that doesn't mean this test should +// be removed. Instead, another way should be found to trigger unwinding in a +// blocked MPSC channel receiver. + +// only-linux +// error-pattern:FATAL: exception not rethrown +// failure-status:signal:6 + +#![feature(rustc_private)] + +mod common; + +fn main() { + let (_s, r) = std::sync::mpsc::sync_channel(0); + common::panic_inside_mpsc_recv(r); +} diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index 591d92f0cfa14..3fca83368fa53 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -271,6 +271,12 @@ impl EarlyProps { } } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum FailureStatus { + ExitCode(i32), + Signal(i32), +} + #[derive(Clone, Debug)] pub struct TestProps { // Lines that should be expected, in order, on standard out @@ -332,7 +338,7 @@ pub struct TestProps { // customized normalization rules pub normalize_stdout: Vec<(String, String)>, pub normalize_stderr: Vec<(String, String)>, - pub failure_status: i32, + pub failure_status: FailureStatus, pub run_rustfix: bool, pub rustfix_only_machine_applicable: bool, } @@ -367,7 +373,7 @@ impl TestProps { disable_ui_testing_normalization: false, normalize_stdout: vec![], normalize_stderr: vec![], - failure_status: -1, + failure_status: FailureStatus::ExitCode(-1), run_rustfix: false, rustfix_only_machine_applicable: false, } @@ -519,10 +525,10 @@ impl TestProps { } }); - if self.failure_status == -1 { + if self.failure_status == FailureStatus::ExitCode(-1) { self.failure_status = match config.mode { - Mode::RunFail => 101, - _ => 1, + Mode::RunFail => FailureStatus::ExitCode(101), + _ => FailureStatus::ExitCode(1), }; } @@ -649,11 +655,13 @@ impl Config { self.parse_name_directive(line, "pretty-compare-only") } - fn parse_failure_status(&self, line: &str) -> Option { - match self.parse_name_value_directive(line, "failure-status") { - Some(code) => code.trim().parse::().ok(), - _ => None, + fn parse_failure_status(&self, line: &str) -> Option { + let status_str = self.parse_name_value_directive(line, "failure-status")?; + if let Ok(code) = status_str.trim().parse::() { + return Some(FailureStatus::ExitCode(code)) } + let signal = self.parse_name_value_directive(&status_str, "signal")?; + Some(FailureStatus::Signal(signal.trim().parse::().ok()?)) } fn parse_compile_pass(&self, line: &str) -> bool { diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index ff201b03a0bdc..82bc66f7f3e8e 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -8,12 +8,13 @@ use common::{Incremental, MirOpt, RunMake, Ui}; use diff; use errors::{self, Error, ErrorKind}; use filetime::FileTime; -use header::TestProps; +use header::{TestProps, FailureStatus}; use json; use regex::Regex; use rustfix::{apply_suggestions, get_suggestions_from_json, Filter}; use util::{logv, PathBufExt}; +use std::cmp::PartialEq; use std::collections::hash_map::DefaultHasher; use std::collections::{HashMap, HashSet, VecDeque}; use std::env; @@ -373,8 +374,23 @@ impl<'test> TestCx<'test> { } fn check_correct_failure_status(&self, proc_res: &ProcRes) { - let expected_status = Some(self.props.failure_status); - let received_status = proc_res.status.code(); + impl PartialEq for FailureStatus { + fn eq(&self, other: &ExitStatus) -> bool { + match *self { + #[cfg(unix)] + FailureStatus::Signal(signal) => { + use std::os::unix::process::ExitStatusExt; + other.signal() == Some(signal) + } + #[cfg(not(unix))] + FailureStatus::Signal(signal) => false, + FailureStatus::ExitCode(code) => other.code() == Some(code), + } + } + } + + let expected_status = self.props.failure_status; + let received_status = proc_res.status; if expected_status != received_status { self.fatal_proc_rec(