atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
159f0998263bdbc45fc96bfc40453947b3a44cc3
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/http/registry.rsRust77 lines2.4 KB
1
//! The live runs. The table is only locked long enough to find a run, and each run has its own
2
//! lock, so one slow turn never holds up anyone else's.
3
4
use std::{
5
    collections::HashMap,
6
    sync::{Arc, Mutex, MutexGuard, PoisonError, RwLock, TryLockError},
7
    time::{Duration, Instant},
8
};
9
10
use crate::game::Run;
11
12
pub type Slot = Arc<Mutex<Run>>;
13
14
#[derive(Default)]
15
pub struct Registry {
16
    runs: RwLock<HashMap<String, Slot>>,
17
}
18
19
/// A panic mid-request must not take the run down with it, so a poisoned lock is recovered.
20
pub fn hold(slot: &Slot) -> MutexGuard<'_, Run> {
21
    slot.lock().unwrap_or_else(PoisonError::into_inner)
22
}
23
24
impl Registry {
25
    pub fn get(&self, id: &str) -> Option<Slot> {
26
        self.read().get(id).cloned()
27
    }
28
29
    pub fn len(&self) -> usize {
30
        self.read().len()
31
    }
32
33
    pub fn insert(&self, run: Run) {
34
        let id = run.id.clone();
35
        self.write().insert(id, Arc::new(Mutex::new(run)));
36
    }
37
38
    pub fn remove(&self, id: &str) {
39
        self.write().remove(id);
40
    }
41
42
    pub fn contains(&self, id: &str) -> bool {
43
        self.read().contains_key(id)
44
    }
45
46
    /// Every live run, each visited under its own lock.
47
    pub fn each<T>(&self, mut visit: impl FnMut(&Run) -> T) -> Vec<T> {
48
        let slots: Vec<Slot> = self.read().values().cloned().collect();
49
        slots.iter().map(|slot| visit(&hold(slot))).collect()
50
    }
51
52
    /// Takes out the runs nobody has touched for `ttl`, and hands them back for the record.
53
    pub fn evict_idle(&self, now: Instant, ttl: Duration) -> Vec<Slot> {
54
        let mut runs = self.write();
55
        let idle: Vec<String> = runs
56
            .iter()
57
            .filter(|(_, slot)| {
58
                let idle = |run: &Run| now.duration_since(run.last_seen) >= ttl;
59
                match slot.try_lock() {
60
                    Ok(run) => idle(&run),
61
                    Err(TryLockError::Poisoned(poisoned)) => idle(&poisoned.into_inner()),
62
                    Err(TryLockError::WouldBlock) => false,
63
                }
64
            })
65
            .map(|(id, _)| id.clone())
66
            .collect();
67
        idle.iter().filter_map(|id| runs.remove(id)).collect()
68
    }
69
70
    fn read(&self) -> std::sync::RwLockReadGuard<'_, HashMap<String, Slot>> {
71
        self.runs.read().unwrap_or_else(PoisonError::into_inner)
72
    }
73
74
    fn write(&self) -> std::sync::RwLockWriteGuard<'_, HashMap<String, Slot>> {
75
        self.runs.write().unwrap_or_else(PoisonError::into_inner)
76
    }
77
}