polars_io/cloud/concurrency/
admission.rs1use std::sync::Arc;
2use std::sync::atomic::Ordering::Relaxed;
3use std::sync::atomic::{AtomicU64, Ordering};
4
5use tokio::sync::Notify;
6
7#[derive(Debug)]
13pub(super) struct ByteBudget {
14 current_budget: AtomicU64,
17 floor_budget: u64,
19 inflight_in_use: AtomicU64,
21 waiters: Notify,
22}
23
24impl ByteBudget {
25 fn new(initial: u64, floor_budget: u64) -> Self {
26 Self {
27 current_budget: AtomicU64::new(initial),
28 floor_budget,
29 inflight_in_use: AtomicU64::new(0),
30 waiters: Notify::new(),
31 }
32 }
33
34 async fn acquire_strict(&self, n_bytes: u64) {
37 assert!(n_bytes <= self.floor_budget);
39
40 loop {
43 let cap = self.current_budget.load(Ordering::Acquire);
44 let inflight = self.inflight_in_use.load(Ordering::Acquire);
45
46 if inflight + n_bytes <= cap {
47 if self
48 .inflight_in_use
49 .compare_exchange_weak(
50 inflight,
51 inflight + n_bytes,
52 Ordering::AcqRel,
53 Ordering::Relaxed,
54 )
55 .is_ok()
56 {
57 self.waiters.notify_one();
62 return;
63 }
64 continue;
65 }
66
67 let notified = self.waiters.notified();
69 let cap = self.current_budget.load(Ordering::Acquire);
70 let inflight = self.inflight_in_use.load(Ordering::Acquire);
71 if inflight + n_bytes <= cap {
72 continue;
73 }
74 notified.await;
75 }
76 }
77
78 fn release(&self, bytes: u64) {
79 self.inflight_in_use.fetch_sub(bytes, Ordering::AcqRel);
80 self.waiters.notify_one();
81 }
82
83 fn resize(&self, new: u64) {
84 let new = new.max(self.floor_budget);
85 let old = self.current_budget.swap(new, Ordering::AcqRel);
86 if new > old {
87 self.waiters.notify_waiters();
89 }
90 }
91
92 fn current_budget(&self) -> u64 {
93 self.current_budget.load(Ordering::Relaxed)
94 }
95
96 fn floor_byte_budget(&self) -> u64 {
97 self.floor_budget
98 }
99
100 fn inflight_in_use(&self) -> u64 {
101 self.inflight_in_use.load(Ordering::Relaxed)
102 }
103}
104
105#[derive(Debug)]
106pub(super) struct RequestBudget {
107 current_budget: AtomicU64,
109 floor_budget: u64,
111 inflight_in_use: AtomicU64,
113 waiters: Notify,
114}
115
116impl RequestBudget {
117 fn new(initial: u64, floor_budget: u64) -> Self {
118 Self {
119 current_budget: AtomicU64::new(initial),
120 floor_budget,
121 inflight_in_use: AtomicU64::new(0),
122 waiters: Notify::new(),
123 }
124 }
125
126 async fn acquire_one(&self) {
129 let n = 1;
130
131 loop {
132 let cap = self.current_budget.load(Ordering::Acquire);
133 let inflight = self.inflight_in_use.load(Ordering::Acquire);
134
135 if inflight + n <= cap {
136 if self
137 .inflight_in_use
138 .compare_exchange_weak(
139 inflight,
140 inflight + n,
141 Ordering::AcqRel,
142 Ordering::Relaxed,
143 )
144 .is_ok()
145 {
146 self.waiters.notify_one();
148 return;
149 }
150 continue;
151 }
152
153 let notified = self.waiters.notified();
155 let cap = self.current_budget.load(Ordering::Acquire);
156 let inflight = self.inflight_in_use.load(Ordering::Acquire);
157 if inflight + n <= cap {
158 continue;
159 }
160 notified.await;
161 }
162 }
163
164 fn release_one(&self) {
165 let n = 1;
166
167 self.inflight_in_use.fetch_sub(n, Ordering::AcqRel);
168 self.waiters.notify_one();
169 }
170
171 fn resize(&self, new: u64) {
172 let new = new.max(self.floor_budget);
173 let old = self.current_budget.swap(new, Ordering::AcqRel);
174 if new > old {
175 self.waiters.notify_waiters();
177 }
178 }
179
180 fn inflight_in_use(&self) -> u64 {
181 self.inflight_in_use.load(Ordering::Relaxed)
182 }
183}
184
185#[derive(Clone, Copy, Debug)]
186pub struct InFlightStats {
187 pub bytes_budget: u64,
188 pub bytes_in_use: u64,
189 pub bytes_saturation: f64,
192 pub request_budget: u64,
193 pub requests_in_use: u64,
194 pub requests_saturation: f64,
195}
196
197#[derive(Debug)]
198pub struct InFlightBudget {
199 byte_budget: Arc<ByteBudget>,
200 request_budget: Arc<RequestBudget>,
201}
202
203impl InFlightBudget {
204 pub fn new(
205 initial_byte_budget: u64,
206 floor_byte_budget: u64,
207 initial_request_budget: u64,
208 floor_request_budget: u64,
209 ) -> Self {
210 let inflight_budget = Self {
211 byte_budget: Arc::new(ByteBudget::new(initial_byte_budget, floor_byte_budget)),
212 request_budget: Arc::new(RequestBudget::new(
213 initial_request_budget,
214 floor_request_budget,
215 )),
216 };
217
218 if polars_config::config().verbose() {
219 eprintln!(
220 "[InFlightConcurrency]: \
221 initial_byte_budget: {}, \
222 floor_byte_budget: {}, \
223 request_budget: {}, \
224 floor_request_budget: {}",
225 initial_byte_budget,
226 floor_byte_budget,
227 initial_request_budget,
228 floor_request_budget
229 );
230 }
231
232 inflight_budget
233 }
234
235 pub async fn acquire(self: &Arc<Self>, n_bytes: u64) -> InFlightPermit {
236 let n_bytes = n_bytes.min(self.byte_budget.floor_byte_budget());
239
240 self.byte_budget.acquire_strict(n_bytes).await;
242
243 let bytes = BytesReservation {
246 budget: self.byte_budget.clone(),
247 n_bytes,
248 };
249
250 self.request_budget.acquire_one().await;
251
252 let request = RequestReservation {
253 budget: self.request_budget.clone(),
254 };
255
256 InFlightPermit {
257 _bytes_reservation: bytes,
258 _req_permit: request,
259 }
260 }
261
262 pub fn current_byte_budget(&self) -> u64 {
263 self.byte_budget.current_budget()
264 }
265
266 pub fn floor_byte_budget(&self) -> u64 {
267 self.byte_budget.floor_byte_budget()
268 }
269
270 pub fn resize_byte_budget(&self, new: u64) {
271 self.byte_budget.resize(new);
272 }
273
274 pub fn resize_request_budget(&self, new: u64) {
275 self.request_budget.resize(new);
276 }
277
278 pub fn stats(&self) -> InFlightStats {
279 let bytes_budget = self.byte_budget.current_budget();
280 let bytes_in_use = self.byte_budget.inflight_in_use();
281 let bytes_saturation = if bytes_budget > 0 {
282 bytes_in_use as f64 / bytes_budget as f64
283 } else {
284 0.0
285 };
286
287 let request_budget = self.request_budget.current_budget.load(Relaxed);
288 let requests_in_use = self.request_budget.inflight_in_use();
289 let requests_saturation = if request_budget > 0 {
290 requests_in_use as f64 / request_budget as f64
291 } else {
292 0.0
293 };
294
295 InFlightStats {
296 bytes_budget,
297 bytes_in_use,
298 bytes_saturation,
299 request_budget,
300 requests_in_use,
301 requests_saturation,
302 }
303 }
304}
305
306struct BytesReservation {
307 budget: Arc<ByteBudget>,
308 n_bytes: u64,
309}
310
311impl Drop for BytesReservation {
312 fn drop(&mut self) {
313 self.budget.release(self.n_bytes);
314 }
315}
316
317struct RequestReservation {
318 budget: Arc<RequestBudget>,
319}
320
321impl Drop for RequestReservation {
322 fn drop(&mut self) {
323 self.budget.release_one();
324 }
325}
326
327pub struct InFlightPermit {
328 _bytes_reservation: BytesReservation,
329 _req_permit: RequestReservation,
330}