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

Fix weird behavior when panicking inside synchronization primitives #58042

Closed
wants to merge 1 commit into from
Closed
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
7 changes: 5 additions & 2 deletions src/libstd/sync/mpsc/oneshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,11 @@ impl<T> Packet<T> {
// 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));
}
}
}

Expand Down
7 changes: 6 additions & 1 deletion src/libstd/sync/mpsc/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,12 @@ impl<T> Packet<T> {
*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);

Expand Down
27 changes: 27 additions & 0 deletions src/test/run-fail/mpsc-recv-unwind/common.rs
Original file line number Diff line number Diff line change
@@ -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()
}
22 changes: 22 additions & 0 deletions src/test/run-fail/mpsc-recv-unwind/oneshot.rs
Original file line number Diff line number Diff line change
@@ -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);
}
23 changes: 23 additions & 0 deletions src/test/run-fail/mpsc-recv-unwind/shared.rs
Original file line number Diff line number Diff line change
@@ -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);
}
26 changes: 26 additions & 0 deletions src/test/run-fail/mpsc-recv-unwind/stream.rs
Original file line number Diff line number Diff line change
@@ -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);
}
22 changes: 22 additions & 0 deletions src/test/run-fail/mpsc-recv-unwind/sync.rs
Original file line number Diff line number Diff line change
@@ -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);
}
26 changes: 17 additions & 9 deletions src/tools/compiletest/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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),
};
}

Expand Down Expand Up @@ -649,11 +655,13 @@ impl Config {
self.parse_name_directive(line, "pretty-compare-only")
}

fn parse_failure_status(&self, line: &str) -> Option<i32> {
match self.parse_name_value_directive(line, "failure-status") {
Some(code) => code.trim().parse::<i32>().ok(),
_ => None,
fn parse_failure_status(&self, line: &str) -> Option<FailureStatus> {
let status_str = self.parse_name_value_directive(line, "failure-status")?;
if let Ok(code) = status_str.trim().parse::<i32>() {
return Some(FailureStatus::ExitCode(code))
}
let signal = self.parse_name_value_directive(&status_str, "signal")?;
Some(FailureStatus::Signal(signal.trim().parse::<i32>().ok()?))
}

fn parse_compile_pass(&self, line: &str) -> bool {
Expand Down
22 changes: 19 additions & 3 deletions src/tools/compiletest/src/runtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<ExitStatus> 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(
Expand Down