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

  1. 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)
  2. Use `init_tokio`/`init_async_executor` and their local-set equivalents on runtimes that support spawn_local safely across your process model
  3. Ensure executor init happens once per process and only on threads that will run the local pool
  4. 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

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


AI-assisted analysis of leptos-rs/leptos@32d20f6c9d (2026-09-01). Data as JSON: /api/errors/6a84b8bf4c7d6431. Report an issue: GitHub.