18

I am using mongodb with tokio as my async runtime.

I want to initialize the MongoDB Client globally so I used the lazy-static crate. The problem is that the client connects asynchronously, and lazy_static! doesn't support the async keyword:

use mongodb::Client;

async {
    let client = Client::with_uri_str(&env_var("MONGO_URL")).await.unwrap();
}

So how can I initialize the client?

Related:

kmdreko
  • 42,554
  • 6
  • 57
  • 106
Mohammed
  • 223
  • 2
  • 11
  • If blocking on initialization is ok using whatever `block_on` function tokio provides instead of `await`ing the result will get the job done. – Aiden4 May 22 '21 at 15:36
  • 1
    Your linked StackOverflow Q&A has the two suggestions I would have: either `block_on` or use a `OnceCell`. Why are the answers there insufficient? – kmdreko May 22 '21 at 16:09
  • As I understand it, what you're looking for is sort of a `OnceCell` wrapper that works like `lazy_static!`, except it is initialized with an `async` block instead of a closure. However, in this case, it seems like the `mongodb` library is poorly coded and the `with_uri_str` method doesn't really need to be `async`, so I would just use `block_on`. – Coder-256 May 23 '21 at 01:57
  • Thanks to all for your time, but using your approach create new issues, I am still looking for answer, with minimum [Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) – Mohammed May 29 '21 at 17:54
  • @Coder-256, @Aiden4 , @kmdreko, I tried `OnceCell `, `futures::executor::block_on` , `tokio::runtime::Runtime::new` etc, they all create new problems, like [“Cannot start a runtime from within a runtime”](https://stackoverflow.com/questions/62536566/how-can-i-create-a-tokio-runtime-inside-another-tokio-runtime-without-getting-th) or "thread 'tokio-runtime-worker' panicked at 'cannot execute `LocalPool` executor from within another executor: EnterError'" and much more... – Mohammed May 29 '21 at 18:02
  • Honestly I’d open an issue with the `mongodb` repo since the root issue is that this method is needlessly `async`. Any other solution would really be more of a workaround. However I can’t imagine why `OnceCell` wouldn’t work, what was the error? – Coder-256 May 29 '21 at 23:16
  • @Coder-256 you're right, but its not exactly `mongodb`'s fault. Setting up the client creates a "resolver" which if configured to use `async-std` will end up calling [`resolver()`](https://docs.rs/async-std-resolver/0.20.3/async_std_resolver/fn.resolver.html) or [`resolver_from_system_conf()`](https://docs.rs/async-std-resolver/0.20.3/async_std_resolver/fn.resolver_from_system_conf.html), which appear to be needlessly `async`. – kmdreko May 30 '21 at 03:08

1 Answers1

23

If you use a new runtime and the lazy static is first used within the context of an existing runtime, like in this example:

use lazy_static::lazy_static;
use mongodb::Client;

lazy_static! {
    static ref CLIENT: Client = {
        tokio::runtime::Runtime::new().unwrap().block_on(async {
            let uri = std::env::var("MONGO_URL").unwrap();
            let client = Client::with_uri_str(&uri).await.unwrap();

            client
        })
    };
}

#[tokio::main]
async fn main() {
    let _db = CLIENT.database("local");
}

You'll get the error mentioned:

thread 'main' panicked at '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.', C:\Users\kmdreko\.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-1.6.1\src\runtime\enter.rs:39:9

You may be able to circumvent this by using a different runtime (futures vs tokio vs async-std) but that's not ideal since it'd still be blocking the underlying one.


A relatively straight-forward way to solve this is not try to do it lazily and initialize it immediately in main. That way you can utilize an asynchronous runtime directly and not worry about needing one elsewhere:

use mongodb::Client;
use once_cell::sync::OnceCell;

static CLIENT: OnceCell<Client> = OnceCell::new();

#[tokio::main]
async fn main() {
    let uri = std::env::var("MONGO_URL").unwrap();
    let client = Client::with_uri_str(&uri).await.unwrap();
    CLIENT.set(client).unwrap();

    let _db = CLIENT.get().unwrap().database("local");
}

This can be done via OnceCell (as seen above) or something like a RwLock if necessary.


The most direct answer to what you're trying to achieve would be to use the async_once crate, which makes it use the receiver's runtime to drive the asynchronous function.

use async_once::AsyncOnce;
use lazy_static::lazy_static;
use mongodb::Client;

lazy_static! {
    static ref CLIENT: AsyncOnce<Client> = AsyncOnce::new(async {
        let uri = std::env::var("MONGO_URL").unwrap();
        let client = Client::with_uri_str(&uri).await.unwrap();

        client
    });
}

#[tokio::main]
async fn main() {
    let _db = CLIENT.get().await.database("local");
}

This assumes all or nearly all uses of the client will be in asynchronous contexts.

kmdreko
  • 42,554
  • 6
  • 57
  • 106