1use std::{
47 future::Future,
48 sync::atomic::{
49 AtomicUsize,
50 Ordering::{AcqRel, Acquire},
51 },
52};
53
54use crossbeam_queue::ArrayQueue;
55
56use crate::{
57 manage::{Detach, Manage},
58 slot::SlotMeta,
59};
60
61pub enum Parked<M: Manage> {
63 Accepted,
65 Refused(M::Connection, SlotMeta),
68 Destroyed,
71}
72
73pub enum Unparked<M: Manage> {
75 Claimed(M::Connection, SlotMeta),
77 Empty,
79 Lost,
83}
84
85pub trait Exchange<M: Manage>: Send + Sync + 'static {
87 fn park(&self, conn: M::Connection, meta: SlotMeta) -> Parked<M>;
89
90 fn unpark(&self) -> impl Future<Output = Unparked<M>>;
93
94 fn parked(&self) -> u64;
96
97 fn clear(&self) {}
99}
100
101#[derive(Debug, Default, Clone, Copy)]
103pub struct NoExchange;
104
105impl<M: Manage> Exchange<M> for NoExchange {
106 fn park(&self, conn: M::Connection, meta: SlotMeta) -> Parked<M> {
107 Parked::Refused(conn, meta)
108 }
109
110 async fn unpark(&self) -> Unparked<M> {
111 Unparked::Empty
112 }
113
114 fn parked(&self) -> u64 {
115 0
116 }
117}
118
119struct Entry<P> {
120 parked: P,
121 meta: SlotMeta,
122}
123
124pub struct Reservoir<M: Detach> {
138 queue: ArrayQueue<Entry<M::Parked>>,
139 admitted: AtomicUsize,
145}
146
147impl<M: Detach> Reservoir<M> {
148 pub fn new(capacity: usize) -> Self {
150 assert!(capacity > 0, "reservoir capacity must be greater than zero");
151 Self {
152 queue: ArrayQueue::new(capacity),
153 admitted: AtomicUsize::new(0),
154 }
155 }
156
157 pub fn capacity(&self) -> usize {
159 self.queue.capacity()
160 }
161
162 pub fn len(&self) -> usize {
164 self.queue.len()
165 }
166
167 pub fn is_empty(&self) -> bool {
169 self.queue.is_empty()
170 }
171
172 fn admit(&self) -> bool {
174 self.admitted
175 .fetch_update(AcqRel, Acquire, |n| {
176 (n < self.queue.capacity()).then_some(n + 1)
177 })
178 .is_ok()
179 }
180
181 fn readmit(&self) {
183 self.admitted.fetch_sub(1, AcqRel);
184 }
185}
186
187impl<M: Detach> Default for Reservoir<M> {
188 fn default() -> Self {
189 Self::new(64)
190 }
191}
192
193impl<M: Detach> std::fmt::Debug for Reservoir<M> {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 f.debug_struct("Reservoir")
196 .field("capacity", &self.queue.capacity())
197 .field("len", &self.queue.len())
198 .finish()
199 }
200}
201
202impl<M: Detach> Exchange<M> for Reservoir<M> {
203 fn park(&self, conn: M::Connection, meta: SlotMeta) -> Parked<M> {
204 if !self.admit() {
207 return Parked::Refused(conn, meta);
208 }
209
210 let Some(parked) = M::detach(conn) else {
211 self.readmit();
214 return Parked::Destroyed;
215 };
216
217 if self.queue.push(Entry { parked, meta }).is_err() {
218 self.readmit();
223 return Parked::Destroyed;
224 }
225 Parked::Accepted
226 }
227
228 async fn unpark(&self) -> Unparked<M> {
229 let Some(entry) = self.queue.pop() else {
230 return Unparked::Empty;
231 };
232 self.readmit();
233
234 match M::attach(entry.parked).await {
238 Ok(conn) => Unparked::Claimed(conn, entry.meta),
239 Err(_) => Unparked::Lost,
240 }
241 }
242
243 fn parked(&self) -> u64 {
244 self.queue.len() as u64
245 }
246
247 fn clear(&self) {
248 while self.queue.pop().is_some() {
252 self.readmit();
253 }
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use std::{
260 pin::pin,
261 sync::atomic::Ordering::SeqCst,
262 task::{Context, Poll, Waker},
263 };
264
265 use super::*;
266 use crate::test_support::{CAN_ATTACH, CAN_DETACH, MovableConn, MovableManager, detach_lock};
267
268 fn drive<F: Future>(fut: F) -> F::Output {
272 let mut fut = pin!(fut);
273 match fut.as_mut().poll(&mut Context::from_waker(Waker::noop())) {
274 Poll::Ready(v) => v,
275 Poll::Pending => panic!("an exchange future must not yield"),
276 }
277 }
278
279 fn meta(generation: u64, uses: u64) -> SlotMeta {
280 let mut m = SlotMeta::new(generation);
281 m.uses = uses;
282 m
283 }
284
285 fn reservoir(capacity: usize) -> Reservoir<MovableManager> {
286 Reservoir::new(capacity)
287 }
288
289 #[test]
292 fn no_exchange_refuses_and_hands_the_connection_straight_back() {
293 let x = NoExchange;
294 match Exchange::<MovableManager>::park(&x, MovableConn::new(7), meta(3, 5)) {
295 Parked::Refused(conn, meta) => {
296 assert_eq!(conn.id, 7, "the caller keeps its connection");
297 assert_eq!(meta.generation, 3);
298 assert_eq!(meta.uses, 5);
299 }
300 _ => panic!("NoExchange must never take ownership"),
301 }
302 }
303
304 #[test]
305 fn no_exchange_never_produces_a_connection() {
306 let x = NoExchange;
307 assert!(matches!(
308 drive(Exchange::<MovableManager>::unpark(&x)),
309 Unparked::Empty
310 ));
311 assert_eq!(Exchange::<MovableManager>::parked(&x), 0);
312 }
313
314 #[test]
315 fn no_exchange_clear_is_the_default_no_op() {
316 Exchange::<MovableManager>::clear(&NoExchange);
319 }
320
321 #[test]
322 fn no_exchange_is_zero_sized_and_derives_the_usual_traits() {
323 fn assert_traits<T: Copy + Clone + Default + std::fmt::Debug>() {}
324 assert_traits::<NoExchange>();
325
326 assert_eq!(std::mem::size_of::<NoExchange>(), 0);
329 assert_eq!(format!("{NoExchange:?}"), "NoExchange");
330 }
331
332 #[test]
335 fn a_new_reservoir_is_empty() {
336 let r = reservoir(4);
337 assert_eq!(r.capacity(), 4);
338 assert_eq!(r.len(), 0);
339 assert!(r.is_empty());
340 assert_eq!(Exchange::<MovableManager>::parked(&r), 0);
341 }
342
343 #[test]
344 #[should_panic(expected = "reservoir capacity must be greater than zero")]
345 fn a_zero_capacity_reservoir_panics() {
346 let _ = reservoir(0);
349 }
350
351 #[test]
352 fn the_default_reservoir_holds_sixty_four() {
353 assert_eq!(Reservoir::<MovableManager>::default().capacity(), 64);
354 }
355
356 #[test]
357 fn debug_reports_capacity_and_occupancy() {
358 let _lock = detach_lock();
359 let r = reservoir(4);
360 r.park(MovableConn::new(0), meta(0, 0));
361 let s = format!("{r:?}");
362 assert!(s.contains("capacity: 4"), "got {s}");
363 assert!(s.contains("len: 1"), "got {s}");
364 }
365
366 #[test]
369 fn parking_takes_ownership() {
370 let _lock = detach_lock();
371 let r = reservoir(2);
372
373 assert!(matches!(
374 r.park(MovableConn::new(1), meta(0, 0)),
375 Parked::Accepted
376 ));
377 assert_eq!(r.len(), 1);
378 assert!(!r.is_empty());
379 assert_eq!(Exchange::<MovableManager>::parked(&r), 1);
380 }
381
382 #[test]
383 fn a_full_reservoir_refuses_without_detaching() {
384 let _lock = detach_lock();
385 let r = reservoir(2);
386 r.park(MovableConn::new(0), meta(0, 0));
387 r.park(MovableConn::new(1), meta(0, 0));
388
389 match r.park(MovableConn::new(2), meta(9, 4)) {
392 Parked::Refused(conn, meta) => {
393 assert_eq!(conn.id, 2);
394 assert_eq!((meta.generation, meta.uses), (9, 4));
395 }
396 _ => panic!("a full reservoir must refuse"),
397 }
398 assert_eq!(r.len(), 2, "and must not grow past capacity");
399 }
400
401 #[test]
405 fn a_connection_that_cannot_detach_is_reported_destroyed() {
406 let _lock = detach_lock();
407 let r = reservoir(2);
408 CAN_DETACH.store(false, SeqCst);
409
410 assert!(matches!(
411 r.park(MovableConn::new(0), meta(0, 0)),
412 Parked::Destroyed
413 ));
414 assert_eq!(r.len(), 0, "nothing was queued");
415
416 CAN_DETACH.store(true, SeqCst);
419 r.park(MovableConn::new(1), meta(0, 0));
420 r.park(MovableConn::new(2), meta(0, 0));
421 assert_eq!(r.len(), 2, "failed detaches must not consume capacity");
422 }
423
424 #[test]
427 fn claiming_an_empty_reservoir_tells_the_caller_to_dial() {
428 let r = reservoir(2);
429 assert!(matches!(
430 drive(Exchange::<MovableManager>::unpark(&r)),
431 Unparked::Empty
432 ));
433 }
434
435 #[test]
436 fn a_claimed_connection_keeps_its_identity_and_metadata() {
437 let _lock = detach_lock();
438 let r = reservoir(2);
439 r.park(MovableConn::new(42), meta(3, 8));
440
441 match drive(Exchange::<MovableManager>::unpark(&r)) {
442 Unparked::Claimed(conn, meta) => {
443 assert_eq!(conn.id, 42, "the same socket, re-wrapped");
444 assert_eq!(
445 (meta.generation, meta.uses),
446 (3, 8),
447 "lifecycle bookkeeping survives the trip"
448 );
449 }
450 _ => panic!("the parked connection should have been claimed"),
451 }
452 assert!(r.is_empty());
453 }
454
455 #[test]
458 fn claims_are_first_in_first_out() {
459 let _lock = detach_lock();
460 let r = reservoir(4);
461 for id in 0..3 {
462 r.park(MovableConn::new(id), meta(0, 0));
463 }
464
465 let mut order = Vec::new();
466 while let Unparked::Claimed(conn, _) = drive(Exchange::<MovableManager>::unpark(&r)) {
467 order.push(conn.id);
468 }
469 assert_eq!(order, vec![0, 1, 2]);
470 }
471
472 #[test]
473 fn a_connection_that_cannot_reattach_is_reported_lost() {
474 let _lock = detach_lock();
475 let r = reservoir(2);
476 r.park(MovableConn::new(0), meta(0, 0));
477 CAN_ATTACH.store(false, SeqCst);
478
479 assert!(matches!(
480 drive(Exchange::<MovableManager>::unpark(&r)),
481 Unparked::Lost
482 ));
483 assert!(r.is_empty(), "it was popped, not left behind");
484 }
485
486 #[test]
489 fn claiming_frees_capacity_for_a_later_park() {
490 let _lock = detach_lock();
491 let r = reservoir(1);
492 assert!(matches!(
493 r.park(MovableConn::new(0), meta(0, 0)),
494 Parked::Accepted
495 ));
496 assert!(matches!(
497 r.park(MovableConn::new(1), meta(0, 0)),
498 Parked::Refused(..)
499 ));
500
501 drive(Exchange::<MovableManager>::unpark(&r));
502 assert!(
503 matches!(r.park(MovableConn::new(2), meta(0, 0)), Parked::Accepted),
504 "the slot freed by the claim must be reusable"
505 );
506 }
507
508 #[test]
509 fn a_lost_connection_also_frees_its_capacity() {
510 let _lock = detach_lock();
511 let r = reservoir(1);
512 r.park(MovableConn::new(0), meta(0, 0));
513 CAN_ATTACH.store(false, SeqCst);
514 assert!(matches!(
515 drive(Exchange::<MovableManager>::unpark(&r)),
516 Unparked::Lost
517 ));
518
519 CAN_ATTACH.store(true, SeqCst);
520 assert!(matches!(
521 r.park(MovableConn::new(1), meta(0, 0)),
522 Parked::Accepted
523 ));
524 }
525
526 #[test]
529 fn clear_drops_everything_and_frees_all_capacity() {
530 let _lock = detach_lock();
531 let r = reservoir(2);
532 r.park(MovableConn::new(0), meta(0, 0));
533 r.park(MovableConn::new(1), meta(0, 0));
534
535 Exchange::<MovableManager>::clear(&r);
536
537 assert_eq!(r.len(), 0);
538 assert!(r.is_empty());
539 assert_eq!(Exchange::<MovableManager>::parked(&r), 0);
540 assert!(matches!(
543 r.park(MovableConn::new(2), meta(0, 0)),
544 Parked::Accepted
545 ));
546 assert!(matches!(
547 r.park(MovableConn::new(3), meta(0, 0)),
548 Parked::Accepted
549 ));
550 }
551
552 #[test]
553 fn clearing_an_empty_reservoir_is_harmless() {
554 let r = reservoir(2);
555 Exchange::<MovableManager>::clear(&r);
556 assert!(r.is_empty());
557 }
558
559 #[test]
562 fn admission_never_exceeds_capacity() {
563 let _lock = detach_lock();
564 let r = reservoir(3);
565 for id in 0..3 {
566 assert!(matches!(
567 r.park(MovableConn::new(id), meta(0, 0)),
568 Parked::Accepted
569 ));
570 }
571 for id in 3..6 {
572 assert!(matches!(
573 r.park(MovableConn::new(id), meta(0, 0)),
574 Parked::Refused(..)
575 ));
576 }
577 assert_eq!(r.len(), 3);
578 assert!(r.len() <= r.capacity());
579 }
580}