#[cfg(not(iroh_loom))]
use std::sync;
use std::{
collections::VecDeque,
future::Future,
pin::Pin,
sync::{Arc, Weak},
task::{self, Poll, Waker},
};
#[cfg(iroh_loom)]
use loom::sync;
use n0_future::stream::Stream;
use sync::{Mutex, RwLock};
#[derive(Debug, Default)]
pub struct Watchable<T> {
shared: Arc<Shared<T>>,
}
impl<T> Clone for Watchable<T> {
fn clone(&self) -> Self {
Self {
shared: self.shared.clone(),
}
}
}
impl<T: Clone + Eq> Watchable<T> {
pub fn new(value: T) -> Self {
Self {
shared: Arc::new(Shared {
state: RwLock::new(State {
value,
epoch: INITIAL_EPOCH,
}),
watchers: Default::default(),
}),
}
}
pub fn set(&self, value: T) -> Result<T, T> {
let mut state = self.shared.state.write().expect("poisoned");
let changed = state.value != value;
let ret = if changed {
let old = std::mem::replace(&mut state.value, value);
state.epoch += 1;
Ok(old)
} else {
Err(value)
};
drop(state);
if changed {
for watcher in self.shared.watchers.lock().expect("poisoned").drain(..) {
watcher.wake();
}
}
ret
}
pub fn watch(&self) -> Watcher<T> {
Watcher {
epoch: self.shared.state.read().expect("poisoned").epoch,
shared: Arc::downgrade(&self.shared),
}
}
pub fn get(&self) -> T {
self.shared.get()
}
}
#[derive(Debug, Clone)]
pub struct Watcher<T> {
epoch: u64,
shared: Weak<Shared<T>>,
}
impl<T: Clone + Eq> Watcher<T> {
pub fn get(&self) -> Result<T, Disconnected> {
let shared = self.shared.upgrade().ok_or(Disconnected)?;
Ok(shared.get())
}
pub fn updated(&mut self) -> WatchNextFut<T> {
WatchNextFut { watcher: self }
}
pub fn stream(mut self) -> WatcherStream<T> {
debug_assert!(self.epoch > 0);
self.epoch -= 1;
WatcherStream { watcher: self }
}
pub fn stream_updates_only(self) -> WatcherStream<T> {
WatcherStream { watcher: self }
}
}
impl<T: Clone + Eq> Watcher<Option<T>> {
pub fn initialized(&mut self) -> WatchInitializedFut<T> {
self.epoch = PRE_INITIAL_EPOCH;
WatchInitializedFut { watcher: self }
}
}
#[derive(Debug)]
pub struct WatchNextFut<'a, T> {
watcher: &'a mut Watcher<T>,
}
impl<T: Clone + Eq> Future for WatchNextFut<'_, T> {
type Output = Result<T, Disconnected>;
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
let Some(shared) = self.watcher.shared.upgrade() else {
return Poll::Ready(Err(Disconnected));
};
match shared.poll_next(cx, self.watcher.epoch) {
Poll::Pending => Poll::Pending,
Poll::Ready((current_epoch, value)) => {
self.watcher.epoch = current_epoch;
Poll::Ready(Ok(value))
}
}
}
}
#[derive(Debug)]
pub struct WatchInitializedFut<'a, T> {
watcher: &'a mut Watcher<Option<T>>,
}
impl<T: Clone + Eq> Future for WatchInitializedFut<'_, T> {
type Output = Result<T, Disconnected>;
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
loop {
let Some(shared) = self.watcher.shared.upgrade() else {
return Poll::Ready(Err(Disconnected));
};
let (epoch, value) = n0_future::ready!(shared.poll_next(cx, self.watcher.epoch));
self.watcher.epoch = epoch;
if let Some(value) = value {
return Poll::Ready(Ok(value));
}
}
}
}
#[derive(Debug, Clone)]
pub struct WatcherStream<T> {
watcher: Watcher<T>,
}
impl<T: Clone + Eq> Stream for WatcherStream<T> {
type Item = T;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
let Some(shared) = self.watcher.shared.upgrade() else {
return Poll::Ready(None);
};
match shared.poll_next(cx, self.watcher.epoch) {
Poll::Pending => Poll::Pending,
Poll::Ready((epoch, value)) => {
self.watcher.epoch = epoch;
Poll::Ready(Some(value))
}
}
}
}
#[derive(thiserror::Error, Debug)]
#[error("Watch lost connection to underlying Watchable, it was dropped")]
pub struct Disconnected;
const INITIAL_EPOCH: u64 = 1;
const PRE_INITIAL_EPOCH: u64 = 0;
#[derive(Debug, Default)]
struct Shared<T> {
state: RwLock<State<T>>,
watchers: Mutex<VecDeque<Waker>>,
}
#[derive(Debug)]
struct State<T> {
value: T,
epoch: u64,
}
impl<T: Default> Default for State<T> {
fn default() -> Self {
Self {
value: Default::default(),
epoch: INITIAL_EPOCH,
}
}
}
impl<T: Clone> Shared<T> {
fn get(&self) -> T {
self.state.read().expect("poisoned").value.clone()
}
fn poll_next(&self, cx: &mut task::Context<'_>, last_epoch: u64) -> Poll<(u64, T)> {
{
let state = self.state.read().expect("poisoned");
let epoch = state.epoch;
if last_epoch < epoch {
return Poll::Ready((epoch, state.value.clone()));
}
}
self.watchers
.lock()
.expect("poisoned")
.push_back(cx.waker().to_owned());
#[cfg(iroh_loom)]
loom::thread::yield_now();
{
let state = self.state.read().expect("poisoned");
let epoch = state.epoch;
if last_epoch < epoch {
return Poll::Ready((epoch, state.value.clone()));
}
}
Poll::Pending
}
}
#[cfg(test)]
mod tests {
use std::time::{Duration, Instant};
use n0_future::StreamExt;
use rand::{thread_rng, Rng};
use tokio::task::JoinSet;
use tokio_util::sync::CancellationToken;
use super::*;
#[tokio::test]
async fn test_watcher() {
let cancel = CancellationToken::new();
let watchable = Watchable::new(17);
assert_eq!(watchable.watch().stream().next().await.unwrap(), 17);
let start = Instant::now();
let mut tasks = JoinSet::new();
for i in 0..3 {
let mut watch = watchable.watch().stream();
let cancel = cancel.clone();
tasks.spawn(async move {
println!("[{i}] spawn");
let mut expected_value = 17;
loop {
tokio::select! {
biased;
Some(value) = &mut watch.next() => {
println!("{:?} [{i}] update: {value}", start.elapsed());
assert_eq!(value, expected_value);
if expected_value == 17 {
expected_value = 0;
} else {
expected_value += 1;
}
},
_ = cancel.cancelled() => {
println!("{:?} [{i}] cancel", start.elapsed());
assert_eq!(expected_value, 10);
break;
}
}
}
});
}
for i in 0..3 {
let mut watch = watchable.watch().stream_updates_only();
let cancel = cancel.clone();
tasks.spawn(async move {
println!("[{i}] spawn");
let mut expected_value = 0;
loop {
tokio::select! {
biased;
Some(value) = watch.next() => {
println!("{:?} [{i}] stream update: {value}", start.elapsed());
assert_eq!(value, expected_value);
expected_value += 1;
},
_ = cancel.cancelled() => {
println!("{:?} [{i}] cancel", start.elapsed());
assert_eq!(expected_value, 10);
break;
}
else => {
panic!("stream died");
}
}
}
});
}
for next_value in 0..10 {
let sleep = Duration::from_nanos(thread_rng().gen_range(0..100_000_000));
println!("{:?} sleep {sleep:?}", start.elapsed());
tokio::time::sleep(sleep).await;
let changed = watchable.set(next_value);
println!("{:?} set {next_value} changed={changed:?}", start.elapsed());
}
println!("cancel");
cancel.cancel();
while let Some(res) = tasks.join_next().await {
res.expect("task failed");
}
}
#[test]
fn test_get() {
let watchable = Watchable::new(None);
assert!(watchable.get().is_none());
watchable.set(Some(1u8)).ok();
assert_eq!(watchable.get(), Some(1u8));
}
#[tokio::test]
async fn test_initialize() {
let watchable = Watchable::new(None);
let mut watcher = watchable.watch();
let mut initialized = watcher.initialized();
let poll = n0_future::future::poll_once(&mut initialized).await;
assert!(poll.is_none());
watchable.set(Some(1u8)).ok();
let poll = n0_future::future::poll_once(&mut initialized).await;
assert_eq!(poll.unwrap().unwrap(), 1u8);
}
#[tokio::test]
async fn test_initialize_already_init() {
let watchable = Watchable::new(Some(1u8));
let mut watcher = watchable.watch();
let mut initialized = watcher.initialized();
let poll = n0_future::future::poll_once(&mut initialized).await;
assert_eq!(poll.unwrap().unwrap(), 1u8);
}
#[test]
fn test_initialized_always_resolves() {
#[cfg(not(iroh_loom))]
use std::thread;
#[cfg(iroh_loom)]
use loom::thread;
let test_case = || {
let watchable = Watchable::<Option<u8>>::new(None);
let mut watch = watchable.watch();
let thread = thread::spawn(move || n0_future::future::block_on(watch.initialized()));
watchable.set(Some(42)).ok();
thread::yield_now();
let value: u8 = thread.join().unwrap().unwrap();
assert_eq!(value, 42);
};
#[cfg(iroh_loom)]
loom::model(test_case);
#[cfg(not(iroh_loom))]
test_case();
}
#[tokio::test(flavor = "multi_thread")]
async fn test_update_cancel_safety() {
let watchable = Watchable::new(0);
let mut watch = watchable.watch();
const MAX: usize = 100_000;
let handle = tokio::spawn(async move {
let mut last_observed = 0;
while last_observed != MAX {
tokio::select! {
val = watch.updated() => {
let Ok(val) = val else {
return;
};
assert_ne!(val, last_observed, "never observe the same value twice, even with cancellation");
last_observed = val;
}
_ = tokio::time::sleep(Duration::from_micros(thread_rng().gen_range(0..10_000))) => {
continue;
}
}
}
});
for i in 1..=MAX {
watchable.set(i).ok();
if thread_rng().gen_bool(0.2) {
tokio::task::yield_now().await;
}
}
tokio::time::timeout(Duration::from_secs(10), handle)
.await
.unwrap()
.unwrap()
}
}