smol-rs/smol

Task::spawn failing when using Tide

andrewbanchich opened this issue · 8 comments

I just switched over a server from using warp to using tide, and noticed that calling Task::spawn fails now with:

thread 'async-std/runtime' panicked at '`Task::spawn()` must be called from an `Executor` or `LocalExecutor`', /Users/andrewbanchich/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/src/libstd/macros.rs:13:23

Everything I'm calling that is async is started within smol::run

    smol::run(async {
        tide::log::start();
        let mut app = tide::new();
        app.at("/example").post(example_handler);
        app.listen(listen_addr).await.unwrap();
    })

example_handler is what is using Task::spawn.

Am I doing anything wrong here?

Isn't that what smol::Task::spawn should be doing itself?

I'm not sure what the right solution is here. Here are some design choices for executors:

  1. There is no global executor (this is the current situation). Instead, you start one with smol::block_on() or smol::run() and then must call Task::spawn() within the executor. Otherwise, you get the panic because we don't know which executor to spawn the task onto, because there could be multiple executors to choose from.

  2. There is a global executor, and the first time it is initialized, an executor thread pool is created. This way Task::spawn() will never panic because it can spawn onto the current executor, or onto the global executor if spawned outside an executor. This is similar to how async-std works.

  3. There is a global executor, but it does not create a thread pool. It only serves as a task queue for tasks spawned outside an executor. Then, other executors like smol::run() will occasionally grab some tasks from the global executor. This is similar to how smol v0.1 worked.

Any preferences or opinions? I feel the current panic is unfortunate and is very much like tokio's infamous panic when using its primitives outside the runtime.

That's what I'm confused about: I am currently using smol::run() and spawning a Task within that, and it's giving me the panic I posted.

Right :) The problem is that tasks are actually not spawned within smol::run().

When you start Tide, it listens for incoming connections and creates a task with async_std::task::spawn() for each connection. Inside this task spawned onto async-std's executor, Tide calls your example_handler. So now we're inside async-std's executor, not inside smol::run()! Then, if we do Task::spawn() there, we get a panic because we're outside the smol's executor.

It's all very subtle and confusing, which is why I think the current situation is far from ideal.

You can try adding async_executor to your dependencies, getting a Spawner using async_exexutor::Spawner::current and using this to spawn tasks (it will end up in the same executor as Task::spawn

It's still panicking for me:

thread 'async-std/runtime' panicked at '`Spawner::current()` must be called from an `Executor`'

@stjepang Thanks for the explanation! Yeah, that isn't how I imagined it currently works.

@Keruspe Ah, I see what you were saying now. I updated my code to use OnceCell and created a Spawner in the context of smol::run() and set the OnceCell to that, then accessed it throughout the rest of my program.

That works! Thank you very much!

Some ideas on how to fix this issue in #202

Feedback would be very helpful!