How to use channels inside sync functions inside tokio runtime?

Hi, I am using a rust binding library rusty_v8, since it's from C++ world, and the rust bindings are actually !Send and !Sync, all smart pointers have to be Rc, and all the interops (rust implemented builtin APIs, exposed to javascript side) have to be sync.

When using it with tokio runtime, I'm trying to send a message inside a sync function to the main thread (main loop) handled by tokio::select!.

The API I'm using is mpsc::Sender::blocking_send. Everything seems correct when I run cargo build, but when I start my command line to run it, there's an error:

thread 'main' panicked at src\js\binding\global.rs:76:6:
Cannot block the current thread from within a runtime. This happens because a function attempted to block the current thread while the thread is being used to drive asynchronous tasks.
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'main' panicked at library\core\src\panicking.rs:221:5:
panic in a function that cannot unwind

Is there any best practice I can follow for such pattern? Maybe I need to find a way to convert these sync functions to futures::Poll type?

Why can't you make the function asynchronously send the value? If you can't mark the function with async, you can get a handle to the runtime you are currently in (which you are as otherwise the panic wouldn't occur) with Handle::current and do the asynchronous send by spawning a task with Handle::spawn.

Thanks for teaching, I think I have one more question:

If I use Handle::current and Handle::spawn, is there a way to join these tasks after complete? In my current codebase, I'm using TaskTracker to spawn async tasks, so these tasks will be automatically be joined.

After I rewrite my code with TaskTracker, instead of using Handle::current and Handle::spawn APIs (sorry I didn't follow your instructions yet, but I will try later) The cargo build is current, and there's a new error:

thread 'main' panicked at src\js\binding\global.rs:76:34:
`spawn_local` called from outside of a `task::LocalSet`
stack backtrace:

Why I prefer using TaskTracker? Because in a previous topic (How to share tokio JoinSet across different tasks/threads? - #3 by alice), I learned this skill from another expert, hope I didn't misunderstand it, please correct me if I'm wrong.

Thanks

Please always post text, not screenshots. Not everyone can read images.

1 Like

got it, edited.

1 Like

Can you use task::spawn_local instead to spawn the future with the send? TaskTracker can also be used. Without seeing your code, we can only guess what your setup looks like.

Actually I already tried both tokio::task::spawn_local and TaskTracker::spawn_local APIs. And both two solutions throw the same error message in my previous comments:

thread 'main' panicked at src\js\binding\global.rs:76:34:
`spawn_local` called from outside of a `task::LocalSet`
stack backtrace:

I'm afraid my codebase is already too big to be a minimal reproduce (because there's a lot of code to handle V8 engine).

But basically the workflow (of the codebase) is:

  1. Create the EventLoop structure and the tokio runtime in the main function. Note:
    • When create the EventLoop structure, it creates a task_tracker inside it.
    • It also creates the JsRuntime structure, and copied a clone of task_tracker into it.
  2. Initialize the JsRuntime (it wraps the V8 engine, and provide most of interops with javascript). It will execute a javascript file during the initialization.
  3. Inside the javascript file, it calls a function setTimeout which is implemented in rust side (named set_timeout).
  4. Inside the rust set_timeout function, it calls the task_tracker.spawn_local API to spawn an async task.
  5. Start the loop of tokio::select! on multiple channels and stdin/stdout.

The error message is throwed in step-4.

One thing need to notice is: the step-2 and step-3 is running inside the V8 engine, which is a C++ library, maybe some context is missing when rust application invokes C++ library, and C++ library calls interops provided by rust bindings.

I can't find the place where you create and execute a LocalSet in your repo (I'd expect this in your main function, more specifically inside the future you pass to Runtime::block_on). Without a LocalSet you can't spawn thread-local, !Send futures via any sort of spawn_local call. The runtime handle does not contain a LocalSet, you need to create one yourself. You can simply wrap your event loop logic in a call to LocalSet::run_until and continue to use your TaskTracker. Note though that you can't spawn_local from a task that you spawned with task::spawn.

2 Likes

oh, (after read doc again) I also realized I need the LocalSet when using spawn_local API, will try later.

1 Like

emmmm, I come here again.

Try LocalSet solution

It failed, (also see LocalSet)

  1. The block_on API need to run with a Runtime, not a runtime Handle, how can I get the current runtime this function is running in?
  2. The run_until API need to call await to take effect, which means the caller function will also to be an async function. But the caller function has to be a sync function because it will be binded into V8 as a callback.
  3. The spawn_local API will never be triggered or scheduled by the runtime.

Try Runtime::Handle solution

If failed (also see Handle)

  1. The block_on API failed, the error message is: 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.

How to send a message from sync world to tokio's async world?

Somehow, I believe this is a deep gap between tokio's async world and the normal sync world.

Because it seems tokio frameworks handles everything in an async way, and also requires user code to make everything async, but it conflicts with the V8 API which is from C++ world, i.e. the old and normal sync world.

Actually I just want to send a message from the sync world to tokio's async world, to trigger the tokio::select! to run a loop for once.

I'm not sure if I could simply use a std::sync::mpsc::Sender to send a message inside the sync function, and let tokio::select! listen to the std::sync::mpsc::Receiver concurrently with other async triggers?

I create another post Use std::sync::mpsc::channel with tokio::select!? for this question (because I think this thread is a little big too long to understand the context).

In this case, why is this "sync world" function ever running inside runtime? Shouldn't it be on a different thread, where blocking, including send_blocking, is a correct way?

2 Likes

Spawn the V8 engine running in a completely separate thread (i.e. the std::thread::spawn), the solution will requires too many engineering effort to sync data between the V8 and tokio runtime.

I'm so s**pid!!!

Finally just use tokio::runtime::Handle::spawn_blocking and Sender::blocking_send could work in pure sync functions!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.