Spawning OS Threads from Tokio Contexts

  • I was surprised that nothing exploded when I tried spawning OS threads from within async fns

Spawning OS threads apparently works just fine from async contexts. I was expecting there to be problems, similar to the way that invoking tokio code outside the tokio runtime results in spontaneous combustion or trying to create a Runtime when you're already in a Runtime tears a hole in the space time fabric.

fn get_runtime() -> tokio::runtime::Runtime {
    tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap()
}

fn fun_with_explosives() {
    let rt = get_runtime();
    rt.block_on(async {
        let inception = get_runtime();
        rt.block_on(async {
            // TODO: add leonardo dicaprio implementation here
        });
    });
}

But, no, apparently you can spawn threads just fine inside of the invisible walls of a Runtime.

use tokio::sync::mpsc::{channel, Sender, Receiver};

fn get_runtime() -> tokio::runtime::Runtime {
    tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap()
}

fn spawn_os_thread(tx: Sender<u64>) -> std::thread::JoinHandle<i32> {
    std::thread::spawn(move || {
        // do some work
        let mut x = 0u64;
        for i in 0..(1024 * 1024) {
            x += i;
        }
        
        // inside the thread, create another rt and use it
        // to execute async code
        let rt = get_runtime();
        
        // send our message via an async channel using our new rt
        rt.block_on(async {
            
            tx.send(x)
                .await
                .expect("tx.send failed");
        });

        123
    })
}

async fn f() {
    let (tx, mut rx) = channel(4);
    let thread = spawn_os_thread(tx);
    
    // await on message sent from os thread    
    dbg!(rx.recv().await);
    
    // then also join the os thread
    dbg!(thread.join());
    
    // tada
}

fn main() {
    let rt = get_runtime();
    rt.block_on(f());
}

Playground results:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 2.11s
     Running `target/debug/playground`
[src/main.rs:45] rx.recv().await = Some(
    549755289600,
)
[src/main.rs:48] thread.join() = Ok(
    123,
)

Sorry ... I just didn't expect that experiment to actually work.