reddb_server/server/
http_principal_limiter.rs1use std::collections::HashMap;
29use std::sync::atomic::{AtomicU64, Ordering};
30use std::sync::{Arc, Mutex};
31
32#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct PrincipalCapExceeded {
38 pub principal: String,
39 pub limit: usize,
40 pub current: usize,
41}
42
43pub const PRINCIPAL_INFLIGHT_CODE: &str = "principal_inflight_exhausted";
46
47#[derive(Debug)]
48struct Inner {
49 cap: usize,
52 in_use: Mutex<HashMap<String, usize>>,
56 rejected: AtomicU64,
58}
59
60#[derive(Debug)]
64pub struct PrincipalInflightPermit {
65 inner: Arc<Inner>,
66 principal: String,
67}
68
69impl Drop for PrincipalInflightPermit {
70 fn drop(&mut self) {
71 let mut map = self.inner.in_use.lock().expect("principal limiter mutex");
72 if let Some(count) = map.get_mut(&self.principal) {
73 *count -= 1;
74 if *count == 0 {
75 map.remove(&self.principal);
76 }
77 }
78 }
79}
80
81#[derive(Debug, Clone)]
82pub struct PrincipalConnectionLimiter {
83 inner: Arc<Inner>,
84}
85
86impl PrincipalConnectionLimiter {
87 pub fn new(cap: usize) -> Self {
90 Self {
91 inner: Arc::new(Inner {
92 cap,
93 in_use: Mutex::new(HashMap::new()),
94 rejected: AtomicU64::new(0),
95 }),
96 }
97 }
98
99 pub fn cap(&self) -> usize {
101 self.inner.cap
102 }
103
104 pub fn is_enforced(&self) -> bool {
106 self.inner.cap > 0
107 }
108
109 pub fn current_for(&self, principal: &str) -> usize {
112 let map = self.inner.in_use.lock().expect("principal limiter mutex");
113 map.get(principal).copied().unwrap_or(0)
114 }
115
116 pub fn tracked_principals(&self) -> usize {
118 let map = self.inner.in_use.lock().expect("principal limiter mutex");
119 map.len()
120 }
121
122 pub fn rejected_total(&self) -> u64 {
125 self.inner.rejected.load(Ordering::Relaxed)
126 }
127
128 pub fn try_acquire(
134 &self,
135 principal: &str,
136 ) -> Result<PrincipalInflightPermit, PrincipalCapExceeded> {
137 if self.inner.cap == 0 {
138 return Ok(PrincipalInflightPermit {
139 inner: Arc::clone(&self.inner),
140 principal: principal.to_string(),
141 });
142 }
143 let mut map = self.inner.in_use.lock().expect("principal limiter mutex");
144 let count = map.entry(principal.to_string()).or_insert(0);
145 if *count >= self.inner.cap {
146 let current = *count;
147 drop(map);
150 self.inner.rejected.fetch_add(1, Ordering::Relaxed);
151 return Err(PrincipalCapExceeded {
152 principal: principal.to_string(),
153 limit: self.inner.cap,
154 current,
155 });
156 }
157 *count += 1;
158 Ok(PrincipalInflightPermit {
159 inner: Arc::clone(&self.inner),
160 principal: principal.to_string(),
161 })
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use std::sync::atomic::AtomicUsize;
169 use std::thread;
170
171 #[test]
172 fn disabled_cap_admits_everything_without_tracking() {
173 let limiter = PrincipalConnectionLimiter::new(0);
174 assert!(!limiter.is_enforced());
175 let mut permits = Vec::new();
176 for _ in 0..1_000 {
177 permits.push(limiter.try_acquire("alice").expect("disabled admits"));
178 }
179 assert_eq!(limiter.tracked_principals(), 0);
181 assert_eq!(limiter.rejected_total(), 0);
182 }
183
184 #[test]
185 fn admits_up_to_cap_then_refuses_with_structured_detail() {
186 let limiter = PrincipalConnectionLimiter::new(3);
187 let p1 = limiter.try_acquire("alice").expect("slot 1");
188 let p2 = limiter.try_acquire("alice").expect("slot 2");
189 let p3 = limiter.try_acquire("alice").expect("slot 3");
190 assert_eq!(limiter.current_for("alice"), 3);
191
192 let err = limiter.try_acquire("alice").expect_err("over cap");
193 assert_eq!(
194 err,
195 PrincipalCapExceeded {
196 principal: "alice".to_string(),
197 limit: 3,
198 current: 3,
199 }
200 );
201 assert_eq!(limiter.rejected_total(), 1);
202 drop((p1, p2, p3));
203 }
204
205 #[test]
206 fn dropping_a_permit_frees_a_slot() {
207 let limiter = PrincipalConnectionLimiter::new(1);
208 let p = limiter.try_acquire("bob").expect("first slot");
209 assert!(limiter.try_acquire("bob").is_err());
210 drop(p);
211 assert_eq!(limiter.current_for("bob"), 0);
213 assert_eq!(limiter.tracked_principals(), 0);
214 let _p = limiter.try_acquire("bob").expect("reacquire after drop");
215 assert_eq!(limiter.current_for("bob"), 1);
216 }
217
218 #[test]
219 fn principals_are_isolated() {
220 let limiter = PrincipalConnectionLimiter::new(1);
221 let _alice = limiter.try_acquire("alice").expect("alice slot");
222 assert!(limiter.try_acquire("alice").is_err());
224 let _bob = limiter.try_acquire("bob").expect("bob unaffected");
225 assert_eq!(limiter.tracked_principals(), 2);
226 }
227
228 #[test]
229 fn entry_evicted_when_last_permit_drops() {
230 let limiter = PrincipalConnectionLimiter::new(4);
231 let a = limiter.try_acquire("carol").expect("1");
232 let b = limiter.try_acquire("carol").expect("2");
233 assert_eq!(limiter.tracked_principals(), 1);
234 drop(a);
235 assert_eq!(limiter.current_for("carol"), 1);
236 assert_eq!(limiter.tracked_principals(), 1);
237 drop(b);
238 assert_eq!(limiter.tracked_principals(), 0);
239 }
240
241 #[test]
242 fn concurrent_acquire_never_over_issues_per_principal() {
243 let cap = 8;
246 let limiter = PrincipalConnectionLimiter::new(cap);
247 let success = Arc::new(AtomicUsize::new(0));
248 let denied = Arc::new(AtomicUsize::new(0));
249 let held: Arc<Mutex<Vec<PrincipalInflightPermit>>> = Arc::new(Mutex::new(Vec::new()));
250
251 let mut handles = Vec::new();
252 for _ in 0..64 {
253 let l = limiter.clone();
254 let s = Arc::clone(&success);
255 let d = Arc::clone(&denied);
256 let h = Arc::clone(&held);
257 handles.push(thread::spawn(move || match l.try_acquire("storm") {
258 Ok(p) => {
259 s.fetch_add(1, Ordering::Relaxed);
260 h.lock().unwrap().push(p);
261 }
262 Err(_) => {
263 d.fetch_add(1, Ordering::Relaxed);
264 }
265 }));
266 }
267 for handle in handles {
268 handle.join().unwrap();
269 }
270 assert_eq!(success.load(Ordering::Relaxed), cap);
271 assert_eq!(denied.load(Ordering::Relaxed), 64 - cap);
272 assert_eq!(limiter.current_for("storm"), cap);
273 assert_eq!(limiter.rejected_total() as usize, 64 - cap);
274
275 held.lock().unwrap().clear();
276 assert_eq!(limiter.current_for("storm"), 0);
277 }
278
279 #[test]
280 fn clone_shares_state() {
281 let a = PrincipalConnectionLimiter::new(2);
282 let b = a.clone();
283 let _p = a.try_acquire("dave").unwrap();
284 assert_eq!(b.current_for("dave"), 1);
285 assert_eq!(b.cap(), 2);
286 }
287}