use std::{
pin::Pin,
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use super::{Identity, IdentityGuard, IdentityKey, SiteKey};
pin_project! {
#[must_use = "futures do nothing unless polled"]
pub struct IdentityFuture<F> {
#[pin]
fut: F,
identity: Identity,
}
}
impl<F> IdentityFuture<F> {
pub fn new(site: SiteKey, fut: F) -> Self {
Self {
fut,
identity: Identity::current_raw().child(site),
}
}
pub fn keyed(site: SiteKey, key: impl IdentityKey, fut: F) -> Self {
Self {
fut,
identity: Identity::current_raw().keyed_child(site, key),
}
}
pub fn ambiguous(site: SiteKey, label: &'static str, fut: F) -> Self {
Self {
fut,
identity: Identity::current_raw().ambiguous_child(site, label),
}
}
}
impl<F: Future> Future for IdentityFuture<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, task_cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let _guard = IdentityGuard::install(*this.identity);
this.fut.poll(task_cx)
}
}
#[cfg(test)]
mod tests {
use std::{
panic::{AssertUnwindSafe, catch_unwind},
pin::pin,
task::Waker,
};
use super::*;
const SITE_A: SiteKey = SiteKey::new(file!(), line!(), column!(), 0);
const SITE_B: SiteKey = SiteKey::new(file!(), line!(), column!(), 0);
fn block_on<F: Future>(fut: F) -> F::Output {
let mut fut = pin!(fut);
let mut task = Context::from_waker(Waker::noop());
loop {
if let Poll::Ready(output) = fut.as_mut().poll(&mut task) {
return output;
}
}
}
struct YieldOnce(bool);
impl Future for YieldOnce {
type Output = ();
fn poll(mut self: Pin<&mut Self>, _task_cx: &mut Context<'_>) -> Poll<()> {
if self.0 {
Poll::Ready(())
} else {
self.0 = true;
Poll::Pending
}
}
}
#[test]
fn the_future_installs_its_identity_only_while_polling() {
let fut = IdentityFuture::new(SITE_A, async {
assert_eq!(Identity::current(), Identity::ROOT.child(SITE_A));
});
assert_eq!(Identity::current(), Identity::ROOT);
block_on(fut);
assert_eq!(Identity::current(), Identity::ROOT);
}
#[test]
fn the_identity_is_derived_at_construction() {
let fut = {
let _parent = IdentityGuard::enter(SITE_A);
IdentityFuture::new(SITE_B, async { Identity::current() })
};
assert_eq!(block_on(fut), Identity::ROOT.child(SITE_A).child(SITE_B));
}
#[test]
fn interleaved_siblings_each_see_their_own_identity() {
let sibling = |key: u32| {
IdentityFuture::keyed(SITE_A, key, async move {
let before = Identity::current();
YieldOnce(false).await;
assert_eq!(Identity::current(), before);
before
})
};
let mut first = pin!(sibling(1));
let mut second = pin!(sibling(2));
let mut task = Context::from_waker(Waker::noop());
assert!(first.as_mut().poll(&mut task).is_pending());
assert!(second.as_mut().poll(&mut task).is_pending());
let Poll::Ready(first) = first.as_mut().poll(&mut task) else {
panic!("ready on the second poll");
};
let Poll::Ready(second) = second.as_mut().poll(&mut task) else {
panic!("ready on the second poll");
};
assert_ne!(first, second);
assert_eq!(first, Identity::ROOT.keyed_child(SITE_A, 1));
assert_eq!(second, Identity::ROOT.keyed_child(SITE_A, 2));
}
#[test]
fn the_future_restores_the_identity_when_a_poll_panics() {
let mut fut = pin!(IdentityFuture::new(SITE_A, async { panic!("boom") }));
let result = catch_unwind(AssertUnwindSafe(|| {
let mut task = Context::from_waker(Waker::noop());
let _ = fut.as_mut().poll(&mut task);
}));
assert!(result.is_err());
assert_eq!(Identity::current(), Identity::ROOT);
}
#[test]
fn an_ambiguous_future_poisons_its_descendants() {
let fut = IdentityFuture::ambiguous(SITE_A, "`card` at src/a.rs:1", async {
let error = Identity::try_current().unwrap_err();
let keyed =
IdentityFuture::keyed(SITE_B, 7, async { Identity::try_current().unwrap_err() });
(error, keyed.await)
});
let (outer, inner) = block_on(fut);
assert_eq!(outer.label(), "`card` at src/a.rs:1");
assert_eq!(inner.label(), "`card` at src/a.rs:1");
}
}