Tokio runtime inside crate code

Hello !

I'm discovering and trying gpui-component. During my proof of concept project, I need to instantiate gitlab objects. I use async version of the gitlab object (as gpui-component use async and sync code freeze the ui).

I have an error when the gitlab async object is created:

thread 'main' panicked at src/xxx/xxx.rs:xx:xx:
there is no reactor running, must be called from the context of a Tokio 1.x runtime

And, I can reproduce with a minimal code:

[package]
name = "demo"
version = "0.1.0"
edition = "2021"

[dependencies]
gpui = "*"
gpui-component = "*"
anyhow = "*"
tokio = { version = "1.48.0" }
use gpui::*;
use gpui_component::*;

pub struct Example;
impl Render for Example {
    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
        div().child("Hello world")
    }
}

fn main() {
    let app = Application::new();

    app.run(move |cx| {
        gpui_component::init(cx);

        cx.spawn(async move |cx| {
            cx.open_window(WindowOptions::default(), |window, cx| {
                let view = cx.new(|_| Example);
                cx.spawn(async move |_cx| {
                    // There is the code extracted from gitlab (and used crates code) crate (in my original code)
                    tokio::task::spawn_blocking(move || {
                        //
                    });
                })
                .detach();
                cx.new(|cx| Root::new(view.into(), window, cx))
            })?;

            Ok::<_, anyhow::Error>(())
        })
        .detach();
    });
}

I'm not very familiar with async in rust. I have done a lot of async in Python, so I understand the basics. But, I don't know yet how a code from a crate (here tokio::task::spawn_blocking) can share the "loop" (Python world naming, here it seems "reactor") with another crate, here gpui-component.

Also, I'm not sure about how use gpui-component, as its young crate with almost no doc.

I'm open to any help :slight_smile:

Thanks !

In order to use task and IO operations from Tokio while not using Tokio for the main structure of your application, you must:

  1. Create a Tokio Runtime once at startup using the Builder, and make sure you don't drop it (e.g. let it be a variable in main).
  2. Get a Handle to that Runtime using Runtime::handle().
  3. Just before calling any any Tokio function that needs a runtime, like spawn_blocking, use Handle::enter().

(Note that you do not need to do anything special to await futures provided by Tokio — contrary to some common misunderstandings, it is fine to poll Tokio futures from a different executor such as the one that evidently is built into gpui. The requirement is that the runtime has been entered before calling Tokio functions that create futures. Once the futures exist, they remember their runtime.)

2 Likes

Here's a more hands-on example of what kpreid said: https://stackoverflow.com/questions/66328113/tokio-error-there-is-no-reactor-running-even-with-tokiomain-and-a-single

1 Like

Hi,

I made working the minimal code example with your recomandations :

diff --git a/Cargo.toml b/Cargo.toml
index aaaa616..80f4635 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,4 +7,4 @@ edition = "2021"
 gpui = "*"
 gpui-component = "*"
 anyhow = "*"
-tokio = { version = "1.48.0" }
+tokio = { version = "1.48.0", features = ["rt", "rt-multi-thread"] }
diff --git a/src/main.rs b/src/main.rs
index d05fc0e..8775f0a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,12 +11,22 @@ impl Render for Example {
 fn main() {
     let app = Application::new();
 
+    let runtime = tokio::runtime::Builder::new_multi_thread()
+        .worker_threads(4)
+        .thread_name("my-custom-name")
+        .thread_stack_size(3 * 1024 * 1024)
+        .build()
+        .unwrap();
+    let handle = runtime.handle();
+    let _guard = handle.enter();
+
     app.run(move |cx| {
         gpui_component::init(cx);
 
         cx.spawn(async move |cx| {
             cx.open_window(WindowOptions::default(), |window, cx| {
                 let view = cx.new(|_| Example);
+
                 cx.spawn(async move |_cx| {
                     tokio::task::spawn_blocking(move || {
                         //

I will now try to make my real code working. Thanks a lot !