use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin;
use std::sync::Mutex;
use std::sync::atomic::{AtomicU64, Ordering};
type Parked = Pin<Box<dyn Future<Output = ()> + Send>>;
pub struct DeferredCompletions {
queue: Mutex<VecDeque<Parked>>,
capacity: usize,
dropped: AtomicU64,
}
impl std::fmt::Debug for DeferredCompletions {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("DeferredCompletions")
.field("pending", &self.len())
.field("capacity", &self.capacity)
.field("dropped", &self.dropped.load(Ordering::Relaxed))
.finish_non_exhaustive()
}
}
impl DeferredCompletions {
pub(crate) fn new(capacity: usize) -> Self {
Self {
queue: Mutex::new(VecDeque::new()),
capacity,
dropped: AtomicU64::new(0),
}
}
pub(crate) fn park(&self, completion: Parked) -> bool {
let mut queue = self
.queue
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if self.capacity == 0 {
self.record_drop();
return false;
}
let mut evicted = false;
while queue.len() >= self.capacity {
queue.pop_front();
evicted = true;
}
queue.push_back(completion);
if evicted {
self.record_drop();
}
!evicted
}
fn take_one(&self) -> Option<Parked> {
self.queue
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.pop_front()
}
fn requeue_in_flight(&self, completion: Parked) {
let mut queue = self
.queue
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if self.capacity == 0 {
self.record_drop();
return;
}
if queue.len() >= self.capacity {
queue.pop_back();
self.record_drop();
}
queue.push_front(completion);
}
fn record_drop(&self) {
let mut current = self.dropped.load(Ordering::Relaxed);
loop {
let next = current.saturating_add(1);
if next == current {
return;
}
match self.dropped.compare_exchange_weak(
current,
next,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => return,
Err(observed) => current = observed,
}
}
}
pub async fn drain_some(&self, limit: usize) -> usize {
let mut completed = 0;
while completed < limit {
let Some(completion) = self.take_one() else {
break;
};
InFlight::new(self, completion).run().await;
completed += 1;
}
completed
}
pub async fn drain(&self) -> usize {
let mut total = 0;
loop {
let ran = self.drain_some(self.capacity.max(1)).await;
if ran == 0 {
return total;
}
total = total.saturating_add(ran);
}
}
#[must_use]
pub fn len(&self) -> usize {
self.queue
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn dropped(&self) -> u64 {
self.dropped.load(Ordering::Relaxed)
}
}
struct InFlight<'a> {
deferred: &'a DeferredCompletions,
completion: Option<Parked>,
}
impl<'a> InFlight<'a> {
fn new(deferred: &'a DeferredCompletions, completion: Parked) -> Self {
Self {
deferred,
completion: Some(completion),
}
}
async fn run(mut self) {
if let Some(completion) = self.completion.as_mut() {
completion.as_mut().await;
}
self.completion = None;
}
}
impl Drop for InFlight<'_> {
fn drop(&mut self) {
if let Some(completion) = self.completion.take() {
if std::thread::panicking() {
self.deferred.record_drop();
} else {
self.deferred.requeue_in_flight(completion);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
struct PendingUntilAllowed {
polls: Arc<AtomicUsize>,
allowed: Arc<std::sync::atomic::AtomicBool>,
}
impl Future for PendingUntilAllowed {
type Output = ();
fn poll(
self: Pin<&mut Self>,
_context: &mut std::task::Context<'_>,
) -> std::task::Poll<()> {
self.polls.fetch_add(1, Ordering::Relaxed);
if self.allowed.load(Ordering::Relaxed) {
std::task::Poll::Ready(())
} else {
std::task::Poll::Pending
}
}
}
fn counting(counter: &Arc<AtomicUsize>) -> Parked {
let counter = Arc::clone(counter);
Box::pin(async move {
counter.fetch_add(1, Ordering::Relaxed);
})
}
#[tokio::test]
async fn parked_completions_run_when_drained() {
let ran = Arc::new(AtomicUsize::new(0));
let deferred = DeferredCompletions::new(8);
for _ in 0..3 {
assert!(deferred.park(counting(&ran)));
}
assert_eq!(deferred.len(), 3);
assert_eq!(deferred.drain().await, 3);
assert_eq!(ran.load(Ordering::Relaxed), 3);
assert!(deferred.is_empty());
assert_eq!(deferred.dropped(), 0);
}
#[tokio::test]
async fn draining_is_bounded_by_the_limit() {
let ran = Arc::new(AtomicUsize::new(0));
let deferred = DeferredCompletions::new(8);
for _ in 0..5 {
deferred.park(counting(&ran));
}
assert_eq!(deferred.drain_some(2).await, 2);
assert_eq!(ran.load(Ordering::Relaxed), 2);
assert_eq!(deferred.len(), 3, "the rest must stay queued");
}
#[tokio::test]
async fn a_full_queue_evicts_the_oldest_and_reports_the_loss() {
let ran = Arc::new(AtomicUsize::new(0));
let deferred = DeferredCompletions::new(2);
assert!(deferred.park(counting(&ran)));
assert!(deferred.park(counting(&ran)));
assert!(
!deferred.park(counting(&ran)),
"the third must report that something was discarded"
);
assert_eq!(deferred.len(), 2, "the queue must stay bounded");
assert_eq!(deferred.dropped(), 1);
assert_eq!(deferred.drain().await, 2);
}
#[tokio::test]
async fn a_zero_capacity_queue_accepts_nothing() {
let ran = Arc::new(AtomicUsize::new(0));
let deferred = DeferredCompletions::new(0);
assert!(!deferred.park(counting(&ran)));
assert!(deferred.is_empty());
assert_eq!(deferred.dropped(), 1);
assert_eq!(deferred.drain().await, 0);
assert_eq!(ran.load(Ordering::Relaxed), 0);
}
#[tokio::test]
async fn draining_an_empty_queue_is_a_no_op() {
let deferred = DeferredCompletions::new(4);
assert_eq!(deferred.drain().await, 0);
assert_eq!(deferred.drain_some(10).await, 0);
}
#[tokio::test]
async fn cancelling_a_drain_requeues_the_in_flight_completion() {
let deferred = Arc::new(DeferredCompletions::new(4));
let polls = Arc::new(AtomicUsize::new(0));
let allowed = Arc::new(std::sync::atomic::AtomicBool::new(false));
assert!(deferred.park(Box::pin(PendingUntilAllowed {
polls: Arc::clone(&polls),
allowed: Arc::clone(&allowed),
})));
let drain = tokio::spawn({
let deferred = Arc::clone(&deferred);
async move { deferred.drain_some(1).await }
});
while polls.load(Ordering::Relaxed) == 0 {
tokio::task::yield_now().await;
}
drain.abort();
assert!(drain.await.unwrap_err().is_cancelled());
assert_eq!(deferred.len(), 1);
allowed.store(true, Ordering::Relaxed);
assert_eq!(deferred.drain_some(1).await, 1);
assert!(deferred.is_empty());
assert_eq!(deferred.dropped(), 0);
}
}