use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::task::{Context, Poll};
use tokio::sync::Notify;
#[derive(Debug)]
pub(crate) struct Flow {
workers: usize,
probing: bool,
one_claim: bool,
budget: usize,
holds: bool,
}
impl Flow {
pub(crate) fn new(max_connections: u32, holds: bool, one_claim: bool) -> Self {
let size = usize::try_from(max_connections).unwrap_or(usize::MAX);
let budget = size.saturating_sub(1).max(1);
Self {
workers: 1,
probing: true,
one_claim,
budget,
holds,
}
}
pub(crate) fn polled(&mut self, in_work: usize) {
self.workers = self.workers.max(in_work.saturating_add(1));
}
pub(crate) fn may_start(
&self,
running: usize,
in_work: usize,
spare: impl FnOnce() -> usize,
) -> bool {
running < self.wanted(in_work)
&& self.held(running, in_work) < self.budget
&& (running == 0 || spare() >= 2)
}
pub(crate) fn wanted(&self, in_work: usize) -> usize {
if self.probing {
return 1;
}
self.workers
.saturating_sub(in_work)
.clamp(1, if self.one_claim { 1 } else { self.budget })
}
pub(crate) fn waits_for_a_settlement(&self, running: usize, in_work: usize) -> bool {
running == 0 && self.held(running, in_work) >= self.budget
}
pub(crate) fn took(&mut self) {
self.probing = false;
}
pub(crate) fn found_nothing(&mut self) {
self.probing = true;
}
fn held(&self, running: usize, in_work: usize) -> usize {
running + if self.holds { in_work } else { 0 }
}
}
const WAITING: usize = 1;
#[derive(Debug, Default)]
pub(crate) struct Settled {
count: AtomicUsize,
wake: Notify,
}
impl Settled {
pub(crate) fn count(&self) -> usize {
self.count.load(Ordering::Acquire) >> 1
}
pub(crate) async fn change(&self, seen: usize) {
let _waiting = Waiting::mark(self);
while self.count() == seen {
self.wake.notified().await;
}
}
fn settle(&self) {
if self.count.fetch_add(2, Ordering::AcqRel) & WAITING != 0 {
self.wake.notify_one();
}
}
}
struct Waiting<'a>(&'a Settled);
impl<'a> Waiting<'a> {
fn mark(settled: &'a Settled) -> Self {
settled.count.fetch_or(WAITING, Ordering::AcqRel);
Self(settled)
}
}
impl Drop for Waiting<'_> {
fn drop(&mut self) {
self.0.count.fetch_and(!WAITING, Ordering::AcqRel);
}
}
#[derive(Debug)]
pub(crate) struct InWork(&'static Settled);
impl InWork {
pub(crate) const fn new(settled: &'static Settled) -> Self {
Self(settled)
}
}
impl Drop for InWork {
fn drop(&mut self) {
self.0.settle();
}
}
type Slot<Fut> = Pin<Box<Option<Fut>>>;
pub(crate) struct Claims<Args, Buffers, Make, Fut> {
make: Make,
_args: PhantomData<fn(Args)>,
slots: Vec<Slot<Fut>>,
idle: Vec<Buffers>,
running: usize,
}
impl<Args, Buffers, Make, Fut> Claims<Args, Buffers, Make, Fut> {
pub(crate) const fn new(make: Make) -> Self {
Self {
make,
_args: PhantomData,
slots: Vec::new(),
idle: Vec::new(),
running: 0,
}
}
pub(crate) const fn running(&self) -> usize {
self.running
}
pub(crate) fn buffers(&mut self) -> Buffers
where
Buffers: Default,
{
self.idle.pop().unwrap_or_default()
}
pub(crate) fn put_back(&mut self, buffers: Buffers) {
self.idle.push(buffers);
}
#[cfg(test)]
fn slots(&self) -> usize {
self.slots.len()
}
}
impl<Args, Buffers, Make, Fut> Claims<Args, Buffers, Make, Fut>
where
Buffers: Default,
Make: Fn(Args, Buffers) -> Fut,
Fut: Future,
{
pub(crate) fn start(&mut self, args: Args) -> usize {
let claim = (self.make)(args, self.idle.pop().unwrap_or_default());
self.running += 1;
if let Some(index) = self.slots.iter().position(|slot| slot.is_none()) {
self.slots[index].as_mut().set(Some(claim));
return index;
}
self.slots.push(Box::pin(Some(claim)));
self.slots.len() - 1
}
pub(crate) fn poll_slot(&mut self, index: usize, cx: &mut Context<'_>) -> Poll<Fut::Output> {
let slot = &mut self.slots[index];
let Some(claim) = slot.as_mut().as_pin_mut() else {
return Poll::Pending;
};
let done = std::task::ready!(claim.poll(cx));
slot.as_mut().set(None);
self.running -= 1;
Poll::Ready(done)
}
pub(crate) fn poll_any(&mut self, cx: &mut Context<'_>) -> Poll<Option<Fut::Output>> {
if self.running == 0 {
return Poll::Ready(None);
}
for index in 0..self.slots.len() {
if let Poll::Ready(done) = self.poll_slot(index, cx) {
return Poll::Ready(Some(done));
}
}
Poll::Pending
}
}
#[cfg(test)]
mod tests {
use std::future::{poll_fn, ready};
use std::pin::pin;
use std::time::Duration;
use super::{Claims, Flow, InWork, Settled};
const ROOMY: fn() -> usize = || 8;
fn row_lock() -> Flow {
Flow::new(8, true, false)
}
fn burst(flow: &Flow, in_work: usize, spare: usize) -> usize {
let mut running = 0;
while flow.may_start(running, in_work, || spare.saturating_sub(running)) {
running += 1;
}
running
}
#[test]
fn a_plain_mount_keeps_one_claim() {
let mut flow = row_lock();
for _ in 0..3 {
flow.polled(0);
flow.took();
assert_eq!(burst(&flow, 0, 8), 1);
}
}
#[test]
fn a_free_worker_gets_a_claim_of_its_own() {
let mut flow = row_lock();
for in_work in 0..4 {
flow.polled(in_work);
flow.took();
}
flow.polled(0);
assert_eq!(burst(&flow, 0, 8), 4);
assert_eq!(burst(&flow, 2, 8), 2);
}
#[test]
fn a_claim_that_took_nothing_leaves_one_claim_in_flight() {
let mut flow = row_lock();
for in_work in 0..4 {
flow.polled(in_work);
flow.took();
}
flow.found_nothing();
flow.polled(0);
assert_eq!(burst(&flow, 0, 8), 1);
flow.took();
assert_eq!(burst(&flow, 0, 8), 4);
}
#[test]
fn a_table_in_order_or_a_database_of_one_writer_keeps_one_claim() {
let mut flow = Flow::new(8, true, true);
for in_work in 0..4 {
flow.polled(in_work);
flow.took();
}
flow.polled(0);
assert_eq!(burst(&flow, 0, 8), 1);
}
#[test]
fn a_subscription_holds_one_connection_less_than_the_pool() {
let mut flow = row_lock();
for in_work in 0..8 {
flow.polled(in_work);
flow.took();
}
assert!(!flow.may_start(0, 7, ROOMY));
assert!(flow.waits_for_a_settlement(0, 7));
assert!(flow.may_start(0, 6, ROOMY));
assert!(!flow.waits_for_a_settlement(0, 6));
}
#[test]
fn a_lease_counts_its_claims_alone() {
let mut flow = Flow::new(8, false, false);
for in_work in 0..12 {
flow.polled(in_work);
flow.took();
}
assert!(!flow.waits_for_a_settlement(0, 11));
flow.polled(0);
assert_eq!(burst(&flow, 0, 8), 7);
}
#[test]
fn a_claim_beside_another_needs_a_connection_to_spare() {
let mut flow = row_lock();
for in_work in 0..4 {
flow.polled(in_work);
flow.took();
}
flow.polled(0);
assert_eq!(burst(&flow, 0, 0), 1);
assert_eq!(burst(&flow, 0, 2), 1);
assert_eq!(burst(&flow, 0, 3), 2);
assert_eq!(burst(&flow, 0, 4), 3);
}
#[test]
fn a_pool_of_one_serves_one_delivery() {
let mut flow = Flow::new(1, true, false);
flow.polled(0);
flow.took();
assert!(flow.may_start(0, 0, || 1));
assert!(flow.waits_for_a_settlement(0, 1));
}
#[tokio::test]
async fn a_settlement_counts_once_and_wakes_a_waiter() {
let settled: &'static Settled = Box::leak(Box::default());
assert_eq!(settled.count(), 0);
let mut waiting = pin!(settled.change(0));
assert!(
futures::poll!(waiting.as_mut()).is_pending(),
"nothing settled yet"
);
drop(InWork::new(settled));
assert_eq!(settled.count(), 1);
tokio::time::timeout(Duration::from_secs(5), waiting)
.await
.expect("the settlement wakes the waiter");
settled.change(0).await;
}
#[test]
fn a_slot_is_reused_by_the_next_claim() {
let mut claims = Claims::<(), Vec<u8>, _, _>::new(|(): (), mut buffer: Vec<u8>| {
buffer.push(1);
ready(buffer)
});
futures::executor::block_on(async {
for round in 1..=3 {
let started = claims.start(());
let buffer = poll_fn(|cx| claims.poll_slot(started, cx)).await;
assert_eq!(buffer.len(), round, "the buffer comes back each round");
claims.put_back(buffer);
assert_eq!(claims.running(), 0);
}
assert_eq!(claims.slots(), 1);
let (first, second) = (claims.start(()), claims.start(()));
assert_ne!(first, second);
assert_eq!(claims.running(), 2);
let done = poll_fn(|cx| claims.poll_any(cx)).await;
claims.put_back(done.expect("a claim ran"));
let done = poll_fn(|cx| claims.poll_any(cx)).await;
claims.put_back(done.expect("a claim ran"));
assert_eq!(claims.slots(), 2);
assert!(
poll_fn(|cx| claims.poll_any(cx)).await.is_none(),
"nothing runs"
);
});
}
}