elkowar/eww · critical
Failed to start outer-main-async-runtime thread
Error message
Failed to start outer-main-async-runtime thread
What it means
std::thread::Builder::spawn failed while starting the 'outer-main-async-runtime' thread that hosts the tokio runtime; the expect turns the io::Error into a panic. Spawn failures are OS resource errors: the process or user hit its thread/process limit or the system ran out of memory.
Solutions
- Check thread/process usage for the user (`ps -o nlwp -p $$`, `ulimit -u`) and raise RLIMIT_NPROC or systemd TasksMax.
- Kill leaked processes/threads belonging to the user, then restart the eww daemon.
- Raise container pids limit (docker `--pids-limit`, k8s pod pids limit).
- Handle the error instead of panicking: log it and shut the daemon down with a diagnostic.
Example fix
// before
std::thread::Builder::new()
.name("outer-main-async-runtime".to_string())
.spawn(move || { /* ... */ })
.expect("Failed to start outer-main-async-runtime thread");
// after
std::thread::Builder::new()
.name("outer-main-async-runtime".to_string())
.spawn(move || { /* ... */ })
.context("Failed to start outer-main-async-runtime thread (check ulimit -u / TasksMax)")?; Defensive patterns
Strategy: try-catch
Validate before calling
// check spawn headroom before starting
let n = std::fs::read_dir("/proc/self/task").map(|d| d.count()).unwrap_or(0);
if n > 500 { log::warn!("high thread count: {}", n); } Try / catch
match std::thread::Builder::new().name("outer-main-async-runtime".into()).spawn(work) {
Ok(h) => h,
Err(e) => { log::error!("thread spawn failed: {}", e); return; }
} Prevention
- Keep RLIMIT_NPROC well above expected thread usage
- Kill leaked eww daemons (eww kill) instead of stacking instances
- Set TasksMax=infinity on user services that spawn threads
- Watch for ENOMEM/EAGAIN under memory pressure
When it happens
Trigger: init_async_part spawning the outer thread when RLIMIT_NPROC is exhausted, the pids cgroup limit is reached, or pthread_create fails with EAGAIN/ENOMEM.
Common situations: Long-running desktop sessions that leaked threads, minimal containers (small pids.max), shared accounts over their process quota, or systems under severe memory pressure.
Related errors
- Failed to initialize tokio runtime
- Failed to start command-execution-thread
- Failed to initialize tokio runtime
AI-assisted analysis of elkowar/eww@48f5aa8b37 (2026-09-08).
Data as JSON: /api/errors/091f4491f6c81ff4.
Report an issue: GitHub.
Appendix: source
Thrown at crates/eww/src/server.rs:205
let forward_exit_to_app_handle = {
let ui_send = ui_send.clone();
tokio::spawn(async move {
// Wait for application exit event
let _ = crate::application_lifecycle::recv_exit().await;
log::debug!("Forward task received exit event");
// Then forward that to the application
let _ = ui_send.send(app::DaemonCommand::KillServer);
})
};
let result = tokio::try_join!(filewatch_join_handle, ipc_server_join_handle, forward_exit_to_app_handle);
if let Err(e) = result {
log::error!("Eww exiting with error: {:?}", e);
}
})
})
.expect("Failed to start outer-main-async-runtime thread");
handle
}
/// Watch configuration files for changes, sending reload events to the eww app when the files change.
async fn run_filewatch<P: AsRef<Path>>(config_dir: P, evt_send: UnboundedSender<app::DaemonCommand>) -> Result<()> {
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let mut watcher: RecommendedWatcher = notify::recommended_watcher(move |res: notify::Result<notify::Event>| match res {
Ok(notify::Event { kind: notify::EventKind::Modify(_), paths, .. }) => {
let relevant_files_changed = paths.iter().any(|path| {
let ext = path.extension().unwrap_or_default();
ext == "yuck" || ext == "scss" || ext == "css"
});
if relevant_files_changed {
if let Err(err) = tx.send(()) {
log::warn!("Error forwarding file update event: {:?}", err);View on GitHub (pinned to 48f5aa8b37)