Electron100 / butane

An ORM for Rust with a focus on simplicity and on writing Rust, not SQL
Apache License 2.0
101 stars 13 forks source link

Postgres backend in Axum: Cannot start a runtime from within a runtime. #275

Open RobertoMaurizzi opened 1 month ago

RobertoMaurizzi commented 1 month ago

Hi!

I'm still going at using Butane within a Leptos/Axum project. I got to a decent point where I'd like to have more visibility on database changes and types, so I switched from SQLite to Postgres. Unfortunately when I try to use the database I now get a:

thread '<unnamed>' panicked at /home/robm/.cargo/registry/src/index.crates.io-6f17d22bba15001f/postgres-0.19.9/src/connection.rs:66:22:
Cannot start a runtime from within a runtime. This happens because a function (like `block_on`) attempted to block the current thread while the thread is being used to drive asynchronous tasks.

and a backtrace. Searching around I found https://github.com/tokio-rs/tokio/issues/2194 where they say it's quite normal since the postgres crate doesn't support async, so if you try to await something you'll cause this... they recommend using a tokio-postgres crate instead.

Did anyone see this before? Could it be I'm doing something wrong in how I manage the r2d2 connection instead?

Electron100 commented 1 month ago

That is unfortunately a known issue. Here's what's going on

  1. Butane is not async aware/capable in its released / main-branch form.
  2. Axum requires async (as does Actix and many other Rust web frameworks).
  3. Butane's Postgres support uses the postgres crate
  4. The postgres crate is actually a wrapper around tokio-postgres and essentially provides a synchronous interface by using block_on from a Tokio runtime.
  5. This means that the layering goes async (Axum) -> sync (Butane) -> async with a different runtime (tokio-postgres). As you've discovered, this nesting is causes Tokio to explode.

I would like to offer async capabilities in Butane. Progress has been slow I'm afraid due to both lack of time on my part as well as complications from my desire to offer both sync and async interfaces at the same time, without duplicating all the code and without major breaking changes to the existing sync APIs. Why both sync and async when most major web frameworks are async-oriented? It stems from Butane's origins as a simple interface for object persistence -- I'd like Butane to be an equally valid choice for data persistence in local agents or CLIs which will typically use SQLite and have no need for async.

However, async with Butane is actually getting really close, and is ready for feedback! You can check it out on the async-wip branch. As of this week, I expect the API to be stable, although that's not a guarantee. If you're interested in giving it a whirl, it should solve your issues with Axum.

On the other hand, if you're not up for being an early adopter, it should be possible to use channels to run your database operations on a separate thread to avoid the runtime nesting. If you'd like to chat more about that I'm happy to provide some pointers, as that's similar to what I've done in the async-wip branch of Butane to provide full parity between sync and async (e.g. you can use sqlite with async and Butane runs it on its own dedicated thread).

See also #13

RobertoMaurizzi commented 1 month ago

Nice! Unfortunately I'm still quite new to both Rust and working with Leptos/Axum, plus free time to work on my project is limited, so I'm not sure what I'll be able to do, but I'll try to replace r2d2 with deadpool/bb8 and give the feedback I can.