leptos-rs/leptos · error
failed to spawn local future
Error message
failed to spawn local future
What it means
Panics when `LocalSpawner::spawn_local(fut)` on the thread-local `LocalPool` returns Err. The futures LocalPool spawn fails when the spawner's pool has been dropped or is no longer able to accept tasks (e.g. the pool handle's inner state was consumed/closed). any_spawner stores the spawner in a thread_local and `.expect()`s on the result, so any failure panics at the spawn site.
Source
Thrown at any_spawner/src/lib.rs:281
fn get_thread_pool() -> &'static ThreadPool {
THREAD_POOL.get_or_init(|| {
ThreadPool::new()
.expect("could not create futures executor ThreadPool")
})
}
let executor_impl = ExecutorFns {
spawn: |fut| {
get_thread_pool()
.spawn(fut)
.expect("failed to spawn future on ThreadPool");
},
spawn_local: |fut| {
// Use the thread_local SPAWNER derived from LOCAL_POOL
SPAWNER.with(|spawner| {
spawner
.spawn_local(fut)
.expect("failed to spawn local future");
});
},
poll_local: || {
// Use the thread_local LOCAL_POOL
LOCAL_POOL.with(|pool| {
// Use try_borrow_mut to prevent panic during re-entrant calls
if let Ok(mut pool) = pool.try_borrow_mut() {
pool.run_until_stalled();
}
// If already borrowed, we're likely in a nested poll, so do nothing.
});
},
};
EXECUTOR_FNS
.set(executor_impl)
.map_err(|_| ExecutorError::AlreadySet)
}View on GitHub (pinned to 32d20f6c9d)
Solutions
- Do not fork after calling init_futures_executor; initialize the executor after any fork (post-fork in the child, call init again only if EXECUTOR_FNS was never set)
- Use `init_tokio`/`init_async_executor` and their local-set equivalents on runtimes that support spawn_local safely across your process model
- Ensure executor init happens once per process and only on threads that will run the local pool
- Wrap spawn_local calls behind your own guard that checks the executor is live before spawning
Example fix
// before
// fork() first, then init and spawn_local
let child = unsafe { libc::fork() };
any_spawner::executor::init_futures_executor().unwrap();
// after
any_spawner::executor::init_futures_executor().unwrap(); // init BEFORE any fork
// or after fork, re-exec the process instead of continuing in the child Defensive patterns
Strategy: try-catch
Try / catch
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
any_spawner::executor::spawn_local(async { /* task */ });
}));
if result.is_err() {
eprintln!("spawn_local failed: local pool unavailable on this thread");
} Prevention
- Never fork() after initializing the executor; init after fork or re-exec
- Initialize the executor once at process start and keep it alive for the process lifetime
- On runtimes with local-set support, prefer tokio's LocalSet-backed init over the futures LocalPool
- Keep spawn_local calls on threads that participate in the same process init
When it happens
Trigger: Calling `any_spawner::executor::spawn_local(...)` (used by Leptos for local reactive tasks) on a thread whose LocalPool spawner can no longer accept tasks — most notably across `fork()` without exec where thread-locals/runtime state is broken, or when spawning after pool teardown/shutdown.
Common situations: Applications that fork after initializing the executor (e.g. daemonization) and then spawn local tasks in the child; spawning during shutdown; using spawn_local from a thread other than where polling was expected after re-initialization attempts.
Related errors
- Executor::spawn_local called, but no global 'spawn_local' fu
- At {caller}, tried to spawn a Future with Executor::spawn_lo
- Unsupported server function HTTP method: {method:?}
- {}
- Reading from a LocalResource outside Suspense in `ssr` mode
AI-assisted analysis of leptos-rs/leptos@32d20f6c9d (2026-09-01).
Data as JSON: /api/errors/6a84b8bf4c7d6431.
Report an issue: GitHub.