elkowar/eww · error

Failed to initialize tokio runtime

Error message

Failed to initialize tokio runtime

What it means

run_async_task builds a current_thread tokio runtime on the fly and blocks on the given future; the expect panics if runtime creation fails. This means the OS refused to create the runtime's backing resources (blocking pool threads, timers), typically a thread/PID or memory limit.

Solutions

  1. Raise thread/PID limits (ulimit -u, TasksMax, container pids limit).
  2. Reuse a single shared runtime Handle instead of building a runtime per call — fewer resources and no repeated init failures.
  3. Retry the task once after a brief sleep if the failure was transient resource pressure.
  4. Return a Result from run_async_task so the caller (systray icon code) can degrade gracefully instead of panicking.

Example fix

// before
fn run_async_task<F: Future>(f: F) -> F::Output {
    let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().expect("Failed to initialize tokio runtime");
    rt.block_on(f)
}
// after
fn run_async_task<F: Future>(f: F) -> Option<F::Output> {
    let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().ok()?;
    Some(rt.block_on(f))
}
Defensive patterns

Strategy: fallback

Validate before calling

// reuse a lazily-initialized global runtime instead of building per call
static RT: OnceLock<tokio::runtime::Runtime> = OnceLock::new();

Try / catch

match tokio::runtime::Builder::new_current_thread().enable_all().build() {
    Ok(rt) => rt.block_on(f),
    Err(e) => { log::error!("runtime init failed: {}", e); unreachable_fallback() }
}

Prevention

When it happens

Trigger: run_async_task invoked (e.g. for item_is_menu DBus calls in the systray) when RLIMIT_NPROC is exhausted, the pids cgroup limit is hit, or memory allocation for the runtime fails. Creating a fresh runtime per call also amplifies the chance of transient resource pressure.

Common situations: Systems under heavy thread churn, systray code invoked many times in quick succession on a resource-starved machine, sandboxed environments with strict limits.

Related errors


AI-assisted analysis of elkowar/eww@48f5aa8b37 (2026-09-08). Data as JSON: /api/errors/6024c026a336f984. Report an issue: GitHub.

Appendix: source

Thrown at crates/eww/src/widgets/systray.rs:33

async fn dbus_session() -> zbus::Result<&'static DBusSession> {
    // TODO make DBusSession reference counted so it's dropped when not in use?

    static DBUS_STATE: tokio::sync::OnceCell<DBusSession> = tokio::sync::OnceCell::const_new();
    DBUS_STATE
        .get_or_try_init(|| async {
            let con = zbus::Connection::session().await?;
            notifier_host::Watcher::new().attach_to(&con).await?;

            let (_, snw) = notifier_host::register_as_host(&con).await?;

            Ok(DBusSession { snw })
        })
        .await
}

fn run_async_task<F: Future>(f: F) -> F::Output {
    let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().expect("Failed to initialize tokio runtime");
    rt.block_on(f)
}

pub struct Props {
    icon_size_tx: tokio::sync::watch::Sender<i32>,
    pub prepend_new: Rc<RefCell<bool>>,
}

impl Props {
    pub fn new() -> Self {
        let (icon_size_tx, _) = tokio::sync::watch::channel(24);
        Self { icon_size_tx, prepend_new: Rc::new(RefCell::new(false)) }
    }

    pub fn icon_size(&self, value: i32) {
        let _ = self.icon_size_tx.send_if_modified(|x| {
            if *x == value {
                false

View on GitHub (pinned to 48f5aa8b37)