Why does tokio::runtime::Runtime::enter() affect each other?

use tokio::runtime::Runtime;

fn function_that_spawns(msg: &'static str) {
    for i in 0..5 {
        tokio::spawn(async move {
            println!("{i}{msg}");
        });
    }
}
fn main() {
    let rt = Runtime::new().unwrap();    
    let v = "Hello World!";
    let v2 = "Hello2World!";
    // Why does function_that_spawns(v) not have any output after opening this thread?
    let _new_thread = std::thread::spawn(|| {
        let rt2 = Runtime::new().unwrap();
        let _guard2 = rt2.enter();
        function_that_spawns(v2);
    });
    
    let _guard = rt.enter();
    // Why dose not comment out this line? There's no output from function_that_spawns(v)?
    // std::thread::sleep(std::time::Duration::from_secs(1));
    function_that_spawns(v);    
    // j.join().unwrap();
}

If you just shut down the runtime like that, tasks that were spawned might not have started executing yet, at all. Such a task would be cancelled and thus never executed. Only in-flight tasks are waited for on shutdown, and only up to the next yield point, in your case you have futures that only execute println, so there are no yield points beyond the very start and very end of the block.

I must admit, in this regard the very documentation of Runtime::enter offers potential for confusion. In fact, if you execute that code example it may or may not print "Hello World" (the point is supposed to be that the spawn succeeds, not that the spawned task runs, necessarily, but that can be misinterpreted easily).

The way to ensure that the tasks you're interested in run to completion is by using API like Runtime::block_on in particular.

1 Like

I'm sorry, my main confusion is why does runtime::enter affect each other? Although they are located in different threads.The secondary confusion is why the main thread cannot sleep after rt.enter()?
Please see the example in Runtime in tokio::runtime - Rust (docs.rs)

They don't affect each other, as far as I’m aware. What are you observing? I can’t see any way in which they affect each other. Of course, correct me if I’m wrong :slight_smile:


The sleep is also not preventing any behavior. The outputs are still random in each of these cases. For example with the sleep present

use tokio::runtime::Runtime;

fn function_that_spawns(msg: &'static str) {
    for i in 0..5 {
        tokio::spawn(async move {
            println!("{i}{msg}");
        });
    }
}
fn main() {
    let rt = Runtime::new().unwrap();    
    let v = "Hello World!";
    let v2 = "Hello2World!";
    // Why does function_that_spawns(v) not have any output after opening this thread?
    let _new_thread = std::thread::spawn(|| {
        let rt2 = Runtime::new().unwrap();
        let _guard2 = rt2.enter();
        function_that_spawns(v2);
    });
    
    let _guard = rt.enter();
    // Why dose not comment out this line? There's no output from function_that_spawns(v)?
    std::thread::sleep(std::time::Duration::from_secs(1));
    function_that_spawns(v);    
    // j.join().unwrap();
}

I’m getting all of these outputs (and more)

$ cargo run --release
   Compiling libc v0.2.153
   Compiling pin-project-lite v0.2.13
   Compiling num_cpus v1.16.0
   Compiling tokio v1.36.0
   Compiling play v0.1.0 (/…/playground/play)
    Finished release [optimized] target(s) in 12.14s
     Running `target/release/play`
0Hello2World!
3Hello2World!
4Hello2World!
1Hello2World!
2Hello2World!
0Hello World!

$ cargo run --release
    Finished release [optimized] target(s) in 0.04s
     Running `target/release/play`
0Hello2World!

$ cargo run --release
    Finished release [optimized] target(s) in 0.03s
     Running `target/release/play`
0Hello World!
3Hello World!
4Hello World!
2Hello World!
1Hello World!

$ cargo run --release
    Finished release [optimized] target(s) in 0.03s
     Running `target/release/play`
0Hello2World!
1Hello2World!
2Hello2World!
3Hello2World!
4Hello2World!
0Hello World!
1Hello World!
2Hello World!
3Hello World!
4Hello World!

Why are these outputs so random? That’s because of the problem I’ve described in my first answer that the runtime is shut down while some of the spawned tasks may or may not have started already. This problem is the source (and only source) of your “main confusion”, as far as I can tell. Perhaps certain things like spawning a thread or sleeping turn out to make one or the other other outcome more likely; maybe that’s even more so on your machine for some reason. Still, you can’t reason about the code like that, and are probably just drawing incorrect conclusions.

Why dose the example in Runtime in tokio::runtime - Rust (docs.rs) has a stable output?can insert a sleep between let _guard = rt.enter(); and function_that_spawns(s);

It doesn’t. That’s my point above:

1 Like

Oh, you have solved my confusion, thank you very much!

For example in this playground running it a lot of times, it’s still quite rare, but the “Hello World” is sometimes missing:

1 Like