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
- Raise thread/PID limits (ulimit -u, TasksMax, container pids limit).
- Reuse a single shared runtime Handle instead of building a runtime per call — fewer resources and no repeated init failures.
- Retry the task once after a brief sleep if the failure was transient resource pressure.
- 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
- Build one shared runtime (OnceLock/lazy_static) rather than per-call runtimes
- Keep system thread limits generous
- Avoid invoking the systray path in extremely constrained sandboxes
- Return Option/Result so callers can degrade gracefully
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
- Failed to initialize tokio runtime
- Failed to start outer-main-async-runtime thread
- Failed to start command-execution-thread
- Failed to obtain toplevel window
- generated well-known name is invalid
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 {
falseView on GitHub (pinned to 48f5aa8b37)