{"record":{"id":"f917b99c65a15c11","repo":"ramensoftware/windhawk","slug":"spawn-the-event-pump-thread","errorCode":null,"errorMessage":"spawn the event pump thread","messagePattern":"spawn the event pump thread","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/windhawk-core/ui/src/lib.rs","lineNumber":729,"sourceCode":"            // from managed state; the banner commands reach the link the same way.\n            app.manage(ctx.clone());\n            app.manage(link.clone());\n\n            // Now that there is a window, the link can report what it is doing.\n            link.attach(app.handle().clone());\n\n            // The pump thread owns the message channel: it routes each operation\n            // event to its op through the bridge (off the core callback thread, so\n            // a composite follow-up may re-enter the session), and it runs the\n            // session swaps, which need the same seams and the same thread.\n            let pump_ctx = ctx.clone();\n            // Nothing reaches the front-end without the pump, and setup has no error\n            // path that leaves a usable window, so a refused thread is a panic.\n            #[allow(clippy::expect_used)]\n            std::thread::Builder::new()\n                .name(\"wh-event-pump\".to_owned())\n                .spawn(move || pump::run(pump_ctx, pump_messages))\n                .expect(\"spawn the event pump thread\");\n\n            // Everything the UI starts for ITSELF waits for the session to settle -\n            // the swap to the broker's session, or degraded mode, whichever comes\n            // first. The startup catalog refresh is why: its terminal writes the\n            // user profile, so issued against the local session in the window\n            // before the broker arrives it would either fail unelevated or be\n            // drained by the swap, on every single launch. Deferring it also leaves\n            // the swap-point drain empty in the normal case, which is what keeps\n            // that path a rare-path concern rather than a per-launch one.\n            //\n            // Off the setup thread, as the seed and the sweep already were: none of\n            // it may delay the window.\n            let background_ctx = ctx.clone();\n            let background_link = link.clone();\n            // As with the pump: setup has nowhere to report a refused thread.\n            #[allow(clippy::expect_used)]\n            std::thread::Builder::new()\n                .name(\"wh-ui-background\".to_owned())","sourceCodeStart":711,"sourceCodeEnd":747,"githubUrl":"https://github.com/ramensoftware/windhawk/blob/61d99ed8e182e1af1b60109612b6763ad1b4b74e/src/windhawk-core/ui/src/lib.rs#L711-L747","documentation":"This is a deliberate panic via .expect() when std::thread::Builder::spawn fails to create the \"wh-event-pump\" thread in the Tauri UI's run(). The library treats a refused thread spawn as fatal because nothing reaches the front-end without the event pump and setup has no error path that leaves a usable window, so unwinding with a usable app is impossible.","triggerScenarios":"OS-level refusal of std::thread::Builder::new().name(\"wh-event-pump\").spawn(...) during windhawk-core UI startup: EAGAIN from pthread_create due to thread/process limit (RLIMIT_NPROC, cgroup pids.max) exhaustion, out of memory for the new stack, or spawning after the runtime is being torn down.","commonSituations":"Running the UI inside a container or systemd service with a low TasksMax/pids limit; hitting ulimit -u under heavy load; extremely low-memory machines where the thread stack cannot be allocated.","solutions":["Raise the thread/process limit: increase ulimit -u (RLIMIT_NPROC), systemd TasksMax, or cgroup pids.max for the user running the UI.","Free memory or increase available memory/swap so the thread stack allocation succeeds.","Check for runaway thread leaks in the process (count threads via /proc/<pid>/status Threads) and fix the leak before startup.","If the crash is expected to be graceful, replace expect with propagation of an error instead of a panic (upstream change)."],"exampleFix":"// shell, before\nulimit -u 256\n// after\nulimit -u 4096\n# or for a systemd unit:\n# TasksMax=infinity","handlingStrategy":"fallback","validationCode":"fn can_spawn_thread() -> bool {\n    std::thread::Builder::new().name(\"probe\").spawn(|| {}).map(|h| h.join().is_ok()).unwrap_or(false)\n}\nif !can_spawn_thread() { eprintln!(\"thread quota exhausted; aborting startup\"); }","typeGuard":null,"tryCatchPattern":"// spawn returns Result, so guard before expect:\nlet handle = std::thread::Builder::new()\n    .name(\"wh-event-pump\".to_owned())\n    .spawn(move || pump::run(pump_ctx, pump_messages));\nmatch handle {\n    Ok(h) => h,\n    Err(e) => { log::error!(\"event pump spawn failed: {e}\"); std::process::exit(1); }\n}","preventionTips":["Run the UI with an adequate RLIMIT_NPROC / systemd TasksMax / cgroup pids.max.","Monitor thread counts of the process in production to catch leaks before startup quota is exhausted.","Keep sufficient free memory/swap for thread stack allocation."],"tags":["rust","tauri","thread-spawn","panic","resource-exhaustion"],"backgroundTag":"thread-spawn-failed","analyzedSha":"61d99ed8e182e1af1b60109612b6763ad1b4b74e","analyzedAt":"2026-09-12T14:02:41.115Z","contentChangedAt":"2026-09-12T14:02:41.115Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}