{"record":{"id":"bb9acc460c38b14c","repo":"zellij-org/zellij","slug":"failed-to-spawn-forward-timeout-driver-thread","errorCode":null,"errorMessage":"failed to spawn forward-timeout driver thread","messagePattern":"failed to spawn forward-timeout driver thread","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"zellij-client/src/stdin_ansi_parser.rs","lineNumber":914,"sourceCode":"static FORWARD_TIMEOUT_RUNTIME: OnceLock<Arc<tokio::runtime::Runtime>> = OnceLock::new();\n\npub fn forward_timeout_runtime() -> &'static Arc<tokio::runtime::Runtime> {\n    FORWARD_TIMEOUT_RUNTIME.get_or_init(|| {\n        let rt = tokio::runtime::Builder::new_current_thread()\n            .enable_time()\n            .build()\n            .expect(\"failed to build forward-timeout runtime\");\n        let rt = Arc::new(rt);\n        let rt_for_driver = rt.clone();\n        // `block_on(pending())` keeps the executor loop alive forever\n        // on this thread; spawned timer tasks are polled as they\n        // become ready (on spawn, on wake from the time driver).\n        std::thread::Builder::new()\n            .name(\"zellij-client-forward-timeout\".into())\n            .spawn(move || {\n                rt_for_driver.block_on(std::future::pending::<()>());\n            })\n            .expect(\"failed to spawn forward-timeout driver thread\");\n        rt\n    })\n}\n\n/// Spawn a timer task that closes a forward slot after `deadline` and\n/// invokes `on_timeout(token, reply_bytes)` with whatever the slot\n/// accumulated. Token-guard idempotent: if the barrier (or a\n/// replacement forward) has already cleared the slot by the time the\n/// timer wakes, `close_forward_on_timeout(token)` returns `None` and\n/// `on_timeout` is never called — no explicit cancellation path\n/// required.\n///\n/// Extracted as a free function so tests can drive it against a\n/// `tokio::time::pause()`-backed paused runtime without instantiating\n/// the full client.\npub fn schedule_forward_timeout<F>(\n    runtime: &tokio::runtime::Handle,\n    parser: Arc<Mutex<StdinAnsiParser>>,","sourceCodeStart":896,"sourceCodeEnd":932,"githubUrl":"https://github.com/zellij-org/zellij/blob/98a0837077492d53dd252ab30bc3e43e41e504f4/zellij-client/src/stdin_ansi_parser.rs#L896-L932","documentation":"Spawns the driver thread that keeps the forward-timeout runtime alive by parking on block_on(pending()). std::thread::Builder::spawn returns io::Error when the OS refuses a new thread - RLIMIT_NPROC or cgroup pids.max reached, or insufficient memory for the thread stack - and this expect turns that into an immediate panic at first runtime use, killing the client thread.","triggerScenarios":"The get_or_init of FORWARD_TIMEOUT_RUNTIME running on a host at its thread or memory limit at the moment the first forward timeout is scheduled.","commonSituations":"Machines with aggressive cgroup pids limits (containers, CI runners); thread leaks in the client; fork-bombed or heavily loaded hosts.","solutions":["Check `ulimit -u` and the cgroup pids.max; raise them for the zellij client","Reduce threads already held by the process (close stale sessions/clients)","Free memory or lower per-thread stack size and restart the client","Restart the process group if leaked processes consume the limit"],"exampleFix":"// before\nstd::thread::Builder::new()\n    .name(\"zellij-client-forward-timeout\".into())\n    .spawn(move || { /* ... */ })\n    .expect(\"failed to spawn forward-timeout driver thread\");\n\n// after - surface a contextual error instead of a bare panic\nlet handle = std::thread::Builder::new()\n    .name(\"zellij-client-forward-timeout\".into())\n    .spawn(move || { /* ... */ })\n    .context(\"could not spawn forward-timeout driver thread (thread/memory limit?)\")\n    .fatal();","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"let mut handle = None;\nfor attempt in 0..3 {\n    match std::thread::Builder::new().name(\"timer-driver\".into()).spawn(driver_fn()) {\n        Ok(h) => {\n            handle = Some(h);\n            break;\n        }\n        Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {\n            std::thread::sleep(std::time::Duration::from_millis(200 * (attempt as u64 + 1)));\n        }\n        Err(e) => {\n            log::error!(\"driver thread spawn failed hard: {e}\");\n            break;\n        }\n    }\n}","preventionTips":["Monitor thread counts of long-lived terminal clients","Set cgroup pids.max above peak thread usage","Spawn the driver thread eagerly at startup so failures surface early with context","Use stack_size() on Builder to reduce memory per thread"],"tags":["rust","zellij","threads","resource-limits","tokio"],"backgroundTag":null,"analyzedSha":"98a0837077492d53dd252ab30bc3e43e41e504f4","analyzedAt":"2026-08-16T13:02:01.396Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}