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

View File

@@ -0,0 +1,14 @@
[package]
name = "flaky"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "flaky"
test = false
bench = false
[dependencies]
types = { workspace = true }
libkos = { workspace = true }
server-framework = { workspace = true }

View File

@@ -0,0 +1,69 @@
//! A server that dies or hangs when asked: on the test image only, so the
//! self-test can watch init restart it (see init's supervise.rs).
#![no_std]
#![forbid(unsafe_code)]
use libkos::syscall;
use server_framework::{Decode, Encode, MALFORMED, Server};
use types::bounded::CapSlot;
/// Requests (word 0); a reply carries how many requests this instance has
/// served, this one included: a restarted server starts at 1.
pub mod op {
pub const WHO: usize = 1;
/// Panics: the process dies.
pub const CRASH: usize = 2;
/// Blocks for good inside the handler.
pub const HANG: usize = 3;
}
/// Everything goes through `handle_call`.
struct Never;
impl Decode for Never {
fn decode(_: [usize; 4]) -> Option<Self> {
None
}
}
impl Encode for Never {
fn encode(&self) -> [usize; 4] {
[MALFORMED, 0, 0, 0]
}
}
struct Flaky {
served: usize,
}
impl Server for Flaky {
type Request = Never;
type Response = Never;
fn handle(&mut self, request: Never) -> Never {
request
}
fn handle_call(&mut self, _badge: usize, words: [usize; 4], _cap: Option<CapSlot>) -> ([usize; 4], Option<syscall::Give>) {
match words[0] {
op::WHO => {}
op::CRASH => panic!("asked to crash"),
op::HANG => {
// A notification nobody signals.
if let Ok(never) = syscall::create_notification() {
loop {
let _ = syscall::wait(never);
}
}
}
_ => return ([MALFORMED, 0, 0, 0], None),
}
self.served += 1;
([self.served, 0, 0, 0], None)
}
}
pub fn run() -> ! {
server_framework::serve(&mut Flaky { served: 0 }, CapSlot::const_new(0))
}

View File

@@ -0,0 +1,11 @@
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
libkos::entry!(main);
fn main() {
flaky::run();
}

View File

@@ -25,6 +25,8 @@ const PERMISSION: CapSlot = CapSlot::const_new(1);
/// The user's files, their `home`: the shell's `/`.
const HOME: CapSlot = CapSlot::const_new(2);
const SESSION: CapSlot = CapSlot::const_new(3);
/// The terminal again, the copy that takes Ctrl+C; apps get the plain one.
const CONTROL: CapSlot = CapSlot::const_new(4);
/// Bits on the shell's notification: the program died (its death watch is
/// unbadged), or Ctrl+C was typed (the terminal's copy).
@@ -294,7 +296,7 @@ fn main() {
println!("shell: no permission manager");
return;
};
if !libkos::terminal::on_interrupt(TERMINAL, done, types::badge::Badge::new(INTERRUPTED)) {
if !libkos::terminal::on_interrupt(CONTROL, done, types::badge::Badge::new(INTERRUPTED)) {
println!("shell: Ctrl+C will not work");
}
let mut sh = Shell {

View File

@@ -11,4 +11,5 @@ bench = false
[dependencies]
types = { workspace = true }
manifest = { workspace = true }
flaky = { path = "../flaky" }
libkos = { workspace = true }

View File

@@ -17,10 +17,11 @@ use types::budget::Limits;
/// Where init put them (see init's manifest: `uses`, storage (the disk,
/// the user's home), the spawn endpoint, then control).
const PERMISSION: CapSlot = CapSlot::const_new(0);
const STORAGE: CapSlot = CapSlot::const_new(1);
const HOME: CapSlot = CapSlot::const_new(2);
const INIT: CapSlot = CapSlot::const_new(3);
const CONTROL: CapSlot = CapSlot::const_new(4);
const FLAKY: CapSlot = CapSlot::const_new(1);
const STORAGE: CapSlot = CapSlot::const_new(2);
const HOME: CapSlot = CapSlot::const_new(3);
const INIT: CapSlot = CapSlot::const_new(4);
const CONTROL: CapSlot = CapSlot::const_new(5);
/// Where storage's buffers are mapped: the whole disk's, a narrowed one's.
const STORAGE_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0180_0000);
const NARROW_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0190_0000);
@@ -172,6 +173,33 @@ fn encrypted_home(fs: &mut Storage, home_fs: &mut Storage) -> Result<(), &'stati
Ok(())
}
/// How many requests the `flaky` answering now has served.
fn flaky() -> Result<usize, &'static str> {
syscall::request(FLAKY, [flaky::op::WHO, 0, 0, 0]).map(|r| r[0]).map_err(|_| "flaky does not answer")
}
/// A server that dies is started again on the same endpoint: the caller's
/// capability reaches the new one. Twice in a row, the second waits.
fn restarted_after_crashes() -> Result<(), &'static str> {
if flaky()? != 1 || flaky()? != 2 {
return Err("flaky counts wrong");
}
// Dies mid-call: the call comes back with an error.
let _ = syscall::request(FLAKY, [flaky::op::CRASH, 0, 0, 0]);
if flaky()? != 1 {
return Err("the crashed server still answers");
}
let crashed = syscall::get_time().raw();
let _ = syscall::request(FLAKY, [flaky::op::CRASH, 0, 0, 0]);
let fresh = flaky()? == 1;
// QEMU virt's timebase is 10 MHz.
let waited_ms = (syscall::get_time().raw() - crashed) / 10_000;
if !fresh || waited_ms < 900 {
return Err("a second crash in a row was not backed off");
}
Ok(())
}
fn check() -> Result<(), &'static str> {
let mut fs = Storage::attach(STORAGE, STORAGE_BUF_VA).map_err(|_| "cannot attach to storage")?;
write_a_file(&mut fs)?;
@@ -201,6 +229,9 @@ fn check() -> Result<(), &'static str> {
revoke_stops_a_running_app(died)?;
libkos::println!("spawntest: revoking stops a running app, allowing lets it run: ok");
restarted_after_crashes()?;
libkos::println!("spawntest: a crashed server is restarted, a crash loop backs off: ok");
Ok(())
}