rustev_remote_http/
seam_queue.rs1use std::sync::Arc;
12use std::sync::atomic::{AtomicU64, Ordering};
13
14use tokio::sync::{OwnedSemaphorePermit, Semaphore};
15use tokio::time::Instant;
16
17pub const MAX_SEAM_LIMIT: u32 = 65_536;
19
20pub const QUEUE_FULL: &str = "remote_seam_queue_full";
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
25pub struct SeamCounters {
26 pub depth: u64,
28 pub refused: u64,
30 pub late_responses_discarded: u64,
32}
33
34#[derive(Default)]
35struct Counters {
36 refused: AtomicU64,
37 late: AtomicU64,
38}
39
40#[derive(Clone)]
42pub struct SeamQueue {
43 active: Arc<Semaphore>,
44 slots: Arc<Semaphore>,
45 total: usize,
46 counters: Arc<Counters>,
47}
48
49impl std::fmt::Debug for SeamQueue {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 f.debug_struct("SeamQueue")
52 .field("counters", &self.counters())
53 .finish()
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub struct QueueFull;
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct QueueDeadline;
64
65pub struct Slot {
67 slot: OwnedSemaphorePermit,
68 active: Arc<Semaphore>,
69}
70
71pub struct Running {
73 _slot: OwnedSemaphorePermit,
74 _active: OwnedSemaphorePermit,
75}
76
77impl SeamQueue {
78 pub fn new(in_flight: u32, capacity: u32) -> Result<Self, String> {
80 for (name, value) in [("in-flight limit", in_flight), ("queue capacity", capacity)] {
81 if !(1..=MAX_SEAM_LIMIT).contains(&value) {
82 return Err(format!("{name} must be 1 to {MAX_SEAM_LIMIT}"));
83 }
84 }
85 let total = (in_flight + capacity) as usize;
86 Ok(SeamQueue {
87 active: Arc::new(Semaphore::new(in_flight as usize)),
88 slots: Arc::new(Semaphore::new(total)),
89 total,
90 counters: Arc::default(),
91 })
92 }
93
94 pub fn try_enter(&self) -> Result<Slot, QueueFull> {
96 match self.slots.clone().try_acquire_owned() {
97 Ok(slot) => Ok(Slot {
98 slot,
99 active: self.active.clone(),
100 }),
101 Err(_) => {
102 self.counters.refused.fetch_add(1, Ordering::Relaxed);
103 Err(QueueFull)
104 }
105 }
106 }
107
108 pub fn count_late(&self) {
110 self.counters.late.fetch_add(1, Ordering::Relaxed);
111 }
112
113 pub fn counters(&self) -> SeamCounters {
114 SeamCounters {
115 depth: (self.total - self.slots.available_permits()) as u64,
116 refused: self.counters.refused.load(Ordering::Relaxed),
117 late_responses_discarded: self.counters.late.load(Ordering::Relaxed),
118 }
119 }
120}
121
122impl Slot {
123 pub async fn start(self, deadline: Instant) -> Result<Running, QueueDeadline> {
125 match tokio::time::timeout_at(deadline, self.active.clone().acquire_owned()).await {
126 Ok(Ok(active)) => Ok(Running {
127 _slot: self.slot,
128 _active: active,
129 }),
130 _ => Err(QueueDeadline),
131 }
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use std::time::Duration;
139
140 #[test]
141 fn limits_are_declared_and_bounded() {
142 assert!(SeamQueue::new(0, 1).is_err());
143 assert!(SeamQueue::new(1, 0).is_err());
144 assert!(SeamQueue::new(MAX_SEAM_LIMIT + 1, 1).is_err());
145 assert!(SeamQueue::new(1, MAX_SEAM_LIMIT + 1).is_err());
146 assert!(SeamQueue::new(MAX_SEAM_LIMIT, MAX_SEAM_LIMIT).is_ok());
147 }
148
149 #[tokio::test(start_paused = true)]
150 async fn a_full_queue_refuses_and_a_queued_call_honours_its_deadline() {
151 let q = SeamQueue::new(1, 1).unwrap();
152 let running = q.try_enter().unwrap();
153 let running = running.start(Instant::now()).await.unwrap();
154 let queued = q.try_enter().unwrap();
155 assert_eq!(q.try_enter().err(), Some(QueueFull));
156 assert_eq!(
157 q.counters(),
158 SeamCounters {
159 depth: 2,
160 refused: 1,
161 late_responses_discarded: 0
162 }
163 );
164 let deadline = Instant::now() + Duration::from_millis(5);
165 assert_eq!(queued.start(deadline).await.err(), Some(QueueDeadline));
166 assert_eq!(q.counters().depth, 1);
167 drop(running);
168 assert_eq!(q.counters().depth, 0);
169 q.count_late();
170 assert_eq!(q.counters().late_responses_discarded, 1);
171 }
172}