about summary refs log tree commit diff
path: root/src/tools/miri/tests/pass/panic/concurrent-panic.rs
blob: 4bf645960f7051e433267c4fb89a0cf0967425ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// We are making scheduler assumptions here.
//@compile-flags: -Zmiri-deterministic-concurrency

//! Cause a panic in one thread while another thread is unwinding. This checks
//! that separate threads have their own panicking state.

use std::sync::{Arc, Condvar, Mutex};
use std::thread::{JoinHandle, spawn};

struct BlockOnDrop(Option<JoinHandle<()>>);

impl BlockOnDrop {
    fn new(handle: JoinHandle<()>) -> BlockOnDrop {
        BlockOnDrop(Some(handle))
    }
}

impl Drop for BlockOnDrop {
    fn drop(&mut self) {
        eprintln!("Thread 2 blocking on thread 1");
        let _ = self.0.take().unwrap().join();
        eprintln!("Thread 1 has exited");
    }
}

fn main() {
    let t1_started_pair = Arc::new((Mutex::new(false), Condvar::new()));
    let t2_started_pair = Arc::new((Mutex::new(false), Condvar::new()));

    let t1_continue_mutex = Arc::new(Mutex::new(()));
    let t1_continue_guard = t1_continue_mutex.lock();

    let t1 = {
        let t1_started_pair = t1_started_pair.clone();
        let t1_continue_mutex = t1_continue_mutex.clone();
        spawn(move || {
            eprintln!("Thread 1 starting, will block on mutex");
            let (mutex, condvar) = &*t1_started_pair;
            *mutex.lock().unwrap() = true;
            condvar.notify_one();

            drop(t1_continue_mutex.lock());
            panic!("panic in thread 1");
        })
    };

    // Wait for thread 1 to signal it has started.
    let (t1_started_mutex, t1_started_condvar) = &*t1_started_pair;
    let mut t1_started_guard = t1_started_mutex.lock().unwrap();
    while !*t1_started_guard {
        t1_started_guard = t1_started_condvar.wait(t1_started_guard).unwrap();
    }
    eprintln!("Thread 1 reported it has started");
    // Thread 1 should now be blocked waiting on t1_continue_mutex.

    let t2 = {
        let t2_started_pair = t2_started_pair.clone();
        let block_on_drop = BlockOnDrop::new(t1);
        spawn(move || {
            let _capture = block_on_drop;

            let (mutex, condvar) = &*t2_started_pair;
            *mutex.lock().unwrap() = true;
            condvar.notify_one();

            panic!("panic in thread 2");
        })
    };

    // Wait for thread 2 to signal it has started.
    let (t2_started_mutex, t2_started_condvar) = &*t2_started_pair;
    let mut t2_started_guard = t2_started_mutex.lock().unwrap();
    while !*t2_started_guard {
        t2_started_guard = t2_started_condvar.wait(t2_started_guard).unwrap();
    }
    eprintln!("Thread 2 reported it has started");
    // Thread 2 should now have already panicked and be in the middle of
    // unwinding. It should now be blocked on joining thread 1.

    // Unlock t1_continue_mutex, and allow thread 1 to proceed.
    eprintln!("Unlocking mutex");
    drop(t1_continue_guard);
    // Thread 1 will panic the next time it is scheduled. This will test the
    // behavior of interest to this test, whether Miri properly handles
    // concurrent panics in two different threads.

    // Block the main thread on waiting to join thread 2. Thread 2 should
    // already be blocked on joining thread 1, so thread 1 will be scheduled
    // to run next, as it is the only ready thread.
    assert!(t2.join().is_err());
    eprintln!("Thread 2 has exited");
}