r402_http/server/
tracker.rs1use std::sync::Arc;
4use std::sync::atomic::{AtomicUsize, Ordering};
5use std::time::Duration;
6
7use tokio::sync::Notify;
8
9#[derive(Clone, Debug)]
24pub struct BackgroundSettlementTracker {
25 inner: Arc<TrackerInner>,
26}
27
28#[derive(Debug)]
29struct TrackerInner {
30 in_flight: AtomicUsize,
31 drained: Notify,
32}
33
34impl Default for BackgroundSettlementTracker {
35 fn default() -> Self {
36 Self::new()
37 }
38}
39
40impl BackgroundSettlementTracker {
41 #[must_use]
43 pub fn new() -> Self {
44 Self {
45 inner: Arc::new(TrackerInner {
46 in_flight: AtomicUsize::new(0),
47 drained: Notify::new(),
48 }),
49 }
50 }
51
52 #[must_use]
55 pub fn in_flight(&self) -> usize {
56 self.inner.in_flight.load(Ordering::SeqCst)
57 }
58
59 pub(crate) fn start(&self) -> SettlementInFlightGuard {
63 let _previous = self.inner.in_flight.fetch_add(1, Ordering::SeqCst);
64 SettlementInFlightGuard {
65 inner: Arc::clone(&self.inner),
66 }
67 }
68
69 pub async fn wait_for_drain(&self, timeout: Duration) -> Result<(), usize> {
79 if self.in_flight() == 0 {
80 return Ok(());
81 }
82 let deadline = tokio::time::Instant::now() + timeout;
83 loop {
84 let notified = self.inner.drained.notified();
85 tokio::pin!(notified);
86 tokio::select! {
87 () = &mut notified => {}
88 () = tokio::time::sleep_until(deadline) => {
89 let remaining = self.in_flight();
90 return if remaining == 0 { Ok(()) } else { Err(remaining) };
91 }
92 }
93 if self.in_flight() == 0 {
94 return Ok(());
95 }
96 }
97 }
98}
99
100#[derive(Debug)]
107pub(crate) struct SettlementInFlightGuard {
108 inner: Arc<TrackerInner>,
109}
110
111impl Drop for SettlementInFlightGuard {
112 fn drop(&mut self) {
113 let previous = self.inner.in_flight.fetch_sub(1, Ordering::SeqCst);
114 if previous == 1 {
115 self.inner.drained.notify_waiters();
116 }
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[tokio::test]
125 async fn empty_tracker_drains_immediately() {
126 let tracker = BackgroundSettlementTracker::new();
127 assert_eq!(tracker.in_flight(), 0);
128 tracker.wait_for_drain(Duration::ZERO).await.unwrap();
129 }
130
131 #[tokio::test]
132 async fn drain_waits_for_guard_drop() {
133 let tracker = BackgroundSettlementTracker::new();
134 let guard = tracker.start();
135 assert_eq!(tracker.in_flight(), 1);
136
137 let tracker_clone = tracker.clone();
138 let drop_task = tokio::spawn(async move {
139 tokio::time::sleep(Duration::from_millis(10)).await;
140 drop(guard);
141 assert_eq!(tracker_clone.in_flight(), 0);
142 });
143
144 tracker
145 .wait_for_drain(Duration::from_secs(1))
146 .await
147 .expect("drain should complete after the guard drops");
148 drop_task.await.unwrap();
149 }
150
151 #[tokio::test]
152 async fn drain_times_out_when_guards_outlive_deadline() {
153 let tracker = BackgroundSettlementTracker::new();
154 let _guard = tracker.start();
155
156 let result = tracker.wait_for_drain(Duration::from_millis(20)).await;
157 assert_eq!(result, Err(1), "deadline elapses with the guard alive");
158 }
159
160 #[tokio::test]
161 async fn nested_guards_decrement_in_order() {
162 let tracker = BackgroundSettlementTracker::new();
163 let g1 = tracker.start();
164 let g2 = tracker.start();
165 let g3 = tracker.start();
166 assert_eq!(tracker.in_flight(), 3);
167 drop(g2);
168 assert_eq!(tracker.in_flight(), 2);
169 drop(g1);
170 assert_eq!(tracker.in_flight(), 1);
171 drop(g3);
172 assert_eq!(tracker.in_flight(), 0);
173 }
174}