Search code examples
rustrpc

Tarpc how to call outer struct's method?


I'm using tarpc to create a tcp service.

rpc service:

#[tarpc::service]
pub trait Hello {
    async fn say_hi() -> String;
}

server:

pub struct HelloServer;

#[tarpc::server]
impl Hello for HelloServer {
   /// requires to call SingletonInstance::say_hi()
   async fn say_hi(self, context: tarpc::context::Context) ->String {
      return "hi".to_string();
     }
 }

/// I want HelloServer to be a proxy of SingletonInstance
/// The data flow like this:
/// client tarpc call -> helloServer.say_hi -> singletonInstance.say_hi -> send response through tarpc
/// 
pub struct SingletonInstance {}

impl SingletonInstance {
    pub async fn say_hi() -> String {
        return String::new()
    }
}

Full Example https://github.com/divflex/demo-rpc

Question: The HelloServer is basiclly a proxy for the SingletonInstance,so the say_hi method need to call the same method on SingletonInstance. What's the proper way to call SingletonInstance::say_hi ?

My idea: I created a tx(tokio::sync::mpsc) property on HelloServer, so the instance can use self.tx.send() to communicate with SingletonInstance.But HelloServer also need a receiver to get results. This seems too complicated.


Solution

  • One way to call SingletonInstance::say_hi from HelloServer is to add a link/reference to SingletonInstance in HelloServer. You can do this by adding a field to the HelloServer struct that holds an Arc<SingletonInstance> and then use this reference to call the say_hi method on the SingletonInstance:

    use std::sync::Arc;
    
    pub struct HelloServer {
        singleton: Arc<SingletonInstance>,
    }
    
    #[tarpc::server]
    impl Hello for HelloServer {
        async fn say_hi(self, context: tarpc::context::Context) -> String {
            self.singleton.say_hi().await
        }
    }
    
    pub struct SingletonInstance {}
    
    impl SingletonInstance {
        pub async fn say_hi(&self) -> String {
            "hi".to_string()
        }
    }
    

    This way, you can avoid using channels to communicate between HelloServer and SingletonInstance.

    Another way is you create an instance of SingletonInstance onto HelloServer (but that's not what SingletonInstance means :)

    use tarpc::context;
    
    #[derive(Clone)]
    pub struct HelloServer {
        instance: SingletonInstance,
    }
    
    #[tarpc::server]
    impl Hello for HelloServer {
        async fn say_hi(self, _: context::Context) -> String {
            self.instance.say_hi().await
        }
    }
    
    pub struct SingletonInstance {}
    
    impl SingletonInstance {
        pub async fn say_hi(&self) -> String {
            "hi".to_string()
        }
    }
    

    Cloning is necessary because tarpc creates a new instance of the server for each connection, and we need to be able to clone the server to serve multiple clients.