mumble_server_runtime_shard/
queue.rs1use std::sync::atomic::{AtomicBool, Ordering};
16
17use mumble_server_runtime_protocol::ControlMessage;
18use thiserror::Error;
19use tokio::sync::mpsc;
20
21pub const CAPACITY: usize = 1024;
23
24pub const MAX_DEPTH_FOR_VOICE: usize = 64;
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum VoiceAdmission {
44 Accepted,
45 Dropped,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
52pub enum Refused {
53 #[error("output queue has {free} free slots, {needed} were needed")]
57 Congested { needed: usize, free: usize },
58
59 #[error("transition of {needed} messages exceeds the queue capacity of {capacity}")]
63 TooLarge { needed: usize, capacity: usize },
64
65 #[error("the connection's writer has ended")]
67 Closed,
68}
69
70#[derive(Debug)]
72pub struct OutboundQueue {
73 sender: mpsc::Sender<ControlMessage>,
74 must_close: AtomicBool,
77}
78
79impl OutboundQueue {
80 #[must_use]
82 pub fn new() -> (OutboundQueue, mpsc::Receiver<ControlMessage>) {
83 Self::with_capacity(CAPACITY)
84 }
85
86 #[must_use]
89 pub fn with_capacity(capacity: usize) -> (OutboundQueue, mpsc::Receiver<ControlMessage>) {
90 let (sender, receiver) = mpsc::channel(capacity.max(1));
91 (
92 OutboundQueue {
93 sender,
94 must_close: AtomicBool::new(false),
95 },
96 receiver,
97 )
98 }
99
100 pub fn try_send_all(&self, messages: Vec<ControlMessage>) -> Result<(), Refused> {
111 let needed = messages.len();
112 if needed == 0 {
113 return Ok(());
114 }
115
116 let capacity = self.sender.max_capacity();
117 if needed > capacity {
118 self.must_close.store(true, Ordering::Relaxed);
119 return Err(Refused::TooLarge { needed, capacity });
120 }
121
122 let mut permits = Vec::with_capacity(needed);
123 for _ in 0..needed {
124 match self.sender.try_reserve() {
125 Ok(permit) => permits.push(permit),
126 Err(mpsc::error::TrySendError::Full(())) => {
127 return Err(Refused::Congested {
130 needed,
131 free: permits.len(),
132 });
133 }
134 Err(mpsc::error::TrySendError::Closed(())) => {
135 self.must_close.store(true, Ordering::Relaxed);
136 return Err(Refused::Closed);
137 }
138 }
139 }
140
141 for (permit, message) in permits.into_iter().zip(messages) {
142 permit.send(message);
143 }
144 Ok(())
145 }
146
147 pub fn push_voice(&self, message: ControlMessage) -> VoiceAdmission {
154 if self.depth() >= MAX_DEPTH_FOR_VOICE {
155 return VoiceAdmission::Dropped;
156 }
157 match self.sender.try_send(message) {
158 Ok(()) => VoiceAdmission::Accepted,
159 Err(mpsc::error::TrySendError::Full(_)) => VoiceAdmission::Dropped,
160 Err(mpsc::error::TrySendError::Closed(_)) => {
161 self.must_close.store(true, Ordering::Relaxed);
162 VoiceAdmission::Dropped
163 }
164 }
165 }
166
167 #[must_use]
169 pub fn must_close(&self) -> bool {
170 self.must_close.load(Ordering::Relaxed)
171 }
172
173 pub fn mark_fatal(&self) {
175 self.must_close.store(true, Ordering::Relaxed);
176 }
177
178 #[must_use]
180 pub fn depth(&self) -> usize {
181 self.sender
182 .max_capacity()
183 .saturating_sub(self.sender.capacity())
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 #![allow(clippy::expect_used)]
190
191 use super::*;
192 use mumble_server_runtime_protocol::messages::tcp;
193
194 fn message(session: u32) -> ControlMessage {
195 ControlMessage::UserRemove(tcp::UserRemove {
196 session,
197 ..Default::default()
198 })
199 }
200
201 #[tokio::test]
202 async fn an_admitted_transition_arrives_whole_and_in_order() {
203 let (queue, mut receiver) = OutboundQueue::new();
204 queue
205 .try_send_all(vec![message(1), message(2), message(3)])
206 .expect("an empty queue has room");
207
208 let mut received = Vec::new();
209 for _ in 0..3 {
210 received.push(receiver.recv().await.expect("queued message"));
211 }
212 assert_eq!(
213 received,
214 vec![message(1), message(2), message(3)],
215 "plan order is what carries the ordering rules"
216 );
217 }
218
219 #[tokio::test]
220 async fn a_transition_that_does_not_fit_queues_nothing_at_all() {
221 let (queue, mut receiver) = OutboundQueue::with_capacity(4);
222 queue
223 .try_send_all(vec![message(1), message(2)])
224 .expect("room for two");
225 let depth_before = queue.depth();
226
227 let refused = queue
228 .try_send_all(vec![message(3); 3])
229 .expect_err("three messages cannot fit in two slots");
230 assert_eq!(refused, Refused::Congested { needed: 3, free: 2 });
231 assert_eq!(
232 queue.depth(),
233 depth_before,
234 "a refused transition must leave the queue exactly as it was"
235 );
236 assert!(
237 !queue.must_close(),
238 "ordinary backpressure is not a reason to drop the connection"
239 );
240
241 receiver.recv().await.expect("queued message");
242 queue
243 .try_send_all(vec![message(3); 3])
244 .expect("one drained slot is all that was missing");
245 }
246
247 #[test]
248 fn a_transition_larger_than_the_queue_ends_the_connection() {
249 let (queue, _receiver) = OutboundQueue::with_capacity(4);
250 let refused = queue
251 .try_send_all(vec![message(1); 5])
252 .expect_err("it cannot fit, now or ever");
253
254 assert_eq!(
255 refused,
256 Refused::TooLarge {
257 needed: 5,
258 capacity: 4
259 }
260 );
261 assert!(
262 queue.must_close(),
263 "retrying something that can never fit is a livelock, not backpressure"
264 );
265 }
266
267 #[test]
268 fn a_closed_writer_ends_the_connection() {
269 let (queue, receiver) = OutboundQueue::with_capacity(4);
270 drop(receiver);
271
272 assert_eq!(queue.try_send_all(vec![message(1)]), Err(Refused::Closed));
273 assert!(queue.must_close());
274 }
275
276 #[test]
277 fn voice_is_refused_long_before_the_queue_is_full() {
278 let (queue, _receiver) = OutboundQueue::new();
279 for _ in 0..MAX_DEPTH_FOR_VOICE {
280 assert_eq!(queue.push_voice(message(1)), VoiceAdmission::Accepted);
281 }
282
283 assert_eq!(queue.push_voice(message(1)), VoiceAdmission::Dropped);
284 assert!(
285 !queue.must_close(),
286 "dropping late audio is the correct outcome, not a fault"
287 );
288 assert!(
289 queue.depth() < CAPACITY,
290 "the point of the voice bound is that control still has room"
291 );
292 }
293
294 #[test]
295 fn control_still_fits_when_voice_has_been_refused() {
296 let (queue, _receiver) = OutboundQueue::with_capacity(MAX_DEPTH_FOR_VOICE + 8);
297 for _ in 0..MAX_DEPTH_FOR_VOICE {
298 assert_eq!(queue.push_voice(message(1)), VoiceAdmission::Accepted);
299 }
300 assert_eq!(queue.push_voice(message(1)), VoiceAdmission::Dropped);
301
302 assert_eq!(queue.try_send_all(vec![message(2); 8]), Ok(()));
303 }
304
305 #[test]
306 fn an_empty_transition_is_free() {
307 let (queue, _receiver) = OutboundQueue::with_capacity(1);
308 assert_eq!(queue.try_send_all(Vec::new()), Ok(()));
309 assert_eq!(queue.depth(), 0);
310 }
311}