#![cfg(all(feature = "async", feature = "instrumentation"))]
use lazily::AsyncContext;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::{Mutex, oneshot};
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn window1_resolved_between_fastpath_and_relock() {
let ctx = Arc::new(AsyncContext::new());
let slot = ctx.computed_async(|_| async { 42i32 });
let ctx_hook = ctx.clone();
ctx.__install_window1_hook(Arc::new(move || {
let ctx_hook = ctx_hook.clone();
Box::pin(async move {
let resolver = tokio::spawn({
let ctx_hook = ctx_hook.clone();
async move { ctx_hook.get_async(&slot).await }
});
assert_eq!(resolver.await.unwrap(), 42, "resolver should compute slot");
let deadline = Instant::now() + Duration::from_secs(2);
while ctx_hook.get(&slot).is_none() {
assert!(Instant::now() < deadline, "slot never resolved in gap");
tokio::task::yield_now().await;
}
})
}));
let value = ctx.get_async(&slot).await;
assert_eq!(value, 42);
assert_eq!(
ctx.__window1_resolved_hits(),
1,
"get_async must return through the window-1 Resolved-after-re-lock arm"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn window2_superseded_notifier_drop_reresolves_to_latest() {
let ctx = Arc::new(AsyncContext::new());
let cell = ctx.cell(1i32);
let (release_first, first_gate) = oneshot::channel::<()>();
let first_gate = Arc::new(Mutex::new(Some(first_gate)));
let slot = ctx.computed_async({
let first_gate = first_gate.clone();
move |ctx| {
let observed = ctx.get_cell(&cell);
let first_gate = first_gate.clone();
async move {
if observed == 1 {
let gate = first_gate.lock().await.take();
if let Some(gate) = gate {
let _ = gate.await;
}
}
observed * 10
}
}
});
let reader = tokio::spawn({
let ctx = ctx.clone();
async move { ctx.get_async(&slot).await }
});
tokio::task::yield_now().await;
ctx.set_cell(&cell, 2);
let _ = release_first.send(());
assert_eq!(
reader.await.unwrap(),
20,
"reader must re-resolve to the latest superseding value, not panic"
);
assert_eq!(ctx.get(&slot), Some(20));
}