diff --git a/Cargo.toml b/Cargo.toml index 08e2799..4f4a4c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "urus" -version = "0.2.1" +version = "0.2.2" edition = "2021" rust-version = "1.95" description = "Cowboy/bandit-style HTTP library for the smarm actor runtime" diff --git a/src/serve.rs b/src/serve.rs index 2b08e2d..e6c0d25 100644 --- a/src/serve.rs +++ b/src/serve.rs @@ -57,8 +57,19 @@ pub struct Config { /// (one per CPU). Set this to a small fixed number in tests so multiple /// concurrent test servers don't oversubscribe the host. pub scheduler_threads: Option, + /// Stack reserve (RFC 019 `smarm::SpawnOpts::stack_reserve`) given to + /// each per-connection actor. Request handlers routinely pull in + /// application code — DB drivers, (de)compression, templating — whose + /// stack needs comfortably exceed smarm's bare-actor default of 64 KiB + /// (the exact shape of bug this exists to head off; see smarm RFC 019). + /// Default: 256 KiB. The reserve is virtual/demand-paged, so raising it + /// costs address space, not RSS, until a handler actually uses it. + pub conn_stack_reserve: usize, } +/// Default per-connection actor stack reserve (see [`Config::conn_stack_reserve`]). +pub const DEFAULT_CONN_STACK_RESERVE: usize = 256 * 1024; + impl Config { pub fn new(addr: SocketAddr) -> Self { let pool = std::thread::available_parallelism() @@ -78,6 +89,7 @@ impl Config { max_frame_payload: 1024 * 1024, max_message_bytes: 4 * 1024 * 1024, scheduler_threads: None, + conn_stack_reserve: DEFAULT_CONN_STACK_RESERVE, } } @@ -125,6 +137,7 @@ fn listener_loop( listener: Arc, pipeline: Pipeline, limits: ConnLimits, + conn_stack_reserve: usize, registry: GenServerRef, shutdown: Arc, ) { @@ -157,7 +170,11 @@ fn listener_loop( let p = pipeline.clone(); let l = limits; let r = registry.clone(); - smarm::spawn(move || run_connection(client, p, l, r)); + let opts = smarm::SpawnOpts { + stack_reserve: Some(conn_stack_reserve), + ..smarm::SpawnOpts::default() + }; + smarm::spawn_with(opts, move || run_connection(client, p, l, r)); } Err(e) if e.kind() == ErrorKind::WouldBlock => { // No pending connection. Park until the listener is @@ -305,8 +322,9 @@ pub fn serve_with_shutdown( listener_fds.push(Arc::new(dup)); } - let limits = config.to_conn_limits(); - let drain_timeout = config.drain_timeout; + let limits = config.to_conn_limits(); + let conn_stack_reserve = config.conn_stack_reserve; + let drain_timeout = config.drain_timeout; let smarm_cfg = match config.scheduler_threads { Some(n) => smarm::Config::exact(n), @@ -327,7 +345,7 @@ pub fn serve_with_shutdown( let sf = shutdown_flag.clone(); sup = sup.child(ChildSpec::new(Restart::Transient, move || { println!("urus: listener {} starting", i); - listener_loop(lfd.clone(), p.clone(), limits, r.clone(), sf.clone()); + listener_loop(lfd.clone(), p.clone(), limits, conn_stack_reserve, r.clone(), sf.clone()); })); } // Default intensity (3 per 5s) applies; a listener crash-looping