Skip to main content

sim_lib_server/
router.rs

1use std::{
2    collections::VecDeque,
3    sync::{
4        Mutex,
5        atomic::{AtomicU64, Ordering},
6    },
7};
8
9use sim_kernel::{Error, Result};
10
11use crate::ServerFrame;
12
13/// Allocates message ids and queues inbound server frames for a connection.
14#[derive(Default)]
15pub struct FrameRouter {
16    next_id: AtomicU64,
17    inbound: Mutex<VecDeque<ServerFrame>>,
18}
19
20impl FrameRouter {
21    /// Allocates and returns the next unique message id.
22    pub fn fresh_msg_id(&self) -> u64 {
23        self.next_id.fetch_add(1, Ordering::Relaxed) + 1
24    }
25
26    /// Returns the message id the next [`FrameRouter::fresh_msg_id`] call would
27    /// produce, without consuming it.
28    pub fn peek_next_msg_id(&self) -> u64 {
29        self.next_id.load(Ordering::Relaxed) + 1
30    }
31
32    /// Pushes a frame onto the back of the inbound queue.
33    ///
34    /// Returns [`Error::PoisonedLock`] rather than panicking when the queue
35    /// mutex is poisoned, matching the sibling server locks.
36    pub fn push_inbound(&self, frame: ServerFrame) -> Result<()> {
37        self.inbound
38            .lock()
39            .map_err(|_| Error::PoisonedLock("frame router inbound queue"))?
40            .push_back(frame);
41        Ok(())
42    }
43
44    /// Pops the next frame from the front of the inbound queue, if any.
45    ///
46    /// Returns [`Error::PoisonedLock`] rather than panicking when the queue
47    /// mutex is poisoned, matching the sibling server locks.
48    pub fn pop_inbound(&self) -> Result<Option<ServerFrame>> {
49        Ok(self
50            .inbound
51            .lock()
52            .map_err(|_| Error::PoisonedLock("frame router inbound queue"))?
53            .pop_front())
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use std::{sync::Arc, thread};
60
61    use sim_kernel::{Error, Symbol};
62
63    use super::FrameRouter;
64    use crate::{FrameEnvelope, FrameKind, ServerFrame};
65
66    fn poison(router: &Arc<FrameRouter>) {
67        let poisoner = Arc::clone(router);
68        let _ = thread::spawn(move || {
69            let _guard = poisoner.inbound.lock().unwrap();
70            panic!("intentionally poison the inbound queue mutex");
71        })
72        .join();
73    }
74
75    fn frame() -> ServerFrame {
76        ServerFrame::new(
77            Symbol::new("lisp"),
78            FrameKind::Notify,
79            FrameEnvelope::default(),
80            Vec::new(),
81        )
82    }
83
84    #[test]
85    fn push_and_pop_return_poisoned_lock_instead_of_panicking() {
86        let router = Arc::new(FrameRouter::default());
87        poison(&router);
88
89        assert!(matches!(
90            router.push_inbound(frame()),
91            Err(Error::PoisonedLock("frame router inbound queue"))
92        ));
93        assert!(matches!(
94            router.pop_inbound(),
95            Err(Error::PoisonedLock("frame router inbound queue"))
96        ));
97    }
98}