atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
56d34031c32175c328592f822e27127d1e792ab2
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/http/registry.rsRust103 lines3.0 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. Nothing ever waits on a run's lock while
3
//! holding the table's, which is what keeps the two from deadlocking.
4
5
use std::{
6
    collections::{HashMap, hash_map::Entry},
7
    sync::{
8
        Arc, Mutex, MutexGuard, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard,
9
        TryLockError,
10
    },
11
    time::{Duration, Instant},
12
};
13
14
use crate::game::Run;
15
16
pub type Slot = Arc<Mutex<Run>>;
17
18
#[derive(Default)]
19
pub struct Registry {
20
    runs: RwLock<HashMap<String, Slot>>,
21
}
22
23
/// A run's lock as it was found. A panic mid-turn poisons it, and that run cannot be trusted.
24
pub enum Lock<'a> {
25
    Held(MutexGuard<'a, Run>),
26
    Poisoned(MutexGuard<'a, Run>),
27
}
28
29
impl<'a> Lock<'a> {
30
    pub fn take(slot: &'a Slot) -> Self {
31
        match slot.lock() {
32
            Ok(run) => Self::Held(run),
33
            Err(poisoned) => Self::Poisoned(poisoned.into_inner()),
34
        }
35
    }
36
}
37
38
fn peek(slot: &Slot) -> Option<MutexGuard<'_, Run>> {
39
    match slot.try_lock() {
40
        Ok(run) => Some(run),
41
        Err(TryLockError::Poisoned(poisoned)) => Some(poisoned.into_inner()),
42
        Err(TryLockError::WouldBlock) => None,
43
    }
44
}
45
46
impl Registry {
47
    pub fn get(&self, id: &str) -> Option<Slot> {
48
        self.read().get(id).cloned()
49
    }
50
51
    pub fn len(&self) -> usize {
52
        self.read().len()
53
    }
54
55
    pub fn contains(&self, id: &str) -> bool {
56
        self.read().contains_key(id)
57
    }
58
59
    /// Refuses to replace a live run, so a colliding id can never evict one.
60
    pub fn insert_new(&self, run: Run) -> bool {
61
        match self.write().entry(run.id().to_owned()) {
62
            Entry::Occupied(_) => false,
63
            Entry::Vacant(slot) => {
64
                slot.insert(Arc::new(Mutex::new(run)));
65
                true
66
            }
67
        }
68
    }
69
70
    pub fn remove(&self, id: &str) {
71
        self.write().remove(id);
72
    }
73
74
    /// Every live run not mid-turn. A busy run is skipped rather than waited for.
75
    pub fn each<T>(&self, mut visit: impl FnMut(&Run) -> T) -> Vec<T> {
76
        let slots: Vec<Slot> = self.read().values().cloned().collect();
77
        slots
78
            .iter()
79
            .filter_map(|slot| peek(slot).map(|run| visit(&run)))
80
            .collect()
81
    }
82
83
    pub fn evict_idle(&self, now: Instant, ttl: Duration) -> Vec<Slot> {
84
        let idle: Vec<String> = self
85
            .read()
86
            .iter()
87
            .filter(|(_, slot)| {
88
                peek(slot).is_some_and(|run| now.duration_since(run.last_seen()) >= ttl)
89
            })
90
            .map(|(id, _)| id.clone())
91
            .collect();
92
        let mut runs = self.write();
93
        idle.iter().filter_map(|id| runs.remove(id)).collect()
94
    }
95
96
    fn read(&self) -> RwLockReadGuard<'_, HashMap<String, Slot>> {
97
        self.runs.read().unwrap_or_else(PoisonError::into_inner)
98
    }
99
100
    fn write(&self) -> RwLockWriteGuard<'_, HashMap<String, Slot>> {
101
        self.runs.write().unwrap_or_else(PoisonError::into_inner)
102
    }
103
}