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

  1. Check thread/process usage for the user (`ps -o nlwp -p $$`, `ulimit -u`) and raise RLIMIT_NPROC or systemd TasksMax.
  2. Kill leaked processes/threads belonging to the user, then restart the eww daemon.
  3. Raise container pids limit (docker `--pids-limit`, k8s pod pids limit).
  4. 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

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


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)