//! 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) }