1use std::sync::atomic::{AtomicU64, Ordering};
33use std::sync::Arc;
34
35use tokio::sync::{OwnedSemaphorePermit, Semaphore};
36
37const UNIT: u64 = 4096;
41
42struct Counters {
43 held: AtomicU64,
44 high_water: AtomicU64,
45}
46
47pub struct ByteGate {
49 sem: Arc<Semaphore>,
50 ctr: Arc<Counters>,
51 max_units: u32,
52 max_bytes: u64,
53}
54
55pub struct BytePermit {
57 _permit: OwnedSemaphorePermit,
58 bytes: u64,
59 ctr: Arc<Counters>,
60}
61
62impl std::fmt::Debug for ByteGate {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 f.debug_struct("ByteGate")
65 .field("max_bytes", &self.max_bytes)
66 .field("held", &self.held())
67 .field("high_water", &self.high_water())
68 .finish()
69 }
70}
71
72impl ByteGate {
73 pub fn new(max_bytes: u64) -> ByteGate {
77 let max_units = max_bytes.div_ceil(UNIT).clamp(1, u32::MAX as u64) as u32;
78 ByteGate {
79 sem: Arc::new(Semaphore::new(max_units as usize)),
80 ctr: Arc::new(Counters { held: AtomicU64::new(0), high_water: AtomicU64::new(0) }),
81 max_units,
82 max_bytes: max_units as u64 * UNIT,
83 }
84 }
85
86 pub fn max_bytes(&self) -> u64 {
88 self.max_bytes
89 }
90
91 pub async fn acquire(&self, bytes: u64) -> BytePermit {
94 let want = bytes.div_ceil(UNIT).clamp(1, self.max_units as u64) as u32;
95 let permit = self
96 .sem
97 .clone()
98 .acquire_many_owned(want)
99 .await
100 .expect("the upload byte gate's semaphore is never closed");
101 let now = self.ctr.held.fetch_add(bytes, Ordering::SeqCst) + bytes;
102 self.ctr.high_water.fetch_max(now, Ordering::SeqCst);
103 BytePermit { _permit: permit, bytes, ctr: self.ctr.clone() }
104 }
105
106 pub fn held(&self) -> u64 {
108 self.ctr.held.load(Ordering::SeqCst)
109 }
110
111 pub fn high_water(&self) -> u64 {
113 self.ctr.high_water.load(Ordering::SeqCst)
114 }
115}
116
117impl Drop for BytePermit {
118 fn drop(&mut self) {
119 self.ctr.held.fetch_sub(self.bytes, Ordering::SeqCst);
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 const MIB: u64 = 1 << 20;
128
129 #[tokio::test]
133 async fn a_request_above_the_budget_clamps_and_proceeds() {
134 let g = ByteGate::new(2 * MIB);
135 assert_eq!(g.max_bytes(), 2 * MIB);
136 let big = tokio::time::timeout(std::time::Duration::from_secs(5), g.acquire(10 * MIB))
137 .await
138 .expect("a request above the budget deadlocked");
139 assert_eq!(g.held(), 10 * MIB, "the counter carries the TRUE bytes");
140 let waiter = tokio::time::timeout(std::time::Duration::from_millis(100), g.acquire(1)).await;
143 assert!(waiter.is_err(), "a clamped holder must own the whole budget");
144 drop(big);
145 assert_eq!(g.held(), 0);
146 let _one = tokio::time::timeout(std::time::Duration::from_secs(5), g.acquire(1))
147 .await
148 .expect("released bytes were not granted on");
149 }
150
151 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
156 async fn high_water_never_exceeds_the_budget_under_contention() {
157 async fn run(gate: Arc<ByteGate>) -> u64 {
158 let mut set = tokio::task::JoinSet::new();
159 for i in 0..64u64 {
160 let g = gate.clone();
161 set.spawn(async move {
162 let _p = g.acquire(MIB / 2 + (i % 3) * 100_000).await;
163 tokio::time::sleep(std::time::Duration::from_millis(5)).await;
164 });
165 }
166 while set.join_next().await.is_some() {}
167 assert_eq!(gate.held(), 0, "every permit releases");
168 gate.high_water()
169 }
170 let bounded = Arc::new(ByteGate::new(3 * MIB));
171 let hw = run(bounded.clone()).await;
172 assert!(hw <= 3 * MIB, "high-water {hw} exceeded the 3 MiB budget");
173 assert!(hw > 0);
174 let control = Arc::new(ByteGate::new(1 << 30));
175 let chw = run(control.clone()).await;
176 assert!(
177 chw > 3 * MIB,
178 "the control's high-water {chw} did not exceed the budget: the tree is too small \
179 to prove the bound load-bearing"
180 );
181 }
182
183 #[tokio::test]
186 async fn permits_release_on_drop_including_cancellation() {
187 let g = Arc::new(ByteGate::new(MIB));
188 let p = g.acquire(MIB).await;
189 assert_eq!(g.held(), MIB);
190 let g2 = g.clone();
193 let parked = tokio::spawn(async move {
194 let _p = g2.acquire(MIB).await;
195 std::future::pending::<()>().await;
196 });
197 tokio::task::yield_now().await;
198 parked.abort();
199 let _ = parked.await;
200 assert_eq!(g.held(), MIB, "a cancelled waiter charged nothing");
201 drop(p);
202 assert_eq!(g.held(), 0);
203 assert_eq!(g.high_water(), MIB);
204 let g3 = g.clone();
206 let holder = tokio::spawn(async move {
207 let _p = g3.acquire(MIB).await;
208 std::future::pending::<()>().await;
209 });
210 tokio::task::yield_now().await;
211 assert_eq!(g.held(), MIB);
212 holder.abort();
213 let _ = holder.await;
214 assert_eq!(g.held(), 0, "an aborted holder released its bytes");
215 let _again = tokio::time::timeout(std::time::Duration::from_secs(5), g.acquire(MIB))
216 .await
217 .expect("the budget was not returned");
218 }
219}