1use std::collections::{HashMap, HashSet, VecDeque};
6use std::sync::{Arc, Mutex};
7use tokio::sync::{oneshot, Notify};
8use tokio::time::{Duration, Instant};
9
10pub const MAX_BULK_BURST_BYTES: u64 = 64 * 1024;
11const MAX_PENDING: usize = 4096;
12const MAX_SAFE_INTEGER: u64 = 9_007_199_254_740_991;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum BulkDirection {
16 Upload,
17 Download,
18}
19impl BulkDirection {
20 fn index(self) -> usize {
21 match self {
22 Self::Upload => 0,
23 Self::Download => 1,
24 }
25 }
26}
27#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
28pub struct BulkRates {
29 pub upload_bytes_per_second: Option<u64>,
30 pub download_bytes_per_second: Option<u64>,
31}
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum BulkFlowError {
34 InvalidRate,
35 InvalidSlice,
36 QueueFull,
37 OwnerClosed,
38}
39impl std::fmt::Display for BulkFlowError {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 f.write_str(match self {
42 Self::InvalidRate => "Bulk rate must be unset or a positive safe integer",
43 Self::InvalidSlice => "Invalid bulk body slice",
44 Self::QueueFull => "receiver_busy: bulk flow queue is full",
45 Self::OwnerClosed => "execution_suspended: bulk flow owner closed",
46 })
47 }
48}
49impl std::error::Error for BulkFlowError {}
50struct Pending {
51 id: u64,
52 bytes: u64,
53 scope: Option<String>,
54 ready: oneshot::Sender<()>,
55}
56struct ScopedBudget {
57 rate: u64,
58 tokens: f64,
59 updated: Instant,
60}
61impl ScopedBudget {
62 fn refill(&mut self) {
63 let now = Instant::now();
64 self.tokens = (self.tokens
65 + now.saturating_duration_since(self.updated).as_secs_f64() * self.rate as f64)
66 .min(MAX_BULK_BURST_BYTES as f64);
67 self.updated = now;
68 }
69}
70struct DirectionState {
71 rate: Option<u64>,
72 tokens: f64,
73 updated: Instant,
74 peers: HashMap<String, VecDeque<Pending>>,
75 order: VecDeque<String>,
76 scopes: HashMap<String, ScopedBudget>,
77}
78impl DirectionState {
79 fn new() -> Self {
80 Self {
81 rate: None,
82 tokens: MAX_BULK_BURST_BYTES as f64,
83 updated: Instant::now(),
84 peers: HashMap::new(),
85 order: VecDeque::new(),
86 scopes: HashMap::new(),
87 }
88 }
89 fn refill(&mut self) {
90 let now = Instant::now();
91 if let Some(rate) = self.rate {
92 self.tokens = (self.tokens
93 + now.saturating_duration_since(self.updated).as_secs_f64() * rate as f64)
94 .min(MAX_BULK_BURST_BYTES as f64);
95 }
96 self.updated = now;
97 }
98 fn admit_scope(&mut self, key: &str, rate: u64) -> Result<(), BulkFlowError> {
99 if let Some(scope) = self.scopes.get(key) {
100 return if scope.rate == rate {
101 Ok(())
102 } else {
103 Err(BulkFlowError::InvalidRate)
104 };
105 }
106 let active: HashSet<_> = self
109 .peers
110 .values()
111 .flat_map(|queue| queue.iter())
112 .filter_map(|slice| slice.scope.clone())
113 .collect();
114 self.scopes.retain(|key, scope| {
115 active.contains(key)
116 || scope.updated.elapsed().as_secs_f64() * (scope.rate as f64)
117 < MAX_BULK_BURST_BYTES as f64
118 });
119 if self.scopes.len() >= MAX_PENDING {
120 return Err(BulkFlowError::QueueFull);
121 }
122 self.scopes.insert(
123 key.into(),
124 ScopedBudget {
125 rate,
126 tokens: MAX_BULK_BURST_BYTES as f64,
127 updated: Instant::now(),
128 },
129 );
130 Ok(())
131 }
132 fn pump(&mut self) -> Option<Duration> {
133 self.refill();
134 let mut blocked = 0;
135 let mut wait: Option<Duration> = None;
136 while !self.order.is_empty() && blocked < self.order.len() {
137 let peer = self.order.pop_front().expect("flow peer order");
138 let queue = self.peers.get_mut(&peer).expect("flow peer order");
139 while queue.front().is_some_and(|slice| slice.ready.is_closed()) {
140 queue.pop_front();
141 }
142 if queue.is_empty() {
143 self.peers.remove(&peer);
144 continue;
145 }
146 let front = queue.front().expect("pending flow slice");
147 let bytes = front.bytes;
148 let scope_key = front.scope.clone();
149 let global_delay = self
150 .rate
151 .filter(|_| self.tokens < bytes as f64)
152 .map(|rate| (bytes as f64 - self.tokens) / rate as f64);
153 let mut scoped_delay = None;
154 if let Some(key) = &scope_key {
155 let scope = self.scopes.get_mut(key).expect("queued flow scope");
156 scope.refill();
157 if scope.tokens < bytes as f64 {
158 let scoped = (bytes as f64 - scope.tokens) / scope.rate as f64;
159 scoped_delay = Some(scoped);
160 }
161 }
162 if scoped_delay.is_none() {
165 if let Some(delay) = global_delay {
166 let delay = Duration::from_secs_f64(delay.max(0.000_000_001));
167 self.order.push_front(peer);
168 return Some(wait.map_or(delay, |prior| prior.min(delay)));
169 }
170 }
171 let delay = match (global_delay, scoped_delay) {
172 (Some(global), Some(scoped)) => Some(global.max(scoped)),
173 (global, scoped) => global.or(scoped),
174 };
175 if let Some(delay) = delay {
176 let delay = Duration::from_secs_f64(delay.max(0.000_000_001));
179 wait = Some(wait.map_or(delay, |prior| prior.min(delay)));
180 self.order.push_back(peer);
181 blocked += 1;
182 continue;
183 }
184 if self.rate.is_some() {
185 self.tokens -= bytes as f64;
186 }
187 if let Some(key) = scope_key {
188 self.scopes.get_mut(&key).expect("queued flow scope").tokens -= bytes as f64;
189 }
190 let slice = queue.pop_front().expect("pending flow slice");
191 if queue.is_empty() {
192 self.peers.remove(&peer);
193 } else {
194 self.order.push_back(peer);
195 }
196 let _ = slice.ready.send(());
197 blocked = 0;
198 }
199 wait
200 }
201}
202struct FlowState {
203 directions: [DirectionState; 2],
204 next_id: u64,
205}
206struct FlowInner {
207 state: Mutex<FlowState>,
208 changed: Notify,
209}
210#[derive(Clone)]
212pub struct BulkFlowController {
213 inner: Arc<FlowInner>,
214}
215struct WaitingSlice {
216 owner: Arc<FlowInner>,
217 direction: usize,
218 peer: String,
219 id: u64,
220}
221impl Drop for WaitingSlice {
222 fn drop(&mut self) {
223 let mut state = self
224 .owner
225 .state
226 .lock()
227 .unwrap_or_else(std::sync::PoisonError::into_inner);
228 let direction = &mut state.directions[self.direction];
229 if let Some(queue) = direction.peers.get_mut(&self.peer) {
230 queue.retain(|slice| slice.id != self.id);
231 if queue.is_empty() {
232 direction.peers.remove(&self.peer);
233 direction.order.retain(|peer| peer != &self.peer);
234 }
235 }
236 direction.pump();
237 drop(state);
238 self.owner.changed.notify_waiters();
239 }
240}
241impl BulkFlowController {
242 pub fn new(rates: BulkRates) -> Result<Self, BulkFlowError> {
243 let result = Self {
244 inner: Arc::new(FlowInner {
245 state: Mutex::new(FlowState {
246 directions: [DirectionState::new(), DirectionState::new()],
247 next_id: 0,
248 }),
249 changed: Notify::new(),
250 }),
251 };
252 result.configure(rates)?;
253 Ok(result)
254 }
255 pub fn configure(&self, rates: BulkRates) -> Result<(), BulkFlowError> {
256 let rates = [
257 rates.upload_bytes_per_second,
258 rates.download_bytes_per_second,
259 ];
260 if rates
261 .into_iter()
262 .flatten()
263 .any(|rate| rate == 0 || rate > MAX_SAFE_INTEGER)
264 {
265 return Err(BulkFlowError::InvalidRate);
266 }
267 let mut state = self
268 .inner
269 .state
270 .lock()
271 .unwrap_or_else(std::sync::PoisonError::into_inner);
272 for (direction, rate) in state.directions.iter_mut().zip(rates) {
273 direction.refill();
274 direction.rate = rate;
275 direction.pump();
276 }
277 drop(state);
278 self.inner.changed.notify_waiters();
279 Ok(())
280 }
281 pub async fn acquire(
282 &self,
283 direction: BulkDirection,
284 peer: &str,
285 bytes: u64,
286 ) -> Result<(), BulkFlowError> {
287 self.acquire_inner(direction, peer, None, bytes).await
288 }
289 pub async fn acquire_scoped(
292 &self,
293 direction: BulkDirection,
294 peer: &str,
295 scope: &str,
296 bytes_per_second: Option<u64>,
297 bytes: u64,
298 ) -> Result<(), BulkFlowError> {
299 if scope.is_empty()
300 || scope.len() > 128
301 || scope.chars().any(char::is_control)
302 || bytes_per_second.is_some_and(|rate| rate == 0 || rate > MAX_SAFE_INTEGER)
303 {
304 return Err(BulkFlowError::InvalidRate);
305 }
306 self.acquire_inner(
307 direction,
308 peer,
309 bytes_per_second.map(|rate| (scope, rate)),
310 bytes,
311 )
312 .await
313 }
314 async fn acquire_inner(
315 &self,
316 direction: BulkDirection,
317 peer: &str,
318 scope: Option<(&str, u64)>,
319 bytes: u64,
320 ) -> Result<(), BulkFlowError> {
321 if peer.is_empty()
322 || peer.len() > 512
323 || peer.chars().any(char::is_control)
324 || bytes > MAX_BULK_BURST_BYTES
325 {
326 return Err(BulkFlowError::InvalidSlice);
327 }
328 if bytes == 0 {
329 return Ok(());
330 }
331 let direction = direction.index();
332 let (send, mut receive) = oneshot::channel();
333 let id = {
334 let mut state = self
335 .inner
336 .state
337 .lock()
338 .unwrap_or_else(std::sync::PoisonError::into_inner);
339 let pending: usize = state
340 .directions
341 .iter()
342 .flat_map(|state| state.peers.values())
343 .map(VecDeque::len)
344 .sum();
345 if pending >= MAX_PENDING {
346 return Err(BulkFlowError::QueueFull);
347 }
348 if let Some((key, rate)) = scope {
349 state.directions[direction].admit_scope(key, rate)?;
350 }
351 let id = state
352 .next_id
353 .checked_add(1)
354 .ok_or(BulkFlowError::QueueFull)?;
355 state.next_id = id;
356 let state = &mut state.directions[direction];
357 if !state.peers.contains_key(peer) {
358 state.order.push_back(peer.into());
359 }
360 state
361 .peers
362 .entry(peer.into())
363 .or_default()
364 .push_back(Pending {
365 id,
366 bytes,
367 scope: scope.map(|(key, _)| key.into()),
368 ready: send,
369 });
370 state.pump();
371 id
372 };
373 let _lease = WaitingSlice {
374 owner: self.inner.clone(),
375 direction,
376 peer: peer.into(),
377 id,
378 };
379 loop {
380 let changed = self.inner.changed.notified();
383 tokio::pin!(changed);
384 changed.as_mut().enable();
385 let wait = {
386 let mut state = self
387 .inner
388 .state
389 .lock()
390 .unwrap_or_else(std::sync::PoisonError::into_inner);
391 state.directions[direction].pump()
392 };
393 tokio::select! {
394 result = &mut receive => return result.map_err(|_| BulkFlowError::OwnerClosed),
395 _ = &mut changed => {},
396 _ = async { match wait { Some(delay) => tokio::time::sleep(delay).await,
397 None => std::future::pending().await } } => {},
398 }
399 }
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406
407 #[tokio::test(start_paused = true)]
408 async fn aggregate_ready_large_slice_keeps_its_turn_ahead_of_later_small_slices() {
409 let flow = BulkFlowController::new(BulkRates {
410 upload_bytes_per_second: Some(1024),
411 ..BulkRates::default()
412 })
413 .unwrap();
414 flow.acquire(BulkDirection::Upload, "initial", MAX_BULK_BURST_BYTES)
415 .await
416 .unwrap();
417 let large = {
418 let flow = flow.clone();
419 tokio::spawn(async move {
420 flow.acquire(BulkDirection::Upload, "large", MAX_BULK_BURST_BYTES)
421 .await
422 })
423 };
424 tokio::task::yield_now().await;
425 let small = {
426 let flow = flow.clone();
427 tokio::spawn(async move { flow.acquire(BulkDirection::Upload, "small", 1024).await })
428 };
429 tokio::task::yield_now().await;
430 tokio::time::advance(Duration::from_secs(1)).await;
431 tokio::task::yield_now().await;
432 tokio::task::yield_now().await;
433 assert!(
434 !small.is_finished(),
435 "Later small slices consumed credit owed to an aggregate-ready large peer"
436 );
437 assert!(!large.is_finished());
438 tokio::time::advance(Duration::from_secs(63)).await;
439 tokio::task::yield_now().await;
440 tokio::task::yield_now().await;
441 assert!(large.is_finished());
442 large.await.unwrap().unwrap();
443 assert!(!small.is_finished());
444 tokio::time::advance(Duration::from_secs(1)).await;
445 small.await.unwrap().unwrap();
446 }
447
448 #[tokio::test(start_paused = true)]
451 async fn parallel_clones_share_one_burst_and_rotate_peers_before_second_slices() {
452 let flow = BulkFlowController::new(BulkRates {
453 upload_bytes_per_second: Some(1024),
454 download_bytes_per_second: Some(2048),
455 })
456 .unwrap();
457 let start = Instant::now();
458 flow.acquire(BulkDirection::Upload, "a", MAX_BULK_BURST_BYTES)
459 .await
460 .unwrap();
461 let observed = Arc::new(Mutex::new(Vec::new()));
462 let mut tasks = Vec::new();
463 for (name, peer) in [("a1", "a"), ("a2", "a"), ("b1", "b")] {
464 let (flow, observed) = (flow.clone(), observed.clone());
465 tasks.push(tokio::spawn(async move {
466 flow.acquire(BulkDirection::Upload, peer, MAX_BULK_BURST_BYTES)
467 .await
468 .unwrap();
469 observed
470 .lock()
471 .unwrap()
472 .push((name, Instant::now().duration_since(start)));
473 }));
474 tokio::task::yield_now().await;
475 }
476 assert!(
477 observed.lock().unwrap().is_empty(),
478 "clones must not each get a fresh burst"
479 );
480 flow.acquire(BulkDirection::Download, "b", MAX_BULK_BURST_BYTES)
482 .await
483 .unwrap();
484 for _ in 0..3 {
485 tokio::time::advance(Duration::from_secs(64)).await;
486 tokio::task::yield_now().await;
487 tokio::task::yield_now().await;
488 }
489 for task in tasks {
490 task.await.unwrap();
491 }
492 let events = observed.lock().unwrap();
493 assert_eq!(
494 events.iter().map(|(name, _)| *name).collect::<Vec<_>>(),
495 ["a1", "b1", "a2"]
496 );
497 for (index, (_, elapsed)) in events.iter().enumerate() {
498 let attempted = (index as u64 + 2) * MAX_BULK_BURST_BYTES;
499 assert!(attempted <= MAX_BULK_BURST_BYTES + elapsed.as_secs() * 1024);
500 }
501 }
502
503 #[tokio::test(start_paused = true)]
504 async fn cancelled_head_releases_waiters_and_live_limits_wake_only_their_direction() {
505 let flow = BulkFlowController::new(BulkRates {
506 upload_bytes_per_second: Some(1024),
507 download_bytes_per_second: Some(1024),
508 })
509 .unwrap();
510 for direction in [BulkDirection::Upload, BulkDirection::Download] {
511 flow.acquire(direction, "initial", MAX_BULK_BURST_BYTES)
512 .await
513 .unwrap();
514 }
515 let head = {
516 let flow = flow.clone();
517 tokio::spawn(async move {
518 flow.acquire(BulkDirection::Upload, "cancelled", MAX_BULK_BURST_BYTES)
519 .await
520 })
521 };
522 tokio::task::yield_now().await;
523 let smaller = {
524 let flow = flow.clone();
525 tokio::spawn(async move { flow.acquire(BulkDirection::Upload, "other", 1024).await })
526 };
527 tokio::task::yield_now().await;
528 head.abort();
529 let _ = head.await;
530 tokio::time::advance(Duration::from_secs(1)).await;
531 tokio::task::yield_now().await;
532 assert!(
533 smaller.is_finished(),
534 "cancelled head cannot leave a 64-second deadline behind"
535 );
536 smaller.await.unwrap().unwrap();
537 let upload = {
538 let flow = flow.clone();
539 tokio::spawn(async move {
540 flow.acquire(BulkDirection::Upload, "a", MAX_BULK_BURST_BYTES)
541 .await
542 })
543 };
544 let download = {
545 let flow = flow.clone();
546 tokio::spawn(async move {
547 flow.acquire(BulkDirection::Download, "a", MAX_BULK_BURST_BYTES)
548 .await
549 })
550 };
551 tokio::task::yield_now().await;
552 assert!(!upload.is_finished());
553 assert!(!download.is_finished());
554 flow.configure(BulkRates {
555 upload_bytes_per_second: None,
556 download_bytes_per_second: Some(1024),
557 })
558 .unwrap();
559 tokio::task::yield_now().await;
560 assert!(upload.is_finished());
561 assert!(!download.is_finished());
562 upload.await.unwrap().unwrap();
563 download.abort();
564 let _ = download.await;
565 assert_eq!(
566 flow.acquire(BulkDirection::Upload, "a", MAX_BULK_BURST_BYTES + 1)
567 .await,
568 Err(BulkFlowError::InvalidSlice)
569 );
570 assert_eq!(
571 flow.configure(BulkRates {
572 upload_bytes_per_second: Some(0),
573 download_bytes_per_second: None
574 }),
575 Err(BulkFlowError::InvalidRate)
576 );
577 }
578}