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

@@ -247,20 +247,15 @@ impl<C: Console> Server for TerminalServer<C> {
self.console.flush();
[0; 4]
}
types::terminal::ON_INTERRUPT => match (cap, self.on_interrupt) {
(Some(n), None) => {
self.on_interrupt = Some(n);
types::terminal::ON_INTERRUPT => match cap {
Some(n) if badge & types::terminal::CONTROL != 0 => {
if let Some(old) = self.on_interrupt.replace(n) {
self.console.forget(old);
}
[0; 4]
}
_ => [MALFORMED, 0, 0, 0],
},
types::terminal::SIGNED_OUT if badge == 0 => {
if let Some(n) = self.on_interrupt.take() {
self.console.forget(n);
}
[0; 4]
}
types::terminal::SIGNED_OUT => [MALFORMED, 0, 0, 0],
types::terminal::CLEAR => {
self.handle(Request::Clear);
[0; 4]

View File

@@ -105,8 +105,9 @@ fn line_of(t: &mut TerminalServer<Fake>) -> Vec<u8> {
line
}
/// Registered through the shell's control copy.
fn on_interrupt(t: &mut TerminalServer<Fake>, slot: u8) -> usize {
t.handle_call(1, [types::terminal::ON_INTERRUPT, 0, 0, 0], CapSlot::new(slot)).0[0]
t.handle_call(1 | types::terminal::CONTROL, [types::terminal::ON_INTERRUPT, 0, 0, 0], CapSlot::new(slot)).0[0]
}
#[test]
@@ -119,16 +120,6 @@ fn ctrl_c_while_a_program_runs_interrupts_it() {
assert_eq!(&t.grid().row(0)[..2], b"^C");
}
#[test]
fn only_the_first_interrupt_notification_is_taken() {
let mut t = term(&[]);
assert_eq!(on_interrupt(&mut t, 7), 0);
assert_ne!(on_interrupt(&mut t, 8), 0);
t.console_mut_for_test().typed = vec![CTRL_C];
t.signal(KEYS_BIT);
assert_eq!(t.console().interrupted, [CapSlot::new(7).unwrap()]);
}
#[test]
fn keys_typed_ahead_start_the_next_line() {
let mut t = term(b"s\n");
@@ -192,15 +183,16 @@ fn a_secret_line_shows_stars_and_is_forgotten() {
}
#[test]
fn only_init_lets_the_next_session_register_for_ctrl_c() {
fn only_the_control_copy_takes_ctrl_c_and_the_latest_counts() {
use server_framework::MALFORMED;
let mut t = term(&[]);
let plain = |t: &mut TerminalServer<Fake>, slot| t.handle_call(1, [types::terminal::ON_INTERRUPT, 0, 0, 0], CapSlot::new(slot)).0[0];
assert_eq!(plain(&mut t, 6), MALFORMED, "an app, with the plain copy, cannot take it");
assert_eq!(on_interrupt(&mut t, 7), 0);
assert_eq!(on_interrupt(&mut t, 8), MALFORMED, "an app cannot take Ctrl+C over");
let signed_out = |t: &mut TerminalServer<Fake>, badge| t.handle_call(badge, [types::terminal::SIGNED_OUT, 0, 0, 0], None).0[0];
assert_eq!(signed_out(&mut t, 1), MALFORMED, "nor say the user signed out");
assert_eq!(on_interrupt(&mut t, 8), MALFORMED);
assert_eq!(signed_out(&mut t, 0), 0);
assert_eq!(plain(&mut t, 8), MALFORMED, "nor take it over");
assert_eq!(on_interrupt(&mut t, 9), 0, "a restarted shell registers again");
assert_eq!(t.console().forgotten, [CapSlot::new(7).unwrap()]);
assert_eq!(on_interrupt(&mut t, 9), 0, "the next shell may");
t.console_mut_for_test().typed = vec![CTRL_C];
t.signal(KEYS_BIT);
assert_eq!(t.console().interrupted, [CapSlot::new(9).unwrap()]);
}