0

I'm trying to write a small webapp communicating with a client (aka Slave in this code) over gRPC (using Tonic) and I'm getting stuck almost at the very beginning. I want the slave to be able to self-register by invoking the register() function over gRPC. The request at the moment contains only the mac_address (due to lack of any better unique device identifier I'm aware of).

The juicy part of the code looks as follows

pub struct SlaveServer {
    slave_repo: Arc<SlaveRepository>
}

#[tonic::async_trait]
impl SlaveManager for SlaveServer {
    async fn register(&self, request : Request<RegistrationRequest>) -> Result<Response<RegistrationResponse>, Status> {
        let req: RegistrationRequest = request.into_inner();

        (*self.slave_repo).add_slave(
            Slave {
                mac_address: req.mac_address.clone()
            }
        );

        println!("New slave: {}", req.mac_address);
        let response = RegistrationResponse { slave_id: "new_id".to_string() };
        return Ok(Response::new(response))
    }
}

unfortunately however it looks like I cannot invoke add_server. It currently gives me the following error:

trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<SlaveRepository>`

It is my guess that an easy way to go around it would be to write register(&mut self,...), but that is not something I can even attempt, given that the SlaveManager is auto-generated from a proto file and that one doesn't use the mut.

I originally used Box<SlaveRepository>, but that didn't seem to work either, plus I suppose it wasn't suitable since the SlaveRepository will be used in several different places.

Kamil Janowski
  • 1,872
  • 2
  • 21
  • 43

1 Answers1

2

The Arc<T> smart pointer gives you read-only access to its pointee. So you cannot modify something which is behind an Arc, at least not directly.

Given that you are using Arc which is a thread-safe reference counted pointer, I assume that this would be used across threads. In that case you should wrap your SlaveRepository in some synchronization primitive, such as Mutex<T> or RWLock<T> (note that in async context you should use the async versions of those primitives, instead of the ones from std):

pub struct SlaveServer {
    slave_repo: Arc<RWLock<SlaveRepository>>
}
        // skipped for brevity
        let mut lock_guard = self.slave_repo
            .write()
            .await;

        lock_quard.add_slave(
            Slave {
                mac_address: req.mac_address.clone()
            }
        );

This is called the interior mutability pattern

Svetlin Zarev
  • 14,713
  • 4
  • 53
  • 82
  • While we are on it, as OP might be using tokio maybe we should make it clear that Mutex and RwLock also come from tokio. Using std library mutex might create some issues. – Shirshak55 Mar 20 '22 at 15:20
  • 1
    You are right, I completely missed that it's an `async` function. I'll update the answer. Thank you! – Svetlin Zarev Mar 20 '22 at 15:30
  • as a matter of fact I am using Tokio, mostly because the Rust gRPC tutorials recommend it. This solution works great! Thank you so much! I tried using `Arc>` as well, but couldn't get it to work either. Perhaps it's because i tried to add the slave with `(*(*self.slave_repo)).add_slave()` instead of doing the `write().await` Man, I have worked with many languages before, but none has been as challenging as this one. Maybe it is safer than c++, but boy is c++ easier :D – Kamil Janowski Mar 21 '22 at 01:44
  • so what's the deal with the std RwLock and Mutex? Are they generally flawed, or is tokio just a bad combo when using the std stuff? – Kamil Janowski Mar 21 '22 at 01:46
  • now that you mentioned this, I found this code sample https://github.com/marshal-shi/tonic-grpc-web-chat/blob/main/backend/src/server.rs where they do exactly same thing... heh... hours have been wasted... – Kamil Janowski Mar 21 '22 at 02:31
  • 1
    @KamilJanowski `RefCell` is only for single threaded usage. If you need to use it across threads, then you should use `Mutex` or `RWLock`. Basically the combinations are `Rc>` and `Arc>` or `Arc>>`. Of course you can use mutex and rwlock with `Rc` but it does not make sense, because they are "heavier" constructs that do not have any added value for a single threaded context. – Svetlin Zarev Mar 21 '22 at 08:33
  • 1
    The stdlib `Mutex` and `RwLock` are fine in and of themselves, but they don't work well with Tokio because they block the current thread so they would prevent Tokio from running any other task on the same thread while the first task is waiting. The Tokio versions must be awaited, which frees the thread for other work in the meantime. – Jmb Mar 21 '22 at 09:16
  • this is great! than you all for the detailed explanation – Kamil Janowski Mar 21 '22 at 13:18