Skip to main content

isolated/
isolated.rs

1//! Each tab in a process of its own: an app that can crash, hang or eat
2//! memory on purpose, to show the other tabs and the server carry on.
3//!
4//!     cargo run -p rs-rich-web --example isolated [-- ADDRESS [--memory BYTES] [--cpu SECONDS] [--token TOKEN] [--startup-memory BYTES]]
5//!
6//! Each tab shows the id of the process it runs in. In the page: `c`
7//! panics, `h` loops forever, `m` allocates until the memory limit (256
8//! MiB unless `--memory` says otherwise) stops it, and `+` shows the tab
9//! is still answering. The server tells the page its session ended and
10//! kills the process (a hung one when the tab closes, or when its CPU
11//! time, 10 s unless `--cpu` says, runs out); the other tabs never notice.
12//! `--startup-memory` makes each session's process allocate that much
13//! before it reaches `serve`, which the limits cover too (on Linux).
14
15use std::time::Duration;
16
17use rich_web::intuituive::prelude::*;
18
19fn app() -> App {
20    App::new(|| {
21        let pid = std::process::id();
22        let state = signal("waiting");
23        column([
24            text!("[b]pid {pid}[/] · {state}").fixed(1),
25            label("[muted]c crashes · h hangs · m eats memory · + answers").fixed(1),
26        ])
27        .on_key("+", move |_| state.set("ANSWERED"))
28        .on_key("c", |_| panic!("crashed on purpose"))
29        .on_key("h", |_| {
30            #[allow(clippy::empty_loop)]
31            loop {}
32        })
33        .on_key("m", |_| {
34            let mut held: Vec<Vec<u8>> = Vec::new();
35            loop {
36                held.push(vec![1u8; 16 << 20]);
37            }
38        })
39    })
40}
41
42fn main() -> std::io::Result<()> {
43    let mut args = std::env::args().skip(1);
44    let mut addr = "127.0.0.1:8080".to_string();
45    let mut memory = 256u64 << 20;
46    let mut cpu = 10u64;
47    let mut token = None;
48    let mut startup = None;
49    while let Some(arg) = args.next() {
50        let mut value = || args.next().expect("a value after the option");
51        match arg.as_str() {
52            "--memory" => memory = value().parse().expect("bytes"),
53            "--cpu" => cpu = value().parse().expect("seconds"),
54            "--token" => token = Some(value()),
55            "--startup-memory" => startup = Some(value().parse::<usize>().expect("bytes")),
56            _ => addr = arg,
57        }
58    }
59    // Startup work in each session's process, before it reaches `bind`.
60    if let (Some(bytes), true) = (startup, std::env::var_os(rich_web::SESSION_ENV).is_some()) {
61        std::hint::black_box(vec![1u8; bytes]);
62    }
63    let mut server = rich_web::Server::bind(addr.as_str(), app)?
64        .title("isolated sessions")
65        .session_memory(memory)
66        .session_cpu(Duration::from_secs(cpu));
67    if let Some(token) = token {
68        server = server.token(token);
69    }
70    server.run()
71}