Why cannot I use non-threads-safe variable in tokio::spawn of basic scheduler?

Hi, I'm trying to write a rusty_v8 based application with tokio.

And now I want to implement a setTimeout function in rust:

    fn set_timeout(scope: &mut HandleScope, args: FunctionCallbackArguments, _: ReturnValue) {
    let func = Local::<Function>::try_from(args.get(0)).unwrap();
    let duration = Local::<Number>::try_from(args.get(1)).unwrap();
    
    // v8::Global<v8::Function>
    let global_func = Global::<Function>::new(scope, func);

    tokio::spawn(async {
        global_func;
        // do something
    });

    println!("123");
}

then tokio::spawn is underscored for:

future cannot be sent between threads safely
future created by async block is not Send
help: within impl std::future::Future, the trait std::marker::Send is not implemented for std::ptr::NonNull<rusty_v8::Function>rustc

Of course, I know a v8::Global<v8::Function> is not threads safely, but I noticed that tokio is running in single thread with rt-core feature (which should not have a problem of thread safety?)

So, my question is as the title, am I missing something? Is there a solution?

The Runtime object of a basic scheduler still implements Send, so if you could spawn non-Send futures on it, it would be possible to move them to another thread by moving the runtime itself.

Additionally, tokio::spawn doesn't know what kind of runtime you are spawning on.

1 Like

Thanks for reply

How can i spawn non- Send futures on it? Seems impossible

You need to use a LocalSet. Once you are using that, you can use spawn_local.

use tokio::runtime::Builder;
use tokio::task;

fn main() {
    let mut rt = Builder::new()
        .enable_all()
        .basic_scheduler()
        .build()
        .unwrap();
    let local = task::LocalSet::new();
    local.block_on(&mut rt, async {
        // Async code running in here can spawn using `spawn_local`.
        let task = task::spawn_local(async move {
            println!("Spawned non-Send task.");
        });
        
        println!("In async code.");
        
        task.await.unwrap();
    });
}

This works because LocalSet does not implement Send, so any futures stored inside it are guaranteed to stay on the current thread.

Note that if you leave the LocalSet by calling the ordinary tokio::spawn, you can no longer call spawn_local from that task. This is because tokio::spawn stores the future in rt, not in local, and once it's outside of the LocalSet, it can't get back in.