use std::sync::Arc;
use tokio::sync::Notify;
#[derive(Debug, Default)]
pub struct NodeSideEffects {
wipe_join_secret: Notify,
}
impl NodeSideEffects {
#[must_use]
pub fn new() -> Arc<Self> {
Arc::new(Self::default())
}
pub fn fire_wipe_join_secret(&self) {
self.wipe_join_secret.notify_waiters();
}
pub async fn wait_wipe_join_secret(&self) {
self.wipe_join_secret.notified().await;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn fire_wakes_single_waiter() {
let effects = NodeSideEffects::new();
let cloned = Arc::clone(&effects);
let handle = tokio::spawn(async move {
cloned.wait_wipe_join_secret().await;
});
tokio::task::yield_now().await;
effects.fire_wipe_join_secret();
tokio::time::timeout(std::time::Duration::from_secs(1), handle)
.await
.expect("waiter woke within timeout")
.expect("waiter task did not panic");
}
#[tokio::test]
async fn fire_wakes_multiple_waiters() {
let effects = NodeSideEffects::new();
let mut handles = Vec::new();
for _ in 0..4 {
let cloned = Arc::clone(&effects);
handles.push(tokio::spawn(async move {
cloned.wait_wipe_join_secret().await;
}));
}
tokio::task::yield_now().await;
effects.fire_wipe_join_secret();
for h in handles {
tokio::time::timeout(std::time::Duration::from_secs(1), h)
.await
.expect("waiter woke within timeout")
.expect("waiter task did not panic");
}
}
#[tokio::test]
async fn fire_without_waiter_is_harmless() {
let effects = NodeSideEffects::new();
effects.fire_wipe_join_secret();
let waited = tokio::time::timeout(
std::time::Duration::from_millis(50),
effects.wait_wipe_join_secret(),
)
.await;
assert!(
waited.is_err(),
"Notify::notify_waiters only wakes current waiters; \
a post-fire await must NOT complete without a fresh fire",
);
}
}