0

I have a web server in actix-web and recently I built a different C++ server. I decided to implement gRPC as a communication bridge between the 2. But I don't know how to integrate actix web + tonic (invoke gRPC calls in http endpoints). My current solution is: RPCRepository holds all the rpc services and I pass it to app_data as a mutex (because the rpc functions are mutable for some unknown reason). But this approach seems counter intuitive, if I have 1k connections at the same time, why should they have to wait for the mutex to be unlocked? I want to know if my integration is not appropriate or thats the limitation of the actix-web+tonic grpc.

// grpc.rs pub struct RPCClientRepository { pub health_client: HealthCheckPingClient<Channel>, pub suggest_client: SuggesterClient<Channel>, } ... rest ... // main.rs let grpc_ctx = Data::new(Mutex::new(grpc_repo)); println!("[INFO] HTTP Server running at port {}", PORT); HttpServer::new(move || { App::new() .app_data(ctx.clone()) .app_data(grpc_ctx.clone()) .service(activities) }) .bind("0.0.0.0:".to_owned() + PORT)? .run() .await // activities.rs #[get("/activities")] pub async fn activities( req: HttpRequest, ctx: Data<Driver>, rpc_repo: Data<Mutex<RPCClientRepository>>, ) -> impl Responder { let mut unlock = rpc_repo .lock() .unwrap(); let suggestion = unlock .suggest_client .suggest_friends(tonic::Request::new(SuggestRequest { skip: 0, user_id: "".to_string(), })) .await; ... rest ... 
4
  • In broad strokes, this seems to follow the shared mutable state pattern well. But what happens if you get rid of the Mutex around the clients? Which versions of each key dependency are you using? Is rt-multi-thread enabled? Commented Jul 5, 2024 at 13:47
  • @E_net4. I can't get rid of the Mutex because the Data generic is an Arc wrapper inside and when you try to call a method that mutates the inside of an arc (RPCClientRepository in this case), it will complain trait "DerefMut" is required to modify through a dereference error[E0596]. And I don't use multiple threads, so probably rt-multi-thread is disabled. As for the versions, it's set to latest or one of the latest: actix-web = "4.8.0", tonic = "0.11.0", tonic-build = "0.11.0" Commented Jul 5, 2024 at 16:52
  • 1
    Please edit the question with those details. Commented Jul 5, 2024 at 16:59
  • While I don't have the time to formulate an answer right now, this discussion on Github might be helpful. Commented Jul 5, 2024 at 17:01

2 Answers 2

0

From tonic::client (emphasis mine):

Upon using the your generated client, you will discover all the functions corresponding to your rpc methods take &mut self, making concurrent usage of the client difficult. The answer is simply to clone the client, which is cheap as all client instances will share the same channel for communication. For more details, see transport::Channel.

So you do not need a Mutex around your RPCClientRepository; just clone each client when you need them:

let grpc_ctx = Data::new(grpc_repo); 
rpc_repo: Data<RPCClientRepository>, // parameter let mut suggest_client = rpc_repo.suggest_client.clone(); let suggestion = suggest_client.suggest_friends(...).await; 
Sign up to request clarification or add additional context in comments.

Comments

0

After some digging around collecting some more information (hopefully true, please correct me if I am wrong), I think I have found the answer.

Clone the Service(s) you need in EVERY request.

I got told that tonic provides a very efficient clone and so that was the way, instead of pending on 1 mutex.

Code

// main.rs let grpc_ctx = Data::new(grpc_repo); println!("[INFO] HTTP Server running at port {}", PORT); HttpServer::new(move || { App::new() .app_data(ctx.clone()) .app_data(grpc_ctx.clone()) .service(activities) }) .bind("0.0.0.0:".to_owned() + PORT)? .run() .await // activities.rs #[get("/activities")] pub async fn activities( req: HttpRequest, ctx: Data<Driver>, rpc_repo: Data<Mutex<RPCClientRepository>>, ) -> impl Responder { let mut cl = rpc_repo.suggest_client.clone(); let res = cl.suggest_friends(tonic::Request::new(SuggestRequest { skip: 0, user_id: "id_123", })) .await; let data = match res { Ok(response) => response, Err(e) => { println!("[E] error at gRPC Activities: {}", e); return HttpResponse::BadGateway().finish(); } }; println!("c++ classifier returned: {} results",data.into_inner().count); ... rest ... 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.