RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / tests / threads_stress.rs
2765 bytes ยท raw
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
//! Stage 19: run the `threads` test program 40 times in a row.
//!
//! It used to hang about once in 40 runs (the "threads_and_futexes flake").
//! Three kernel bugs were behind it, all races between CPUs, so they only
//! showed up now and then:
//!
//! 1. clone() armed CLONE_CHILD_CLEARTID *after* the new thread was already
//!    running. A thread that finished quickly exited before that, nobody
//!    woke its joiner, and `join` waited forever.
//! 2. A futex call spun on the address-space lock with interrupts off,
//!    while the thread holding that lock (munmap of a finished thread's
//!    stack) waited for *this* CPU to answer a TLB-shootdown interrupt.
//!    Each waited for the other: a deadlock.
//! 3. futex_wait read the futex word before taking the waiter-list lock,
//!    so a wake in between could be missed.
//!
//! A watchdog thread fails the test (with a dump of every thread and
//! futex waiter) if no run finishes for 8 seconds, instead of letting the
//! whole test hang until its timeout.
#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(rustbelt::test_runner)]
#![reexport_test_harness_main = "test_main"]

use bootloader::{BootInfo, entry_point};
use core::panic::PanicInfo;
use core::sync::atomic::{AtomicU64, Ordering};
use rustbelt::process::{self, ExitStatus, SpawnArgs};
use rustbelt::{fs, futex, serial_println, thread};

entry_point!(main);

/// How many runs have finished.
static PROGRESS: AtomicU64 = AtomicU64::new(0);
const RUNS: u64 = 40;

fn main(boot_info: &'static BootInfo) -> ! {
    rustbelt::init();
    rustbelt::init_memory(boot_info);
    rustbelt::init_smp();
    fs::init();
    rustbelt::init_storage();
    rustbelt::init_net();
    test_main();
    rustbelt::hlt_loop();
}

fn watchdog() {
    let mut last = 0;
    loop {
        thread::sleep_ticks(18 * 8); // ~8 s (one tick is ~55 ms)
        let now = PROGRESS.load(Ordering::SeqCst);
        if now == RUNS {
            return;
        }
        if now == last {
            serial_println!("\n[watchdog] stuck in run {}\n{}{}", now + 1, thread::debug_dump(), futex::debug_dump());
            rustbelt::exit_qemu(rustbelt::QemuExitCode::Failed);
        }
        last = now;
    }
}

#[test_case]
fn threads_forty_times() {
    thread::spawn("watchdog", watchdog);
    for i in 0..RUNS {
        let p = process::spawn_path("/mnt/disk0/bin/threads", SpawnArgs::kernel("threads")).expect("spawn threads");
        let status = process::wait(&p);
        assert_eq!(status, ExitStatus::Exited(0), "run {} failed", i + 1);
        PROGRESS.store(i + 1, Ordering::SeqCst);
    }
    serial_println!("all {} runs ok", RUNS);
}

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    rustbelt::test_panic_handler(info)
}