coven_storage/
transfer_budget.rs1use coven_foundation::locks::lock_unpoisoned;
4use std::{
5 num::NonZeroUsize,
6 sync::{Arc, Mutex},
7};
8use tokio::sync::Notify;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub struct TransferLimits {
13 pub uploads: NonZeroUsize,
16 pub downloads: NonZeroUsize,
20}
21impl Default for TransferLimits {
22 fn default() -> Self {
23 Self { uploads: NonZeroUsize::MIN, downloads: NonZeroUsize::MIN }
24 }
25}
26
27pub struct TransferBudget {
30 state: Mutex<BudgetState>,
31 changed: Notify,
32 #[cfg(any(test, feature = "test-utils"))]
35 waited: tokio::sync::watch::Sender<usize>,
36}
37struct BudgetState {
38 limits: TransferLimits,
39 downloads: usize,
40}
41impl TransferBudget {
42 pub fn new(limits: TransferLimits) -> Self {
44 Self {
45 state: Mutex::new(BudgetState { limits, downloads: 0 }),
46 changed: Notify::new(),
47 #[cfg(any(test, feature = "test-utils"))]
48 waited: tokio::sync::watch::channel(0).0,
49 }
50 }
51 pub fn limits(&self) -> TransferLimits {
53 lock_unpoisoned(&self.state).limits
54 }
55 pub fn set_limits(&self, limits: TransferLimits) {
58 lock_unpoisoned(&self.state).limits = limits;
59 self.changed.notify_waiters();
60 }
61 pub(crate) async fn download(self: &Arc<Self>) -> DownloadLease {
62 loop {
63 let changed = self.changed.notified();
64 tokio::pin!(changed);
65 changed.as_mut().enable();
66 {
67 let mut state = lock_unpoisoned(&self.state);
68 if state.downloads < state.limits.downloads.get() {
69 state.downloads += 1;
70 return DownloadLease(self.clone());
71 }
72 }
73 #[cfg(any(test, feature = "test-utils"))]
74 self.waited.send_modify(|waited| *waited += 1);
75 changed.await;
76 }
77 }
78
79 #[cfg(any(test, feature = "test-utils"))]
82 pub fn downloads_waiting_from_now(
83 &self,
84 count: usize,
85 ) -> impl std::future::Future<Output = ()> + Send + 'static {
86 let mut waited = self.waited.subscribe();
87 let now = *waited.borrow_and_update();
88 async move {
89 coven_foundation::clock::within(waited.wait_for(|waited| *waited >= now + count))
90 .await
91 .expect("the downloads wait")
92 .expect("the budget is kept");
93 }
94 }
95}
96
97pub(crate) struct DownloadLease(Arc<TransferBudget>);
98impl Drop for DownloadLease {
99 fn drop(&mut self) {
100 lock_unpoisoned(&self.0.state).downloads -= 1;
101 self.0.changed.notify_waiters();
102 }
103}
104
105#[cfg(test)]
106#[path = "transfer_budget_tests.rs"]
107mod tests;