rustrust-tonic

Using a common variable between two services on the same server


I am trying to see if this is the right tool for my use case and playing with some hello worlds.

I currently have two proto definitions:

I followed the Hello World example of Tonic, and created my service with

 Server::builder()
        .add_service(GreeterServer::new(greeter))
        .add_service(WonderfulServer::new(wonderful))
        .serve(addr)
        .await?;

Now the thing is that I would like to have a common variable, let's say world_name, for both services Greeter and Wonderful to use and modify. Could I do this, and what would be the recommended way to proceed?

I have tried:

  1. Implementing both Wonderful and Greeter on the same MyGreeter struct, but then I can't chain the add_service as I did here, since the new() constructor uses value and not borrow, and I don't know how else to do.

  2. Creating a MyServer struct with a world_name variable, and look on the API/online if I could somehow use this struct as the server to add services, but I didn't find anything at all.

  3. Using some form of global singleton... I guess it works but meh... not a big fan if it's possible to do otherwise

Following is my full server.rs file in case it's useful.

use tonic::{transport::Server, Request, Response, Status};

use hello_wonderful_world::greeter_server::{Greeter, GreeterServer};
use hello_wonderful_world::{HelloReply, HelloRequest};

use hello_wonderful_world::wonderful_server::{Wonderful, WonderfulServer};
use hello_wonderful_world::{WonderfulRequest, WonderfulReply};

pub mod hello_wonderful_world {
    tonic::include_proto!("helloworld"); 
    tonic::include_proto!("wonderfulworld");
}

#[derive(Debug, Default)]
pub struct MyGreeter {}

#[derive(Debug, Default)]
pub struct MyWonderfulWorld{}

#[tonic::async_trait]
impl Greeter for MyGreeter {
    async fn say_hello(
        &self,
        request: Request<HelloRequest>, // Accept request of type HelloRequest
    ) -> Result<Response<HelloReply>, Status> { // Return an instance of type HelloReply
        println!("Got a request: {:?}", request);

        let reply = hello_wonderful_world::HelloReply {
            message: format!("Hello {}!", request.into_inner().name).into(), // We must use .into_inner() as the fields of gRPC requests and responses are private
        };

        Ok(Response::new(reply)) // Send back our formatted greeting
    }
}

#[tonic::async_trait]
impl Wonderful for MyWonderfulWorld {
    async fn its_a_wonderful_world(
        &self,
        request: Request<WonderfulRequest>)
        -> Result<Response<WonderfulReply>, Status> {
        let reply = hello_wonderful_world::WonderfulReply {
            message: format!("And I said to {}: What a Wonderful World", request.into_inner().name),
        };

        Ok(Response::new(reply))

    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "[::1]:50051".parse()?;
    let greeter = MyGreeter::default();
    let my_wonderful = MyWonderfulWorld::default();

    Server::builder()
        .add_service(GreeterServer::new(greeter))
        .add_service(WonderfulServer::new(my_wonderful))
        .serve(addr)
        .await?;

    Ok(())
}

Solution

  • You can try something like this

    pub struct SharedState {
        name: String,
        age: u8,
    }
    
    #[derive(Debug, Default)]
    pub struct MyGreeter {
        shared_state: std::sync::Arc<std::sync::Mutex<SharedState>>,
    }
    
    #[derive(Debug, Default)]
    pub struct MyWonderfulWorld {
        shared_state: std::sync::Arc<std::sync::Mutex<SharedState>>,
    }
    
    use tonic::{transport::Server, Request, Response, Status};
    
    use hello_wonderful_world::greeter_server::{Greeter, GreeterServer};
    use hello_wonderful_world::{HelloReply, HelloRequest};
    
    use hello_wonderful_world::wonderful_server::{Wonderful, WonderfulServer};
    use hello_wonderful_world::{WonderfulRequest, WonderfulReply};
    
    pub mod hello_wonderful_world {
        tonic::include_proto!("helloworld");
        tonic::include_proto!("wonderfulworld");
    }
    
    #[tonic::async_trait]
    impl Greeter for MyGreeter {
        async fn say_hello(
            &self,
            request: Request<HelloRequest>, // Accept request of type HelloRequest
        ) -> Result<Response<HelloReply>, Status> { // Return an instance of type HelloReply
            println!("Got a request: {:?}", request);
    
            // here get the shared state and lock it
            if let Ok(shared_state) = self.shared_state.lock()
            {
                println!("Got the lock");
                println!("Name: {}", shared_state.name);
                println!("Age: {}", shared_state.age);
            }
    
            let reply = hello_wonderful_world::HelloReply {
                message: format!("Hello {}!", request.into_inner().name).into(), // We must use .into_inner() as the fields of gRPC requests and responses are private
            };
    
            Ok(Response::new(reply)) // Send back our formatted greeting
        }
    }
    
    #[tonic::async_trait]
    impl Wonderful for MyWonderfulWorld {
        async fn its_a_wonderful_world(
            &self,
            request: Request<WonderfulRequest>)
            -> Result<Response<WonderfulReply>, Status> {
    
            // here get the shared state and lock it
            if let Ok(shared_state) = self.shared_state.lock()
            {
                println!("Got the lock");
                println!("Name: {}", shared_state.name);
                println!("Age: {}", shared_state.age);
            }
    
            let reply = hello_wonderful_world::WonderfulReply {
                message: format!("And I said to {}: What a Wonderful World", request.into_inner().name),
            };
    
            Ok(Response::new(reply))
        }
    }
    
    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let addr = "[::1]:50051".parse()?;
        
        let shared_state = std::sync::Arc::new(std::sync::Mutex::new(SharedState {
            name: "John".to_string(),
            age: 32,
        }));
        
        let greeter = MyGreeter {
            shared_state: shared_state.clone(),
        };
        
        let my_wonderful = MyWonderfulWorld {
            shared_state: shared_state.clone(),
        };
    
        Server::builder()
            .add_service(GreeterServer::new(greeter))
            .add_service(WonderfulServer::new(my_wonderful))
            .serve(addr)
            .await?;
    
        Ok(())
    }
    

    I used std::sync::Mutex but you maybe can use std::sync::RwLock.