use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use tokio::sync::{Notify, OwnedSemaphorePermit, Semaphore};
#[derive(Debug)]
pub(super) struct ByteBudget {
current_budget: AtomicU64,
floor_budget: u64,
inflight_in_use: AtomicU64,
waiters: Notify,
}
impl ByteBudget {
fn new(initial: u64, floor_budget: u64) -> Self {
Self {
current_budget: AtomicU64::new(initial),
floor_budget,
inflight_in_use: AtomicU64::new(0),
waiters: Notify::new(),
}
}
async fn acquire_strict(&self, n_bytes: u64) {
assert!(n_bytes <= self.floor_budget);
loop {
let cap = self.current_budget.load(Ordering::Acquire);
let inflight = self.inflight_in_use.load(Ordering::Acquire);
if inflight + n_bytes <= cap {
if self
.inflight_in_use
.compare_exchange_weak(
inflight,
inflight + n_bytes,
Ordering::AcqRel,
Ordering::Relaxed,
)
.is_ok()
{
self.waiters.notify_one();
return;
}
continue;
}
let notified = self.waiters.notified();
let cap = self.current_budget.load(Ordering::Acquire);
let inflight = self.inflight_in_use.load(Ordering::Acquire);
if inflight + n_bytes <= cap {
continue;
}
notified.await;
}
}
fn release(&self, bytes: u64) {
self.inflight_in_use.fetch_sub(bytes, Ordering::AcqRel);
self.waiters.notify_one();
}
fn resize(&self, new: u64) {
let new = new.max(self.floor_budget);
let old = self.current_budget.swap(new, Ordering::AcqRel);
if new > old {
self.waiters.notify_waiters();
}
}
fn current_budget(&self) -> u64 {
self.current_budget.load(Ordering::Relaxed)
}
fn floor_byte_budget(&self) -> u64 {
self.floor_budget
}
fn inflight_in_use(&self) -> u64 {
self.inflight_in_use.load(Ordering::Relaxed)
}
}
#[derive(Debug)]
pub(super) struct RequestBudget {
budget: usize,
semaphore: Arc<Semaphore>,
}
impl RequestBudget {
fn new(budget: usize) -> Self {
Self {
budget,
semaphore: Arc::new(Semaphore::new(budget)),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct InFlightStats {
pub bytes_budget: u64,
pub bytes_in_use: u64,
pub bytes_saturation: f64,
pub request_budget: usize,
pub requests_in_use: usize,
pub requests_saturation: f64,
}
#[derive(Debug)]
pub struct InFlightBudget {
byte_budget: Arc<ByteBudget>,
request_budget: Arc<RequestBudget>,
}
impl InFlightBudget {
pub fn new(
initial_byte_budget: u64,
floor_byte_budget: u64,
initial_request_budget: u32,
) -> Self {
let inflight_budget = Self {
byte_budget: Arc::new(ByteBudget::new(initial_byte_budget, floor_byte_budget)),
request_budget: Arc::new(RequestBudget::new(initial_request_budget as usize)),
};
if polars_config::config().verbose() {
eprintln!(
"[InFlightConcurrency]: \
initial_byte_budget: {}, \
floor_byte_budget: {}, \
request_budget: {}",
initial_byte_budget, floor_byte_budget, initial_request_budget
);
}
inflight_budget
}
pub async fn acquire(self: &Arc<Self>, n_bytes: u64) -> InFlightPermit {
let n_bytes = n_bytes.min(self.byte_budget.floor_byte_budget());
self.byte_budget.acquire_strict(n_bytes).await;
let bytes = ByteReservation {
budget: self.byte_budget.clone(),
n_bytes,
};
let req_permit = self
.request_budget
.semaphore
.clone()
.acquire_owned()
.await
.expect("semaphore closed");
InFlightPermit {
_byte_reservation: bytes,
_req_permit: req_permit,
}
}
pub fn current_byte_budget(&self) -> u64 {
self.byte_budget.current_budget()
}
pub fn floor_byte_budget(&self) -> u64 {
self.byte_budget.floor_byte_budget()
}
pub fn resize_byte_budget(&self, new: u64) {
self.byte_budget.resize(new);
}
pub fn stats(&self) -> InFlightStats {
let bytes_budget = self.byte_budget.current_budget();
let bytes_in_use = self.byte_budget.inflight_in_use();
let bytes_saturation = if bytes_budget > 0 {
bytes_in_use as f64 / bytes_budget as f64
} else {
0.0
};
let request_budget = self.request_budget.budget;
let requests_in_use = request_budget - self.request_budget.semaphore.available_permits();
let requests_saturation = if request_budget > 0 {
requests_in_use as f64 / request_budget as f64
} else {
0.0
};
InFlightStats {
bytes_budget,
bytes_in_use,
bytes_saturation,
request_budget,
requests_in_use,
requests_saturation,
}
}
}
struct ByteReservation {
budget: Arc<ByteBudget>,
n_bytes: u64,
}
impl Drop for ByteReservation {
fn drop(&mut self) {
self.budget.release(self.n_bytes);
}
}
pub struct InFlightPermit {
_byte_reservation: ByteReservation,
_req_permit: OwnedSemaphorePermit,
}