How to share tokio::net::TpcStream across multiple async fn in napi-rs

I'm new to Rust.

I want to implement a node addon with napi-rs for sending and receiving msg by TCP;

here is my code

use napi::{bindgen_prelude::Result, Error, Status};
use std::sync::{Arc, Mutex};
use tokio::{
  io::{AsyncReadExt, AsyncWriteExt},
  net::TcpStream,
};
// store TcpStream
lazy_static::lazy_static! {
  static ref TCP_STREAM: Arc<Mutex<Option<TcpStream>>> = Arc::new(Mutex::new(None));
}
#[napi]
pub async fn send_msg() -> Result<()> {
  let mut tcp = TCP_STREAM.lock().unwrap();
  let tcp_stream = tcp
    .as_mut()
    .ok_or_else(|| Error::new(Status::GenericFailure, "Not connected".to_string()))?;
 // here send msg to server
  tcp_stream.write("hello Rust".as_bytes()).await?;

  Ok(())
}

#[napi]
pub async fn listen_to_server() -> Result<u32> {
  let addr = "127.0.0.1:8899";
  let stream = TcpStream::connect(addr).await.unwrap();
  let p = TCP_STREAM.clone();
  let mut tcp = p.lock().unwrap();
  *tcp = Some(stream);

  let p2 = TCP_STREAM.clone();
  tokio::spawn(async move {
    loop {
      let mut mut_tcp = p2.lock().unwrap();
      let tcp_in = mut_tcp.as_mut().unwrap();
      let mut str = String::new();
      tcp_in.read_to_string(&mut str).await;
      // here listen server msg and do something
    }
  });

  Ok(1)
}

then I get an error at " tokio::spawn"

future cannot be sent between threads safely
within `{async block@src/lib.rs:39:16: 46:4}`, the trait `Send` is not implemented for `std::sync::MutexGuard<'_, Option<tokio::net::TcpStream>>

how to resolve this problem?

It seems ok that use tokio::sync::Mutex instead of std::sync::Mutex

When people ask me "how do I share a tcp stream?" My first answer is "don't". My second answer is give ownership of the tcp stream to an actor, and share the actor instead.

1 Like

how to dispatch a task if handle_message is async fn and there is a loop in handle_message. the task is to start a TcpStream and to listen to data by try_read in a loop.

I don't really understand your question. Are you able to give more details?

You should not be using try_read in a loop. That has no .await, so you would be blocking the thread. Use one of the async methods to read instead.

I want to new a TcpStream client. the duty of the client is to send msg to the server and to receive msg from the server. for APIs exported to Nodejs. I want a this

import {createClient, sendMsg} from './node-addon-build-by-rusr-napi-rs'

async function main () {
    await createClient('address', { onData: (msg) => {
          console.log("msg from the server is", msg)
          // handle msg

          // maybe
          sendMsg({type: 'login', data: encrypt(data)})
    } })
}

On Rust side, in napi-rs doc, I find async and tokio.
at first, sharing TcpStream across multiple async fns annotated by #[napi] occurred to me.
Then you mentioned actor, and I just took a look at it, but I feel that due to the spread of await, it will still block somewhere anyway.

You may be able to find some inspiration in this example:

Specifically, look at the src/client.rs file.

1 Like

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.