use crate::Result;
use std::future::Future;
pub(crate) async fn bounded_in_order<'a, T, R, F, Fut>(
items: &'a [T],
concurrency: usize,
op: F,
) -> Result<Vec<R>>
where
F: Fn(&'a T) -> Fut,
Fut: Future<Output = Result<R>>,
{
let concurrency = concurrency.max(1);
let mut out = Vec::with_capacity(items.len());
for chunk in items.chunks(concurrency) {
let running = chunk.iter().map(&op);
out.extend(futures::future::try_join_all(running).await?);
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
#[tokio::test]
async fn results_keep_input_order() -> Result<()> {
let items: Vec<u32> = (0..10).collect();
let got = bounded_in_order(&items, 4, |n| {
let n = *n;
async move { Ok(n * 2) }
})
.await?;
assert_eq!(got, (0..10).map(|n| n * 2).collect::<Vec<_>>());
Ok(())
}
#[tokio::test]
async fn no_more_than_concurrency_run_at_once() -> Result<()> {
let live = AtomicUsize::new(0);
let peak = AtomicUsize::new(0);
let items: Vec<u32> = (0..6).collect();
bounded_in_order(&items, 2, |_| {
let (live, peak) = (&live, &peak);
async move {
let now = live.fetch_add(1, Ordering::SeqCst) + 1;
peak.fetch_max(now, Ordering::SeqCst);
tokio::task::yield_now().await;
live.fetch_sub(1, Ordering::SeqCst);
Ok(())
}
})
.await?;
assert_eq!(
peak.load(Ordering::SeqCst),
2,
"at most `concurrency` operations may be in flight"
);
Ok(())
}
#[tokio::test]
async fn zero_concurrency_reads_sequentially() -> Result<()> {
let items: Vec<u32> = (0..5).collect();
let got = bounded_in_order(&items, 0, |n| {
let n = *n;
async move { Ok(n) }
})
.await?;
assert_eq!(got, items, "0 must clamp to 1, not drop every item");
Ok(())
}
}