{"record":{"id":"6f4f7ac5c06dac54","repo":"zellij-org/zellij","slug":"failed-to-build-forward-timeout-runtime","errorCode":null,"errorMessage":"failed to build forward-timeout runtime","messagePattern":"failed to build forward-timeout runtime","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"zellij-client/src/stdin_ansi_parser.rs","lineNumber":903,"sourceCode":"// Forward-slot timeout infrastructure\n// =====================================================================\n\nuse std::sync::{Arc, Mutex, OnceLock};\n\n/// Dedicated, lazily-initialised runtime for driving forward-slot\n/// timeouts. A single current-thread executor runs on its own OS\n/// thread; timer tasks are `spawn`-ed onto it from the synchronous\n/// `ClientInstruction::ForwardQueryToHost` handler. One-thread model\n/// because timer tasks do no CPU work — they just sleep and perform a\n/// millisecond-scale mutex check on wake-up.\nstatic 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","sourceCodeStart":885,"sourceCodeEnd":921,"githubUrl":"https://github.com/zellij-org/zellij/blob/98a0837077492d53dd252ab30bc3e43e41e504f4/zellij-client/src/stdin_ansi_parser.rs#L885-L921","documentation":"Lazily builds the client's global forward-timeout executor: a one-thread tokio runtime with only the time driver enabled, held in a OnceLock and driven by a dedicated thread parked on block_on(pending()). Runtime construction is expected to succeed; it realistically fails only when the time driver cannot allocate its resources (fd exhaustion, EMFILE) or memory allocation fails. There is no fallback executor, so the first failing build panics the thread that scheduled the timeout.","triggerScenarios":"First call to forward_timeout_runtime() - scheduling a forward/query timeout - when the process is out of fds (RLIMIT_NOFILE) or under severe memory pressure.","commonSituations":"Long-lived clients on hosts with low `ulimit -n`; containers with tiny fd ceilings; fd leaks elsewhere in the process pushing it over the limit.","solutions":["Raise the fd limit before launching (ulimit -n 4096, or LimitNOFILE in systemd) and retry","Count open fds (`ls /proc/$(pidof zellij)/fd | wc -l`) to find leaks","Restart the client; the runtime is built once per process","If memory pressure is the cause, free memory or raise limits and retry"],"exampleFix":"// before\nlet rt = tokio::runtime::Builder::new_current_thread()\n    .enable_time()\n    .build()\n    .expect(\"failed to build forward-timeout runtime\");\n\n// after - fail with context instead of a bare panic\nlet rt = tokio::runtime::Builder::new_current_thread()\n    .enable_time()\n    .build()\n    .context(\"forward-timeout runtime: fd or memory exhaustion?\")\n    .fatal();","handlingStrategy":"fallback","validationCode":"fn fd_headroom(min_free: usize) -> bool {\n    match std::fs::read_dir(\"/proc/self/fd\") {\n        Ok(entries) => entries.count() + min_free < 1024, // compare against your RLIMIT_NOFILE\n        Err(_) => true, // cannot tell; assume ok\n    }\n}","typeGuard":null,"tryCatchPattern":"match tokio::runtime::Builder::new_current_thread().enable_time().build() {\n    Ok(rt) => rt,\n    Err(e) => {\n        log::error!(\"tokio time runtime failed ({e}); falling back to std thread timer\");\n        // fallback: std::thread + mpsc with recv_timeout implements the same one-shot timeout\n        fallback_std_timer()\n    }\n}","preventionTips":["Set generous RLIMIT_NOFILE for terminal clients","Audit plugins/wasm for fd leaks before scheduling many timers","Build the runtime eagerly at startup where failure surfaces with a clear message","Keep a std::thread timer fallback path for resource-constrained hosts"],"tags":["rust","zellij","tokio","runtime","fd-exhaustion","resources"],"backgroundTag":null,"analyzedSha":"98a0837077492d53dd252ab30bc3e43e41e504f4","analyzedAt":"2026-08-16T13:02:01.396Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}