`Weak` in a `HashMap` without periodic cleaning?

I'm working on a small client-server application where clients can create or join a Room created by another client, the server is fully async (with async-std).

To achieve this I've created a State struct:

use uuid::Uuid;
use std::{collections::HashMap, sync::{Arc, Weak}};
use async_std::sync::RwLock;

struct Room {
    // --snip--
}

struct State {
    rooms: RwLock<HashMap<Uuid, Weak<Room>>>,
    // --snip--
}

I wrapped the map in an RwLock because I need mutability to add or remove rooms, and I also wrapped the Room in a Weak because the rooms should be dropped once all clients (each one owns an Arc<Room>) disconnect. A State is created on startup and wrapped in an Arc because I handle new connections like this: async_std::task::spawn(handle_stream(&state, stream, ..)).

The problem is that I need to periodically iterate over the hash_map to remove the Weak that can't be upgraded.

Is there any way to store a Weak<Room> without periodic cleaning ?
What do you think of this approach ? How would you have done it ?

You could do it like this:

  • Store the Rooms directly in the HashMap
  • Don't give pointers to Room to clients, only their Uuid. Make them look up the Room in the HashMap on the fly if they need it.
  • Wrap the Uuid into an RAII newtype and wrap the RAII newtype into an Rc before handing it out.
  • In UuidNewtype::drop(), remove the corresponding entry from the hash map.
    This will in turn require the newtype to hold a reference to the hash map; you can do this by putting the hash map into an Arc, and making its key type a raw Uuid like now, of which the handed-out UuidNewtype handle holds a copy.
1 Like

Thanks for your answer,
I had something along what you described but without the RAII newtype, I changed it to the Arc / Weak because it seemed more logical, each client knows for sure that the room exist.
Having to check if the room still exist for every operation the client makes and handling rooms that no longer exists is a real pain.

Edit: Also dropping the value from UuidNewtype::drop() means that I would need to hold a mut ref to the hashmap inside it.

That's exactly what I was saying, isn't it? Hence my recommendation of putting it into an Arc<RwLock<_>>.

But you don't have to, that's the whole point. You can just assume it does exist as long as you access it through the Uuid newtype handle, because it's only removed once every handle is dropped.

2 Likes

Oooh, I see what you mean. Thank you !