GraphiteEditor/Graphite · error
failed to construct async-message tokio runtime
Error message
failed to construct async-message tokio runtime
What it means
Panic when tokio::runtime::Builder::new_multi_thread().worker_threads(1).enable_all().build() returns an Err inside TokioSpawner, the editor's async-message executor. The multi-thread runtime build fails when the OS cannot create the worker thread (thread limits, memory), or — more commonly on Linux — when enable_all's I/O driver cannot be set up because epoll_create1/timerfd_create fail (fd exhaustion or a seccomp sandbox blocking those syscalls).
Source
Thrown at editor/src/messages/future/future_message_handler.rs:175
}
#[cfg(not(target_family = "wasm"))]
impl Default for TokioSpawner {
fn default() -> Self {
Self { runtime: std::sync::OnceLock::new() }
}
}
#[cfg(not(target_family = "wasm"))]
impl TokioSpawner {
fn runtime(&self) -> &tokio::runtime::Runtime {
self.runtime.get_or_init(|| {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.thread_name("graphite-async")
.enable_all()
.build()
.expect("failed to construct async-message tokio runtime")
})
}
}
#[cfg(not(target_family = "wasm"))]
impl MessageSpawner for TokioSpawner {
fn spawn(&self, future: InnerMessageFuture, results: UnboundedSender<Message>, wake: Wake) {
self.runtime().spawn(async move {
let message = future.await;
let _ = results.unbounded_send(message);
wake();
});
}
}
#[cfg(target_family = "wasm")]
struct WasmSpawner;
View on GitHub (pinned to c507b35645)
Solutions
- Raise fd and thread limits for the host process (ulimit -n, ulimit -u, container limits)
- If sandboxed, allow epoll_create1, epoll_ctl, epoll_wait and timerfd_create syscalls in the security profile
- Check /proc/<pid>/fd counts and thread counts to identify exhaustion
- Reduce the number of concurrently running runtimes if the host creates many TokioSpawners
Example fix
// before
.build()
.expect("failed to construct async-message tokio runtime")
// after
.build()
.unwrap_or_else(|e| panic!("failed to construct async-message tokio runtime: {e}")) Defensive patterns
Strategy: try-catch
Try / catch
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.thread_name("graphite-async")
.enable_all()
.build();
let runtime = match runtime {
Ok(rt) => rt,
Err(e) => panic!("failed to construct async-message tokio runtime: {e} (check thread/fd limits and sandbox syscalls)"),
}; Prevention
- Ensure host processes embedding the editor have adequate thread and fd limits
- In sandboxed runtimes, permit epoll_create1/epoll_ctl/epoll_wait and timerfd_create syscalls
- Construct the spawner's runtime lazily and once, so repeated message spawns cannot stack runtimes
When it happens
Trigger: Constructing the editor's async spawner in a process at its fd or thread limit; running under a strict seccomp/container profile that blocks epoll or timerfd syscalls; severe memory pressure preventing the worker thread's stack allocation.
Common situations: Embedding the editor crate inside sandboxed executors (gVisor, firecracker, restricted gVisor-like runtimes) that block the I/O driver syscalls; apps that exhaust file descriptors before spawning async work; test harnesses with low rlimits.
Related errors
- Failed to spawn socket thread
- Failed to spawn the CEF control thread
- Failed to create control channel
- Buffer mapping communication failed
- InvalidData
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/4fd731927a10aba4.
Report an issue: GitHub.