leptos-rs/leptos · critical

failed to spawn future on ThreadPool

Error message

failed to spawn future on ThreadPool

What it means

Panics when the futures-executor `ThreadPool::spawn(fut)` call inside the globally installed ExecutorFns returns Err. `ThreadPool::spawn` fails when the pool has been closed or when the pool object no longer has any threads registered (e.g. its threads were unable to start or all exited), so the future cannot be queued. any_spawner's spawn closure `.expect()`s on it, turning a spawn failure into an immediate panic in the caller's thread.

Source

Thrown at any_spawner/src/lib.rs:274

        static THREAD_POOL: OnceLock<ThreadPool> = OnceLock::new();
        thread_local! {
            static LOCAL_POOL: RefCell<LocalPool> = RefCell::new(LocalPool::new());
            // SPAWNER is derived from LOCAL_POOL, keep it for efficiency inside the closure
            static SPAWNER: LocalSpawner = LOCAL_POOL.with(|pool| pool.borrow().spawner());
        }

        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.
                });

View on GitHub (pinned to 32d20f6c9d)

Solutions

  1. Prefer initializing with a runtime-backed executor (`init_tokio`, `init_async_executor`) so spawning is tied to a live runtime instead of a standalone ThreadPool
  2. Ensure the executor is initialized once at program start before any spawning, and keep the process alive as long as tasks may be spawned
  3. Audit worker-thread panics (panic hooks) that could close/empty the pool; fix or guard those task bodies
  4. Avoid spawning tasks during process shutdown; gate spawns behind an app-lifecycle flag

Example fix

// before
any_spawner::executor::init_futures_executor().unwrap();
// after
// bind spawning to the tokio runtime the server already runs on
any_spawner::executor::init_tokio().unwrap();
Defensive patterns

Strategy: try-catch

Try / catch

// spawning through any_spawner panics on failure; keep task-spawning entry points
// isolated and catch unwind at the boundary
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    any_spawner::executor::spawn(async { /* task */ });
}));
if result.is_err() {
    eprintln!("spawn failed: thread pool unavailable");
}

Prevention

When it happens

Trigger: After `init_futures_executor()`, calling any API that routes through `any_spawner::executor::spawn(...)` (e.g. Leptos tasks, `spawn` from a reactive scope) when the lazily created ThreadPool was closed or its worker threads never registered/failed.

Common situations: ThreadPool thread panics or the process is shutting down while tasks are still being spawned; exotic hosts where the pool's threads failed to register; mixing executor features so tasks spawn after pool teardown; running under environments that kill worker threads (signal handlers, fork after threads created).

Related errors


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