Hi, I am working on websockets using tokio-tungstenite crate. So once after the socket connection is established, based on some event change in front-end, my listen events in rust gets triggered. And inside the listen event I'm trying to send request to server. Since it is an async call, can someone help me with the syntax for async/await inside listen events in tauri application?
Code:
#[tokio::main]
async fn main() {
let url = url::Url::parse("wss://127.0.0.1:8080/ws").unwrap();
let (ws_stream, _) = connect_async(url).await.expect("Failed to connect");
println!("WebSocket handshake has been successfully completed \n");
tauri::Builder::default()
.setup(move |app|{
let (write, read) = ws_stream.split();
let write_stream = Mutex::new(write);
let read_stream = Mutex::new(read);
app.listen_global("request", move |_handler| {
let mut write_data = write_stream.lock().unwrap();
let mut read_data = read_stream.lock().unwrap();
println!("Sending the request to the server");
write_data.send(Message::Text(r#"{
"type": "config"
}"#.to_string()+"\n")).await.unwrap();
println!("Request sent!!!");
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
You probably want the tauri::async_runtime::spawn() function to run the async code in the background. If you want your listener to wait until the async code is finished (not recommended because it could block your main thread), you can use tauri::async_runtime::block_on() to block and get the future's return value.
I tried using this handle.block_on() method, but then I'm facing a runtime error "Cannot start a runtime from within a runtime".
Code:
#[tokio::main]
async fn main() {
let url = url::Url::parse("wss://127.0.0.1:8080/ws").unwrap();
let (ws_stream, _) = connect_async(url).await.expect("Failed to connect");
println!("WebSocket handshake has been successfully completed \n");
let handle = Handle::current();
tauri::Builder::default()
.setup(move |app|{
let (write, read) = ws_stream.split();
let write_stream = Mutex::new(write);
let read_stream = Mutex::new(read);
app.listen_global("request", move |_handler| {
let mut write_data = write_stream.lock().unwrap();
let mut read_data = read_stream.lock().unwrap();
println!("Sending the request to the server");
handle.block_on(async {
write_data.send(Message::Text(r#"{
"type": "config"
}"#.to_string()+"\n")).await.unwrap();
println!("Request sent!!!");
})
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Error:
thread 'tokio-runtime-worker' panicked at '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.', C:\Users\PhaleAam\.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-1.19.2\src\runtime\enter.rs:39:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Yeah, when I tried using the spawn() method, I got compile-time error "future cannot be sent between threads safely"
Code:
#[tokio::main]
async fn main() {
let url = url::Url::parse("wss://127.0.0.1:8080/ws").unwrap();
let (ws_stream, _) = connect_async(url).await.expect("Failed to connect");
println!("WebSocket handshake has been successfully completed \n");
let handle = Handle::current();
tauri::Builder::default()
.setup(move |app|{
let (write, read) = ws_stream.split();
let write_stream = Mutex::new(write);
let read_stream = Mutex::new(read);
app.listen_global("request", move |_handler| {
let mut write_data = write_stream.lock().unwrap();
let mut read_data = read_stream.lock().unwrap();
println!("Sending the request to the server");
tauri::async_runtime::spawn(async move{
write_data.send(Message::Text(r#"{
"type": "getConfig",
"envelope": {
"scope": ["navigation"],
"content" : ["all"]
}
}"#.to_string()+"\n")).await.unwrap();
println!("Request sent!!!");
});
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Error:
error: future cannot be sent between threads safely
--> src\main.rs:36:7
|
36 | tauri::async_runtime::spawn(async move{
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ future created by async block is not `Send`
|
= help: within `impl futures_util::Future<Output = ()>`, the trait `std::marker::Send` is not implemented for `std::sync::MutexGuard<'_, SplitSink<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, tokio_tungstenite::tungstenite::Message>>`
note: captured value is not `Send`
--> src\main.rs:37:9
|
37 | write_data.send(Message::Text(r#"{
| ^^^^^^^^^^ has type `std::sync::MutexGuard<'_, SplitSink<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, tokio_tungstenite::tungstenite::Message>>` which is not `Send`
note: required by a bound in `tauri::async_runtime::spawn`
--> C:\Users\PhaleAam\.cargo\registry\src\github.com-1ecc6299db9ec823\tauri-1.0.3\src\async_runtime.rs:271:15
|
271 | F: Future + Send + 'static,
| ^^^^ required by this bound in `tauri::async_runtime::spawn`
And while using tauri::async_runtime block_on() method, I'm getting the same runtime error "Cannot start a runtime from within a runtime"
Code:
app.listen_global("request", move |_handler| {
let mut write_data = write_stream.lock().unwrap();
let mut read_data = read_stream.lock().unwrap();
println!("Sending the request to the server");
tauri::async_runtime::block_on(async move{
write_data.send(Message::Text(r#"{
"type": "getConfig",
"envelope": {
"scope": ["navigation"],
"content" : ["all"]
}
}"#.to_string()+"\n")).await.unwrap();
println!("Request sent!!!");
})
});
Error:
thread 'tokio-runtime-worker' panicked at '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.', C:\Users\PhaleAam\.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-1.19.2\src\runtime\enter.rs:39:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Ooof, so the problem is that Tokio doesn't like having two copies of itself run at the same time. Tauri has its own tokio environment already running, so when you add your tokio::main it causes problems.
I've noticed that Tauri provides a workaround for this:
I'm guessing the spawn would work if you moved the locks inside the spawn. At the moment it's complaining that the MutexGuard they return can't be sent between threads (that would mean that you lock on one thread and unlock on another, which basically doesn't make sense).
Is it really necessary to make fn main() async? According to the answers given by swetha above, I think it would be just fine if you make the main function sync right?