team04_server/server/state/
mod.rs

1use std::collections::BTreeMap;
2use std::sync::Arc;
3use std::{collections::HashMap, time::Instant};
4
5use tokio::sync::{Mutex, MutexGuard};
6use uuid::Uuid;
7
8use crate::config::ConfigSet;
9use crate::lobby::state::{
10    LobbyState, SharedLobbyState,
11    clients::{Clients, PlayerId, ReconnectToken},
12};
13use crate::log::uuid_human_hash::uuid_human_hash;
14use crate::{LOCK_WAIT_TIME_MS, log};
15
16#[derive(Debug)]
17pub struct ServerState {
18    lobbies: HashMap<LobbyId, SharedLobbyState>,
19    /// Player IDs are globally unique, and each id represents a player in a lobby.
20    /// When the lobby is closed, the id remains reserved for some time in case a client attempts
21    /// to reconnect. In this state, the value is `Err(time)`. Once `now > time`, i.e. `time` is in the past,
22    /// a cleanup operation may free the reconnect token to prevent this map from growing infinitely large.
23    player_ids: HashMap<PlayerId, Result<LobbyId, Instant>>,
24    /// Reconnect tokens are globally unique, and each token represents a player in a lobby.
25    /// When the lobby is closed, the token remains reserved for some time in case a client attempts
26    /// to reconnect. In this state, the value is `Err(time)`. Once `now > time`, i.e. `time` is in the past,
27    /// a cleanup operation may free the reconnect token to prevent this map from growing infinitely large.
28    /// This is the same logic as in `player_ids`.
29    reconnect_tokens: HashMap<ReconnectToken, Result<(LobbyId, PlayerId), Instant>>,
30}
31
32#[derive(Debug)]
33pub struct SyncedServerState(Mutex<ServerState>);
34
35impl SyncedServerState {
36    pub fn try_lock(&self) -> Result<MutexGuard<ServerState>, tokio::sync::TryLockError> {
37        self.0.try_lock()
38    }
39    /// NOTE: Trying to lock `SyncedServerState` while holding a `LockedLobbyState`
40    /// may cause a deadlock, because certain processes iterate over and lock all lobby states
41    /// in the server state, meaning these processes hold a server lock until they have gotten
42    /// all lobby locks at least once. If you hold a lobby lock while waiting for a server lock,
43    /// and such a process is holding the server lock, a deadlock will occur.
44    pub async fn lock(&self) -> MutexGuard<ServerState> {
45        let now = tokio::time::Instant::now();
46        let task = tokio::spawn(async move {
47            tokio::time::sleep(tokio::time::Duration::from_millis(LOCK_WAIT_TIME_MS.0)).await;
48            log::info!("[lock] getting server lock took more than {} ms", LOCK_WAIT_TIME_MS.0; &log::pfx());
49            tokio::time::sleep(tokio::time::Duration::from_millis(LOCK_WAIT_TIME_MS.1)).await;
50            let _ondrop = Ondrop(now);
51            log::warning!("[lock] getting server lock took more than {} ms", LOCK_WAIT_TIME_MS.1; &log::pfx());
52            tokio::time::sleep(tokio::time::Duration::MAX).await;
53            struct Ondrop(tokio::time::Instant);
54            impl Drop for Ondrop {
55                fn drop(&mut self) {
56                    log::warning!("[lock] finally got server lock after {} ms", self.0.elapsed().as_millis(); &log::pfx());
57                }
58            }
59        });
60        let lock = self.0.lock().await;
61        task.abort();
62        lock
63    }
64}
65
66impl ServerState {
67    pub fn new(configs: BTreeMap<u64, ConfigSet>) -> Arc<SyncedServerState> {
68        Self::new_internal(configs, true)
69    }
70    #[cfg(test)]
71    pub(crate) fn new_without_task(configs: BTreeMap<u64, ConfigSet>) -> Arc<SyncedServerState> {
72        Self::new_internal(configs, false)
73    }
74    fn new_internal(configs: BTreeMap<u64, ConfigSet>, spawn_task: bool) -> Arc<SyncedServerState> {
75        let selbst = Arc::new(SyncedServerState(Mutex::new(Self {
76            lobbies: HashMap::new(),
77            player_ids: HashMap::new(),
78            reconnect_tokens: HashMap::new(),
79        })));
80        // We could maybe ensure that lobbies appear in the same order as in the
81        // config directory, or have predictable uuids for the config 'numbers'.
82        let mut sperre = selbst
83            .0
84            .try_lock()
85            .expect("noone else should have a lock at this time");
86        for (_, conf) in configs {
87            sperre.add_lobby(conf, Arc::clone(&selbst), spawn_task);
88        }
89        drop(sperre);
90        selbst
91    }
92
93    /// Creates a new lobby from a [ConfigSet]. Also starts the lobby task (see
94    /// [crate::lobby::game::run]).
95    pub fn add_lobby(
96        &mut self,
97        conf: ConfigSet,
98        shared_server_state: Arc<SyncedServerState>,
99        spawn_task: bool,
100    ) {
101        let id = LobbyId::random();
102        let lobby = SharedLobbyState::new(LobbyState::new(
103            id,
104            Clients::new_empty(),
105            conf,
106            shared_server_state,
107        ));
108        if spawn_task {
109            // disabled in tests so we can test individual game
110            // phases instead of the entire game loop.
111            tokio::task::spawn(crate::lobby::game::run(lobby.clone()));
112        }
113        self.lobbies.insert(id, lobby);
114    }
115    pub async fn remove_lobby(&mut self, id: LobbyId) {
116        if let Some(l) = self.lobbies.remove(&id) {
117            let mut lock = l.lock().await;
118            for p in lock.clients.players.players_all_mut() {
119                if let Some(con) = &mut p.con {
120                    con.terminate();
121                }
122            }
123            for p in lock.clients.spectators.iter_mut() {
124                p.con.terminate();
125            }
126        }
127    }
128
129    pub fn lobbies(&self) -> impl Iterator<Item = (&LobbyId, &SharedLobbyState)> {
130        self.lobbies.iter()
131    }
132    pub fn lobby(&self, id: &LobbyId) -> Option<SharedLobbyState> {
133        self.lobbies.get(id).cloned()
134    }
135    pub fn gen_random_player_id_and_reconnect_token(
136        &mut self,
137        lobby_id: LobbyId,
138    ) -> (PlayerId, ReconnectToken) {
139        let mut player_id = PlayerId::random();
140        while self.player_ids.contains_key(&player_id) {
141            player_id = PlayerId::random();
142        }
143        self.player_ids.insert(player_id, Ok(lobby_id));
144
145        let mut token = ReconnectToken::random();
146        while self.reconnect_tokens.contains_key(&token) {
147            token = ReconnectToken::random();
148        }
149        self.reconnect_tokens
150            .insert(token, Ok((lobby_id, player_id)));
151
152        (player_id, token)
153    }
154}
155
156#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
157pub struct LobbyId(Uuid);
158
159impl LobbyId {
160    pub fn new(uuid: Uuid) -> Self {
161        Self(uuid)
162    }
163    pub fn random() -> Self {
164        Self(Uuid::new_v4())
165    }
166    pub fn uuid(&self) -> &Uuid {
167        &self.0
168    }
169}
170
171impl ServerState {
172    pub async fn cleanup(&mut self) {
173        let now = Instant::now();
174        self.reconnect_tokens.retain(|_token, value| match value {
175            Ok((_lobby_id, _player_id)) => true,
176            Err(time) => *time > now,
177        });
178        self.player_ids.retain(|_token, value| match value {
179            Ok(_lobby_id) => true,
180            Err(time) => *time > now,
181        });
182    }
183}
184
185impl std::fmt::Display for LobbyId {
186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187        write!(f, "{} ({})", uuid_human_hash(&self.0), self.0)
188    }
189}
190
191#[cfg(test)]
192mod test {
193    use std::{
194        sync::Arc,
195        time::{Duration, Instant},
196    };
197
198    use tokio::{spawn, time::sleep};
199
200    use crate::lobby::{
201        state::clients::{PlayerId, ReconnectToken},
202        test::get_server_and_lobby,
203    };
204
205    #[tokio::test]
206    async fn cleanup() {
207        let (server, _) = get_server_and_lobby();
208        let server2 = Arc::clone(&server);
209        spawn(async move {
210            let lock_for_a_bit = server2.lock().await;
211            sleep(Duration::from_millis(133)).await;
212            drop(lock_for_a_bit);
213        });
214        sleep(Duration::from_millis(10)).await;
215        let mut lock = server.lock().await;
216        lock.player_ids.clear();
217        lock.player_ids.insert(
218            PlayerId::random(),
219            Err(Instant::now() - Duration::from_secs(60)),
220        );
221        lock.player_ids.insert(
222            PlayerId::random(),
223            Err(Instant::now() - Duration::from_secs(180)),
224        );
225        lock.player_ids.insert(
226            PlayerId::random(),
227            Err(Instant::now() + Duration::from_secs(60)),
228        );
229        lock.reconnect_tokens.clear();
230        lock.reconnect_tokens.insert(
231            ReconnectToken::random(),
232            Err(Instant::now() - Duration::from_secs(60)),
233        );
234        lock.reconnect_tokens.insert(
235            ReconnectToken::random(),
236            Err(Instant::now() + Duration::from_secs(180)),
237        );
238        assert_eq!(lock.player_ids.len(), 3);
239        assert_eq!(lock.reconnect_tokens.len(), 2);
240        lock.cleanup().await;
241        assert_eq!(lock.player_ids.len(), 1);
242        assert_eq!(lock.reconnect_tokens.len(), 1);
243    }
244}