Search code examples
rustrust-tokioserde

How to serialize struct with multiple fields protected by tokio's RwLock?


I use tokio to listen on UDP socket where I send/receive bincode encoded structs and serialize/deserialize them with serde.

How should I approach the serialization of Arc<tokio::sync::RwLock<T>> fields? Serde supports standard library RwLock but I need an async lock.

Data is being changed and transferred over the network fairly often (by design), so it's probably not a good idea to wait for multiple read locks every time I need to send something.

And considering that serde is synchronous, I'd have to spawn blocking tokio tasks in place for every RwLock field. (And every field has RwLock fields of their own). So this will get really slow.

Here's an example struct:

use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;

#[derive(Serialize, Deserialize)]
struct Player {
    // In order to serialize Player I'd have to one by one acquire lock for `meta` and then `stats`, and then some other potential fields.
    pub id: u64,
    pub meta: Arc<RwLock<Metadata>>,
    pub stats: Arc<RwLock<Metadata>>,  
    pub someOtherField1: Arc<RwLock<...>>,
    pub someOtherField2: Arc<RwLock<...>>,
    pub someOtherField3: Arc<RwLock<...>>,
}

What is the Rust way of dealing with this?


Solution

  • Data is being changed and transferred over the network fairly often (by design), so it's probably not a good idea to wait for multiple read locks every time i need to send something.

    Forget performance - this won't be your bottleneck in any sort of game networking (which does appear what you're trying to do).

    The only correct way is to first acquire a read lock on each field before serializing the data, and then serialize them all. If you don't do this you end up with inconsistent states that never logically existed. A player could be alive yet have 0 hitpoints, or be disconnected yet in the game.

    This can lead to deadlocks! This is the difficulty you chose when you started sharing data. There is a very good rule of thumb that can prevent deadlocks if you stick to it religiously, which you have to do throughout your code: any routine that acquires multiple locks must acquire them in the same order. If a new lock is needed but we already have other locks that come later in the order, we must first release all locks before acquiring them all again, in the correct order.

    The easiest way to stick to the above is by having only a single lock for a player.