bacnet_ops/limits/
account.rs1use std::future::Future;
13use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
14use std::sync::{Arc, Mutex, PoisonError};
15use std::time::Duration;
16
17use tokio::time::Instant;
18
19tokio::task_local! {
20 static ACCOUNT: Arc<Account>;
21}
22
23#[derive(Debug, Default)]
26pub struct Account {
27 requests: AtomicU32,
28 throttled_us: AtomicU64,
29 bucket: Option<Arc<TokenBucket>>,
30}
31
32impl Account {
33 pub fn new(bucket: Option<Arc<TokenBucket>>) -> Self {
35 Self {
36 requests: AtomicU32::new(0),
37 throttled_us: AtomicU64::new(0),
38 bucket,
39 }
40 }
41
42 pub fn requests(&self) -> u32 {
44 self.requests.load(Ordering::Relaxed)
45 }
46
47 pub fn throttled(&self) -> Duration {
50 Duration::from_micros(self.throttled_us.load(Ordering::Relaxed))
51 }
52
53 pub(super) fn count_request(&self) {
54 self.requests.fetch_add(1, Ordering::Relaxed);
55 }
56
57 fn uncount_request(&self) {
59 if self.requests.load(Ordering::Relaxed) > 0 {
62 self.requests.fetch_sub(1, Ordering::Relaxed);
63 }
64 }
65
66 pub(super) fn add_wait(&self, waited: Duration) {
67 let micros = u64::try_from(waited.as_micros()).unwrap_or(u64::MAX);
68 self.throttled_us.fetch_add(micros, Ordering::Relaxed);
69 }
70
71 pub(super) fn bucket(&self) -> Option<&TokenBucket> {
72 self.bucket.as_deref()
73 }
74}
75
76pub async fn charged<F: Future>(account: Arc<Account>, future: F) -> F::Output {
78 ACCOUNT.scope(account, future).await
79}
80
81pub fn refund() {
84 if let Some(account) = current() {
85 account.uncount_request();
86 }
87}
88
89pub(super) fn current() -> Option<Arc<Account>> {
91 ACCOUNT.try_with(Arc::clone).ok()
92}
93
94#[derive(Debug)]
99pub struct TokenBucket {
100 per_second: f64,
101 burst: f64,
102 state: Mutex<Tokens>,
103}
104
105#[derive(Debug)]
106struct Tokens {
107 left: f64,
109 at: Instant,
110}
111
112impl TokenBucket {
113 pub fn new(per_second: u32, burst: u32) -> Self {
116 let burst = f64::from(burst.max(1));
117 Self {
118 per_second: f64::from(per_second),
119 burst,
120 state: Mutex::new(Tokens {
121 left: burst,
122 at: Instant::now(),
123 }),
124 }
125 }
126
127 pub async fn take(&self) {
129 if self.per_second == 0.0 {
130 return;
131 }
132 let wait = {
133 let mut tokens = self.state.lock().unwrap_or_else(PoisonError::into_inner);
134 let now = Instant::now();
135 let refill = now.saturating_duration_since(tokens.at).as_secs_f64() * self.per_second;
136 tokens.left = (tokens.left + refill).min(self.burst) - 1.0;
137 tokens.at = now;
138 (tokens.left < 0.0).then(|| Duration::from_secs_f64(-tokens.left / self.per_second))
139 };
140 if let Some(wait) = wait {
141 tokio::time::sleep(wait).await;
142 }
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[tokio::test(start_paused = true)]
153 async fn a_bucket_gives_its_burst_then_its_rate() {
154 let bucket = TokenBucket::new(5, 3);
155 let start = Instant::now();
156 let mut times = Vec::new();
157 for _ in 0..5 {
158 bucket.take().await;
159 times.push((Instant::now() - start).as_millis());
160 }
161 assert_eq!(times, [0, 0, 0, 200, 400]);
162 tokio::time::sleep(Duration::from_secs(10)).await;
163 let before = Instant::now();
164 for _ in 0..3 {
165 bucket.take().await;
166 }
167 assert_eq!(Instant::now(), before, "refilled to the burst, no more");
168 bucket.take().await;
169 assert_eq!(Instant::now() - before, Duration::from_millis(200));
170 let unlimited = TokenBucket::new(0, 1);
171 let before = Instant::now();
172 for _ in 0..100 {
173 unlimited.take().await;
174 }
175 assert_eq!(Instant::now(), before);
176 }
177
178 #[tokio::test]
180 async fn the_account_rides_with_the_call() {
181 assert!(current().is_none());
182 let account = Arc::new(Account::new(None));
183 charged(Arc::clone(&account), async {
184 current().expect("an account").count_request();
185 })
186 .await;
187 assert_eq!(account.requests(), 1);
188 }
189}