{"record":{"id":"a011fa65ef779ed3","repo":"facebook/flow","slug":"failed-to-spawn-wait-for-anything-thread","errorCode":null,"errorMessage":"failed to spawn wait_for_anything thread","messagePattern":"failed to spawn wait_for_anything thread","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"rust_port/crates/flow_server_env/src/server_monitor_listener_state.rs","lineNumber":769,"sourceCode":"    _process_updates: &dyn Fn(bool, &BTreeSet<String>) -> Updates,\n    _get_forced: &dyn Fn() -> CheckedSet,\n) {\n    wait_for_anything_blocking()\n}\n\n/// Tokio wrapper for `wait_for_anything`.\npub async fn wait_for_anything_async(\n    _process_updates: &dyn Fn(bool, &BTreeSet<String>) -> Updates,\n    _get_forced: &dyn Fn() -> CheckedSet,\n) {\n    let (sender, receiver) = tokio::sync::oneshot::channel();\n    let waiter = std::thread::Builder::new()\n        .name(\"wait_for_anything\".to_string())\n        .spawn(move || {\n            wait_for_anything_blocking();\n            let _ = sender.send(());\n        })\n        .expect(\"failed to spawn wait_for_anything thread\");\n    match receiver.await {\n        Ok(()) => {}\n        Err(err) => panic!(\"wait_for_anything task failed: {}\", err),\n    }\n    if let Err(err) = waiter.join() {\n        std::panic::resume_unwind(err);\n    }\n}\n","sourceCodeStart":751,"sourceCodeEnd":778,"githubUrl":"https://github.com/facebook/flow/blob/f88ac94bcf6992f5d5a158854d94613ebb92c6e6/rust_port/crates/flow_server_env/src/server_monitor_listener_state.rs#L751-L778","documentation":"wait_for_anything_async bridges a blocking wait into async by spawning a wait_for_anything OS thread and awaiting a oneshot; the spawn is expected to succeed (rust_port/crates/flow_server_env/src/server_monitor_listener_state.rs:761-769). If the OS refuses the thread, the expect panics inside async code and takes down the task and usually the server. The sibling panic 'wait_for_anything task failed' covers the channel-closed case instead.","triggerScenarios":"A monitor/file-watcher wait cycle starting when the process cannot create one more thread: pids.max or RLIMIT_NPROC exhaustion, or stack mapping failure under memory pressure.","commonSituations":"Long-lived language servers in constrained containers; many concurrent editor connections each adding threads; memory pressure after loading large projects.","solutions":["Raise thread and memory limits (ulimit -u, cgroup pids.max, memory.max) and restart the server","Reduce concurrent connections and workers to leave headroom for helper threads","Change the function to return Result and have callers retry the wait later instead of crashing","Track /proc/<pid>/status Threads over time to catch budget exhaustion before it hits this spawn"],"exampleFix":"// before\n.spawn(move || { wait_for_anything_blocking(); let _ = sender.send(()); })\n.expect(\"failed to spawn wait_for_anything thread\");\n\n// after\nlet waiter = std::thread::Builder::new()\n    .name(\"wait_for_anything\".to_string())\n    .spawn(move || { wait_for_anything_blocking(); let _ = sender.send(()); });\nif waiter.is_err() {\n    return Err(WaitError::NoThreads); // caller retries later\n}","handlingStrategy":"validation","validationCode":"if current_thread_count() >= thread_soft_limit() {\n    // defer the wait instead of panicking inside async code\n    tokio::time::sleep(Duration::from_secs(1)).await;\n    return;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep thread headroom for bridging threads in async wrappers","Raise limits in containers hosting long-lived servers","Consider rewriting blocking waits as native async (inotify/tokio) to avoid the bridge thread entirely"],"tags":["thread-spawn","async","file-watcher","resource-limits","panic"],"backgroundTag":"thread-spawn-failed","analyzedSha":"f88ac94bcf6992f5d5a158854d94613ebb92c6e6","analyzedAt":"2026-08-20T10:41:37.992Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}