blob: 24b2d15550948fed66d1b7c081706055c91bda08 [file] [edit]
use crate::util::data_structures::HashMap;
use futures::{FutureExt, future::LocalBoxFuture, stream::FuturesUnordered};
use std::{hash::Hash, ops::Deref, task::Poll};
/// A local (!Send) adapter for caching and executing an async method
/// from a non-async context.
///
/// The `self_parameter`, `key`, and successful (Ok) results must all be cheap to `clone`.
///
/// Ensures at most one in-flight computation per key. Results are:
/// - cached on success
/// - not retained on error
pub struct LocalPollAdapter<'a, S, K, R> {
pool: FuturesUnordered<LocalBoxFuture<'a, (K, R)>>,
cache: HashMap<K, Poll<R>>,
self_parameter: S,
}
impl<'a, S, K, V, E> LocalPollAdapter<'a, S, K, Result<V, E>>
where
S: Clone + Deref + 'a,
K: Clone + Hash + Eq + 'a,
V: Clone,
{
pub fn new(self_parameter: S) -> Self {
Self {
pool: FuturesUnordered::new(),
cache: HashMap::default(),
self_parameter,
}
}
/// Polls the result for `key`, spawning work if needed.
///
/// If this function returns [`Poll::Pending`], call [`LocalPollAdapter::wait`]
/// to execute the work, then call this function again with the same key
/// to pick up the result.
///
/// Futures that complete immediately are not queued.
pub fn poll<F>(&mut self, f: F, key: K) -> Poll<Result<V, E>>
where
F: AsyncFn(&S::Target, &K) -> Result<V, E> + 'a,
{
match self.cache.get(&key) {
// We have a cached success value, clone it and return.
Some(Poll::Ready(Ok(v))) => return Poll::Ready(Ok(v.clone())),
// We have a cached error value, remove it and return.
// Errors are not Clone, so they are only stored once.
Some(Poll::Ready(Err(_))) => return self.cache.remove(&key).unwrap(),
// This key is already pending.
Some(Poll::Pending) => return Poll::Pending,
// Looks like we have work to do!
None => {}
}
// Created a pinned future that executes the function,
// returning the key and the result.
let mut future = {
let key = key.clone();
let self_parameter = self.self_parameter.clone();
async move {
let v = f(self_parameter.deref(), &key).await;
(key, v)
}
.boxed_local()
};
// Attempt to run the future immediately. If it has no `await` yields,
// it will return here.
if let Some((k, v)) = (&mut future).now_or_never() {
if let Ok(success) = &v {
// Only cache successful results.
self.cache.insert(k, Poll::Ready(Ok(success.clone())));
}
return Poll::Ready(v);
}
// Insert Pending into the cache so we avoid queuing the same future twice.
self.cache.insert(key.clone(), Poll::Pending);
// Add the future to the pending queue.
self.pool.push(future);
Poll::Pending
}
/// Returns the number of pending futures.
pub fn pending_count(&self) -> usize {
self.pool.len()
}
/// Run all pending futures. Returns true if there was no work to do.
pub fn wait(&mut self) -> bool {
let is_empty = self.pool.is_empty();
for (k, v) in crate::util::block_on_stream(&mut self.pool) {
*self
.cache
.get_mut(&k)
.expect("all pending work is in the cache") = Poll::Ready(v);
}
is_empty
}
}
#[cfg(test)]
mod tests {
use super::LocalPollAdapter;
use std::{rc::Rc, task::Poll};
/// Future that yields once.
fn yield_once() -> impl std::future::Future<Output = ()> {
let mut yielded = false;
std::future::poll_fn(move |cx| {
if yielded {
Poll::Ready(())
} else {
yielded = true;
cx.waker().wake_by_ref();
Poll::Pending
}
})
}
struct Thing {}
impl Thing {
async fn widen(&self, i: &i32) -> Result<i64, ()> {
if *i > 10 {
// Big numbers take longer to process (need to test futures that yield).
yield_once().await;
}
if *i % 2 != 0 {
// Odd numbers are not supported (need to test errors).
return Err(());
}
Ok(*i as i64)
}
}
/// Poll wrapper around `Thing`
struct PolledThing<'a> {
poller: LocalPollAdapter<'a, Rc<Thing>, i32, Result<i64, ()>>,
}
impl<'a> PolledThing<'a> {
fn new() -> Self {
Self {
poller: LocalPollAdapter::new(Rc::new(Thing {})),
}
}
// Non-async version of the widen method.
fn widen(&mut self, i: &i32) -> Poll<Result<i64, ()>> {
self.poller.poll(Thing::widen, i.clone())
}
fn wait(&mut self) -> bool {
self.poller.wait()
}
}
#[test]
fn immediate_success() {
let mut p = PolledThing::new();
assert_eq!(p.widen(&2), Poll::Ready(Ok(2)));
assert!(p.wait());
}
#[test]
fn immediate_error() {
let mut p = PolledThing::new();
assert_eq!(p.widen(&1), Poll::Ready(Err(())));
assert!(p.wait());
}
#[test]
fn deferred_error() {
let mut p = PolledThing::new();
assert_eq!(p.widen(&1001), Poll::Pending);
assert!(!p.wait());
assert_eq!(p.widen(&1001), Poll::Ready(Err(())));
assert!(p.wait());
// Errors are not cached
assert_eq!(p.widen(&1001), Poll::Pending);
assert!(!p.wait());
assert_eq!(p.widen(&1001), Poll::Ready(Err(())));
assert!(p.wait());
}
#[test]
fn deferred_success() {
let mut p = PolledThing::new();
assert_eq!(p.widen(&50), Poll::Pending);
assert!(!p.wait());
assert_eq!(p.widen(&50), Poll::Ready(Ok(50)));
assert!(p.wait());
// Success is cached.
assert_eq!(p.widen(&50), Poll::Ready(Ok(50)));
assert!(p.wait());
}
}