feat(init): supervise servers+session progs: restart on death, same endpoint, backoff 0/1/5/30s then give up, restarts_clients cascade; release grants/spawn at stop (stale-slot safe); storage death fatal; init output via printer thread (never blocks on log); terminal Ctrl+C via CONTROL-badged copy, latest wins; flaky test server + spawntest restart/backoff check

This commit is contained in:
2026-10-28 01:44:24 +01:00
parent c4871320a0
commit 28f1ace00a
23 changed files with 736 additions and 144 deletions

102
crates/init/src/output.rs Normal file
View File

@@ -0,0 +1,102 @@
//! What init prints never waits on the log server: init restarts the log
//! server, so a blocking call to it could wait on itself. Text goes into a
//! ring; a thread of init's own takes it to the log and waits there when
//! the log is down, instead of init. Until that thread runs (early boot),
//! init prints to the console directly.
use core::fmt;
use core::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
use libkos::syscall;
use types::bounded::CapSlot;
const SIZE: usize = 4096;
/// Bytes waiting: written at `TAIL` by init's thread, read at `HEAD` by
/// the printer.
static RING: [AtomicU8; SIZE] = [const { AtomicU8::new(0) }; SIZE];
static HEAD: AtomicUsize = AtomicUsize::new(0);
static TAIL: AtomicUsize = AtomicUsize::new(0);
/// Bytes dropped because the ring was full: the log was down a long time.
static LOST: AtomicUsize = AtomicUsize::new(0);
/// The printer's notification, once it runs.
static WAKE: AtomicUsize = AtomicUsize::new(usize::MAX);
fn wake() -> Option<CapSlot> {
u8::try_from(WAKE.load(Ordering::Acquire)).ok().and_then(CapSlot::new)
}
pub(crate) fn write(bytes: &[u8]) {
let Some(wake) = wake() else {
libkos::io::write_bytes(bytes);
return;
};
let tail = TAIL.load(Ordering::Relaxed);
let room = SIZE - (tail - HEAD.load(Ordering::Acquire));
let n = bytes.len().min(room);
for (i, b) in bytes[..n].iter().enumerate() {
RING[(tail + i) % SIZE].store(*b, Ordering::Relaxed);
}
TAIL.store(tail + n, Ordering::Release);
LOST.fetch_add(bytes.len() - n, Ordering::Relaxed);
let _ = syscall::signal(wake);
}
struct Ring;
impl fmt::Write for Ring {
fn write_str(&mut self, s: &str) -> fmt::Result {
write(s.as_bytes());
Ok(())
}
}
pub(crate) fn print(args: fmt::Arguments) {
let _ = fmt::Write::write_fmt(&mut Ring, args);
}
/// Starts the printer; from here on init's output goes through the ring.
pub(crate) fn start() -> bool {
let Ok(wake) = syscall::create_notification() else { return false };
WAKE.store(wake.value() as usize, Ordering::Release);
if libkos::thread::spawn(&WAKE, printer) {
return true;
}
WAKE.store(usize::MAX, Ordering::Release);
false
}
fn printer(_: &'static AtomicUsize) -> ! {
let mut chunk = [0u8; 256];
loop {
if let Some(wake) = wake() {
let _ = syscall::wait(wake);
}
loop {
let (head, tail) = (HEAD.load(Ordering::Relaxed), TAIL.load(Ordering::Acquire));
if head == tail {
break;
}
let n = (tail - head).min(chunk.len());
for (i, b) in chunk[..n].iter_mut().enumerate() {
*b = RING[(head + i) % SIZE].load(Ordering::Relaxed);
}
HEAD.store(head + n, Ordering::Release);
// Blocks while the log server is down; init goes on.
libkos::io::write_bytes(&chunk[..n]);
}
let lost = LOST.swap(0, Ordering::Relaxed);
if lost > 0 {
libkos::println!("init: {} bytes of output lost while the log was down", lost);
}
}
}
#[macro_export]
macro_rules! out {
($($arg:tt)*) => ($crate::output::print(format_args!($($arg)*)));
}
#[macro_export]
macro_rules! outln {
() => ($crate::out!("\n"));
($($arg:tt)*) => ($crate::out!("{}\n", format_args!($($arg)*)));
}