├── .gitignore ├── Cargo.toml ├── README.md ├── src ├── combinators │ └── mod.rs ├── error.rs ├── eventual │ ├── change.rs │ ├── eventual.rs │ ├── eventual_ext.rs │ ├── mod.rs │ ├── ptr.rs │ ├── reader.rs │ ├── shared_state.rs │ └── writer.rs └── lib.rs └── tests ├── eventual.rs ├── handle_errors.rs ├── idle.rs ├── join.rs ├── map.rs ├── pipe.rs ├── select.rs └── timer.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "eventuals" 3 | version = "0.6.7" 4 | authors = ["Zac Burns "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Eventually consistent values" 8 | repository = "https://github.com/edgeandnode/eventuals" 9 | keywords = ["async", "future", "data-pipeline"] 10 | categories = ["asynchronous", "concurrency"] 11 | readme = "README.md" 12 | documentation = "https://docs.rs/eventuals" 13 | resolver = "2" 14 | 15 | [features] 16 | default = [] 17 | # Adds some debugging capabilities. 18 | trace = [] 19 | 20 | [badges] 21 | maintenance = { status = "experimental" } 22 | 23 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 24 | 25 | [dependencies] 26 | by_address = "1.0" 27 | tokio = { version="1.8", features=["macros", "time", "rt", "sync", "parking_lot"] } 28 | futures = "0.3.15" 29 | never = "0.1.0" 30 | 31 | [dev-dependencies] 32 | eventuals = { path=".", features=["trace"] } 33 | lazy_static = "1.0" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Eventuals give you the most up-to-date snapshots of some value. They are like Futures that update over time, continually resolving to an eventually consistent state. You can chain eventuals together, inspect their present value, and subscribe for updates. The eventual paradigm is the culmination of many hard lessons in asynchronous programming. 2 | 3 | 4 | Consider this code using an imaginary set_interval inspired by JavaScript: 5 | ```rust 6 | set_interval(|| async { let update = fetch().await; set_ui_data(update); }, 2000) 7 | ``` 8 | 9 | The above is a typical pattern used to update a UI from external data over time. There are a couple of problems with this code. First, the fetch operations may overlap. This overlap can result in stale data being shown in the UI after new data! Furthermore, there is no clear way to cancel - either from the reading or the writing perspective. 10 | 11 | 12 | Eventuals solve these and other common issues! 13 | 14 | ```rust 15 | 16 | let cancel_on_drop = timer(2000).map(|_| fetch).pipe(set_ui_update); 17 | 18 | ``` 19 | 20 | Here, `set_ui_update` is guaranteed to progress only forward in time. Different pipeline stages may execute concurrently, and some updates may be skipped if newer values are available. Still, the eventual state will always be consistent with the final writes to data. 21 | 22 | 23 | # Subscriptions 24 | 25 | Subscriptions are views of the latest update of an eventual from the reader's perspective. A subscription will only observe an update if the value has changed since the last observation and may skip updates between the previous and current observations. Taken together, this ensures that any reader performs the least amount of work necessary to be eventually consistent with the state of the writer. 26 | 27 | 28 | You can get a subscription by calling `eventual.subscribe()` and `subscription.next().await` any time you are ready to process an update. If there has been an update since the previously processed update the Future is ready immediately, otherwise it will resolve as soon as an update is available. 29 | 30 | 31 | # Snapshots 32 | There are two ways to get the current value of an eventual without a subscription. These are `eventual.value_immediate()` and `eventual.value`, depending on whether you want to wait for the first update. 33 | 34 | # Unit tests 35 | 36 | One useful application of eventuals is to make components testable without mocks. Instead of making a mock server (which requires traits) you can supply your component with an eventual and feed the component data. It's not dependency injection. It's data injection. 37 | 38 | 39 | # FAQ 40 | 41 | What is the difference between an eventual and a Stream? 42 | 43 | A Stream is a collection of distinct, ordered values that are made available asynchronously. An eventual, however, is an asynchronous view of a single value changing over time. 44 | 45 | 46 | The API seems sparse. I see map and join, but where are filter, reduce, and select? 47 | 48 | It is natural to want to apply the full range of functional techniques to eventuals, but this is an anti-pattern. The goal of eventuals is to make the final value of any view of any data pipeline deterministically consistent with the latest write. The path to producing a result may not be deterministic, but the final value is consistent if each building block is also consistent. Not every functional block passes this test. To demonstrate, we can compare the behavior of map and an imaginary filter combinator on the same sequence of writes. 49 | 50 | 51 | Consider `eventual.map(|n| async move { n * 2 })` and `eventual.filter(|n| async move { n % 2 == 0 })` both consuming the series writes `[0, 1, 2, 3, 4, 5]` over time. `map`, in this case, will always eventually resolve to the value `10`. It may also produce some subset of intermediate values along the way (like `[0, 4, 8, 10]` or `[2, 10]`). Still, it will always progress forward in time and always resolves to a deterministic value consistent with the final write. However, the final value that `filter` produces would be a function of which intermediate values were observed. If filter observes the subset of writes `[0, 2, 5]`, it will resolve to `2`, but if it observes the subset of writes `[1, 4, 5]`, it will resolve to `4`. This bug is exactly the kind eventuals are trying to avoid. 52 | -------------------------------------------------------------------------------- /src/combinators/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use futures::future::select_all; 3 | use never::Never; 4 | use std::sync::{Arc, Mutex}; 5 | use std::time::Duration; 6 | use std::{future::Future, time::Instant}; 7 | use tokio::{ 8 | self, select, 9 | time::{sleep, sleep_until}, 10 | }; 11 | 12 | /// Applies an operation to each observed snapshot from the source. For example: 13 | /// map([1, 2, 3, 4, 5], |v| v+1) may produce something like [2, 6] or [3, 4, 14 | /// 6]. In this case, 6 is the only value guaranteed to be observed eventually. 15 | pub fn map(source: E, mut f: F) -> Eventual 16 | where 17 | E: IntoReader, 18 | F: 'static + Send + FnMut(I) -> Fut, 19 | I: Value, 20 | O: Value, 21 | Fut: Send + Future, 22 | { 23 | let mut source = source.into_reader(); 24 | 25 | Eventual::spawn(|mut writer| async move { 26 | loop { 27 | writer.write(f(source.next().await?).await); 28 | } 29 | }) 30 | } 31 | 32 | /// Periodically writes a new value of the time elapsed. No guarantee is made 33 | /// about frequency or the value written except that at least "interval" time 34 | /// has passed since producing the last snapshot. 35 | pub fn timer(interval: Duration) -> Eventual { 36 | Eventual::spawn(move |mut writer| async move { 37 | loop { 38 | writer.write(Instant::now()); 39 | sleep(interval).await; 40 | } 41 | }) 42 | } 43 | 44 | /// Indicates the type can be used with the join method. Not intended to 45 | /// be used directly. 46 | pub trait Joinable { 47 | type Output; 48 | fn join(self) -> Eventual; 49 | } 50 | 51 | macro_rules! impl_tuple { 52 | ($len:expr, $($T:ident, $t:ident),*) => { 53 | impl Selectable for ($($T,)*) 54 | where 55 | $($T: IntoReader,)* 56 | T: Value, 57 | { 58 | type Output = T; 59 | fn select(self) -> Eventual { 60 | let ($($t),*) = self; 61 | $(let $t = $t.into_reader();)* 62 | #[allow(deprecated)] 63 | vec![$($t),*].select() 64 | } 65 | } 66 | 67 | impl<$($T,)*> Joinable for ($($T,)*) 68 | where 69 | $($T: IntoReader,)* 70 | { 71 | type Output = ($($T::Output),*); 72 | 73 | #[allow(non_snake_case)] 74 | fn join(self) -> Eventual { 75 | let ($($T),*) = self; 76 | $(let mut $T = $T.into_reader();)* 77 | 78 | Eventual::spawn(move |mut writer| async move { 79 | // In the first section we wait until all values are available 80 | let mut len: usize = 0; 81 | let mut count: usize = 0; 82 | $(let mut $t = None; len += 1;)* 83 | let ($(mut $t,)*) = loop { 84 | select! { 85 | $( 86 | next = $T.next() => { 87 | if $t.replace(next?).is_none() { 88 | count += 1; 89 | } 90 | } 91 | )* 92 | } 93 | if count == len { 94 | break ($($t.unwrap()),*); 95 | } 96 | }; 97 | // Once all values are available, start writing but continue 98 | // to update. 99 | loop { 100 | writer.write(($($t.clone(),)*)); 101 | 102 | select! { 103 | $( 104 | next = $T.next() => { 105 | $t = next?; 106 | } 107 | )* 108 | } 109 | } 110 | }) 111 | } 112 | } 113 | }; 114 | } 115 | 116 | // This macro exists to expand to the implementation for one tuple and 117 | // call itself for the smaller tuple until running out of tuples. 118 | macro_rules! impl_tuples { 119 | ($len:expr, $A:ident, $a:ident) => { }; 120 | ($len:expr, $A:ident, $a:ident, $($T:ident, $t:ident),+) => { 121 | impl_tuple!($len, $A, $a, $($T, $t),+); 122 | impl_tuples!($len - 1, $($T, $t),+); 123 | } 124 | } 125 | 126 | impl_tuples!(12, A, a, B, b, C, c, D, d, E, e, F, f, G, g, H, h, I, i, J, j, K, k, L, l); 127 | 128 | /// An eventual that will only progress once all inputs are available, and then 129 | /// also progress with each change as they become available. For example, 130 | /// join((["a", "b, "c"], [1, 2, 3])) may observe something like [("a", 1), 131 | /// ("a", 2), ("c", 2), ("c", 3)] or [("c", 1), ("c", 3)]. The only snapshot 132 | /// that is guaranteed to be observed is ("c", 3). 133 | pub fn join(joinable: J) -> Eventual 134 | where 135 | J: Joinable, 136 | { 137 | joinable.join() 138 | } 139 | 140 | pub trait Selectable { 141 | type Output; 142 | #[deprecated = "Not deterministic. This doesn't seem as harmful as filter, because it doesn't appear to miss updates."] 143 | fn select(self) -> Eventual; 144 | } 145 | 146 | #[deprecated = "Not deterministic. This doesn't seem as harmful as filter, because it doesn't appear to miss updates."] 147 | pub fn select(selectable: S) -> Eventual 148 | where 149 | S: Selectable, 150 | { 151 | #[allow(deprecated)] 152 | selectable.select() 153 | } 154 | 155 | impl Selectable for Vec 156 | where 157 | R: IntoReader, 158 | { 159 | type Output = R::Output; 160 | fn select(self) -> Eventual { 161 | // TODO: With specialization we can avoid what is essentially an 162 | // unnecessary clone when R is EventualReader 163 | let mut readers: Vec<_> = self.into_iter().map(|v| v.into_reader()).collect(); 164 | Eventual::spawn(move |mut writer| async move { 165 | loop { 166 | if readers.len() == 0 { 167 | return Err(Closed); 168 | } 169 | let read_futs: Vec<_> = readers.iter_mut().map(|r| r.next()).collect(); 170 | 171 | let (output, index, remainder) = select_all(read_futs).await; 172 | 173 | // Ideally, we would want to re-use this list, but in most 174 | // cases we can't because it may have been shuffled. 175 | drop(remainder); 176 | 177 | match output { 178 | Ok(value) => { 179 | writer.write(value); 180 | } 181 | Err(Closed) => { 182 | readers.remove(index); 183 | } 184 | } 185 | } 186 | }) 187 | } 188 | } 189 | 190 | /// Prevents observation of values more frequently than the provided duration. 191 | /// The final value is guaranteed to be observed. 192 | pub fn throttle(read: E, duration: Duration) -> Eventual 193 | where 194 | E: IntoReader, 195 | { 196 | let mut read = read.into_reader(); 197 | 198 | Eventual::spawn(move |mut writer| async move { 199 | loop { 200 | let mut next = read.next().await?; 201 | let end = tokio::time::Instant::now() + duration; 202 | loop { 203 | // Allow replacing the value until the time is up. This 204 | // necessarily introduces latency but de-duplicates when there 205 | // are intermittent bursts. Not sure what is better. Matching 206 | // common-ts for now. 207 | select! { 208 | n = read.next() => { 209 | next = n?; 210 | } 211 | _ = sleep_until(end) => { 212 | break; 213 | } 214 | } 215 | } 216 | writer.write(next); 217 | } 218 | }) 219 | } 220 | 221 | /// Produce a side effect with the latest snapshots as they become available. 222 | /// The caller must not drop the returned PipeHandle until it is no longer 223 | /// desirable to produce the side effect. 224 | pub fn pipe(reader: E, mut f: F) -> PipeHandle 225 | where 226 | E: IntoReader, 227 | F: 'static + Send + FnMut(E::Output), 228 | { 229 | let mut reader = reader.into_reader(); 230 | 231 | #[allow(unreachable_code)] 232 | PipeHandle::new(Eventual::spawn(|_writer| async move { 233 | loop { 234 | f(reader.next().await?); 235 | } 236 | // Prevent the writer from being dropped. Normally we would expect 237 | // _writer to not be dropped, but the async move creates a new lexical 238 | // scope for the Future. If _writer is not moved into the Future it 239 | // would be dropped right after the Future is created and before the 240 | // closure returns. Without this line, Pipe stops prematurely. 241 | drop(_writer); 242 | })) 243 | } 244 | 245 | /// Similar to `pipe`, but allows for the side effect to be async. 246 | /// See also: `pipe` 247 | pub fn pipe_async(reader: E, mut f: F) -> PipeHandle 248 | where 249 | E: IntoReader, 250 | F: 'static + Send + FnMut(E::Output) -> Fut, 251 | Fut: Send + Future, 252 | { 253 | let mut reader = reader.into_reader(); 254 | 255 | #[allow(unreachable_code)] 256 | PipeHandle::new(Eventual::spawn(|_writer| async move { 257 | loop { 258 | f(reader.next().await?).await; 259 | } 260 | drop(_writer); 261 | })) 262 | } 263 | 264 | /// Pipe ceases when this is dropped 265 | #[must_use] 266 | pub struct PipeHandle { 267 | inner: Eventual, 268 | } 269 | 270 | impl PipeHandle { 271 | fn new(eventual: Eventual) -> Self { 272 | Self { inner: eventual } 273 | } 274 | 275 | /// Prevent the pipe operation from ever stopping for as long 276 | /// as snapshots are observed. 277 | #[inline] 278 | pub fn forever(self) { 279 | let Self { inner } = self; 280 | tokio::task::spawn(async move { 281 | // Drops the reader when the writer is closed 282 | // This value is always Err(Closed) because inner is Eventual 283 | let _closed = inner.value().await; 284 | }); 285 | } 286 | } 287 | 288 | #[deprecated = "Not deterministic. This is a special case of filter. Retry should be better"] 289 | pub fn handle_errors(source: E, mut f: F) -> Eventual 290 | where 291 | E: IntoReader>, 292 | F: 'static + Send + FnMut(Err), 293 | Ok: Value, 294 | Err: Value, 295 | { 296 | let mut reader = source.into_reader(); 297 | 298 | Eventual::spawn(move |mut writer| async move { 299 | loop { 300 | match reader.next().await? { 301 | Ok(v) => writer.write(v), 302 | Err(e) => f(e), 303 | } 304 | } 305 | }) 306 | } 307 | 308 | // TODO: Improve retry API. Some retry is needed because retry should be 309 | // eventual aware in that it will only retry if there is no update available, 310 | // instead preferring the update. It's a little tricky to write in a general 311 | // sense because it is not clear _what_ is being retried. A retry can't force an 312 | // upstream map to produce a value again. You could couple the map and retry 313 | // API, but that's not great. The only thing I can think of is to have a 314 | // function produce an eventual upon encountering an error. That seems like the 315 | // right choice but need to let it simmer. With this API the retry "region" is 316 | // configurable where the "region" could be an entire pipeline of eventuals. 317 | // 318 | // Below is an "interesting" first attempt. 319 | // 320 | // This is a retry that is maximally abstracted. It is somewhat experimental, 321 | // but makes sense if you want to be able to not tie retry down to any 322 | // particular other feature (like map). It's also BONKERS. See map_with_retry 323 | // for usage. 324 | pub fn retry(mut f: F) -> Eventual 325 | where 326 | Ok: Value, 327 | Err: Value, 328 | Fut: Send + Future>>, 329 | F: 'static + Send + FnMut(Option) -> Fut, 330 | { 331 | Eventual::spawn(move |mut writer| async move { 332 | loop { 333 | let mut e = f(None).await.subscribe(); 334 | let mut next = e.next().await; 335 | 336 | loop { 337 | match next? { 338 | Ok(v) => { 339 | writer.write(v); 340 | next = e.next().await; 341 | } 342 | Err(err) => { 343 | select! { 344 | e_temp = f(Some(err)) => { 345 | e = e_temp.subscribe(); 346 | next = e.next().await; 347 | } 348 | n_temp = e.next() => { 349 | next = n_temp; 350 | } 351 | } 352 | } 353 | } 354 | } 355 | } 356 | }) 357 | } 358 | 359 | /// Ensure that a fallible map operation will succeed eventually. For example 360 | /// given map_with_retry(["url_1", "url_2"], fallibly_get_data, sleep) may 361 | /// produce ["data_1", "data_2"] or just ["data_2"]. The difference between 362 | /// map_with_retry and something like map(source, retry(fallibly_get_data, 363 | /// on_err)) is that the former supports 'moving on' to "url_2" even if "url_1" 364 | /// is in a retry state, whereas the latter would have to complete one item 365 | /// fully before progressing. It is because of this distinction that 366 | /// map_with_retry is allowed to retry forever instead of giving up after a set 367 | /// number of attempts. 368 | pub fn map_with_retry(source: R, f: F, on_err: E) -> Eventual 369 | where 370 | R: IntoReader, 371 | F: 'static + Send + FnMut(R::Output) -> Fut, 372 | E: 'static + Send + Sync + FnMut(Err) -> FutE, 373 | Ok: Value, 374 | Err: Value, 375 | Fut: Send + Future>, 376 | FutE: Send + Future, 377 | { 378 | let source = source.into_reader(); 379 | 380 | // Wraping the FnMut values in Arc> allows us 381 | // to use FnMut instead of Fn, and not require Fn to impl 382 | // clone. This should make it easier to do things like 383 | // exponential backoff. 384 | let f = Arc::new(Mutex::new(f)); 385 | let on_err = Arc::new(Mutex::new(on_err)); 386 | 387 | retry(move |e| { 388 | let mut reader = source.clone(); 389 | let f = f.clone(); 390 | let on_err = on_err.clone(); 391 | async move { 392 | if let Some(e) = e { 393 | let fut = { 394 | let mut locked = on_err.lock().unwrap(); 395 | locked(e) 396 | }; 397 | fut.await; 398 | // Without this line there is a very subtle problem. 399 | // One thing that map_with_retry needs to do is resume as 400 | // of the state of the source. We accomplish this with clone. 401 | // But, consider the following scenario: if the source had prev=A, 402 | // then [B, A] is observed, and A needs to retry. Without this line 403 | // the output of B could have been produced and the output of 404 | // map(A) would not have been produced. Interestingly, we also 405 | // know that this line does not force a double-read, because in order 406 | // to get here the reader must have had at least one observation. 407 | // Unless you count (Ok(A), Fail(B), Ok(A)) as a double read. 408 | // 409 | // There's one more subtle issue to consider, which is why force_dirty 410 | // is not public. force_dirty could cause the final value to be 411 | // double-read if the eventual is closed. However, we know that in this 412 | // case it was not ready to receive closed. 413 | // 414 | // This does raise a philisophical question about guaranteeing that 415 | // the last value is observed though. It could be that retry gets 416 | // stuck here on the last value forever. (Unless the readers are dropped) 417 | reader.force_dirty(); 418 | } 419 | map(reader, move |value| { 420 | let fut = { 421 | let mut locked = f.lock().unwrap(); 422 | locked(value) 423 | }; 424 | fut 425 | }) 426 | } 427 | }) 428 | } 429 | 430 | /// Return an eventual with a starting value that then defers to source. 431 | pub fn init_with(source: R, value: R::Output) -> Eventual 432 | where 433 | R: IntoReader, 434 | { 435 | let mut source = source.into_reader(); 436 | Eventual::spawn(|mut writer| async move { 437 | writer.write(value); 438 | loop { 439 | writer.write(source.next().await?); 440 | } 441 | }) 442 | } 443 | 444 | /// Prefer values from source_1 if available, otherwise use source_2. 445 | pub fn prefer(source_1: R1, source_2: R2) -> Eventual 446 | where 447 | R1: IntoReader, 448 | R2: IntoReader, 449 | T: Value, 450 | { 451 | let mut source_1 = source_1.into_reader(); 452 | let mut source_2 = source_2.into_reader(); 453 | 454 | Eventual::spawn(|mut writer| async move { 455 | loop { 456 | select! { 457 | biased; 458 | 459 | one = source_1.next() => { 460 | if let Ok(one) = one { 461 | writer.write(one); 462 | break; 463 | } else { 464 | loop { 465 | writer.write(source_2.next().await?); 466 | } 467 | } 468 | } 469 | two = source_2.next() => { 470 | if let Ok(two) = two { 471 | writer.write(two); 472 | } else { 473 | break; 474 | } 475 | } 476 | } 477 | } 478 | drop(source_2); 479 | loop { 480 | writer.write(source_1.next().await?); 481 | } 482 | }) 483 | } 484 | 485 | // TODO: Consider if this is "sound" because it may work kind of like filter 486 | // because the final eventual may not have a write and in that case this could 487 | // settle on an non-deterministic value. The motivation for this function was in 488 | // imagining a DAG graph (like Apple Shake, or Autodesk Maya) powered by 489 | // eventuals where via a UI you could make and break connections by writing a 490 | // DAG node output eventual to a DAG node input eventual. A broken connection 491 | // would use something like `dag_input.write(Eventual::with_value(None))`, and 492 | // newly formed connections would use 493 | // `dag_input.write(dag_output.init_with(None))`. Inside the implementation of a 494 | // generic DAG node there would always be something like `output: 495 | // join((flatten(input_1), flatten(input_2)).map(add)`. For that specific 496 | // use-case this is not a problem. It may be that the non-deterministic APIs 497 | // generally should be resurrected with documentation caveats to bring their 498 | // non-determinism to the attention of the user. If the final eventual has a write, 499 | // this is always deterministic. Otherwise not. 500 | #[deprecated = "Unsure if this meets determinism requirements"] 501 | pub fn flatten(outer: R1) -> Eventual 502 | where 503 | R1: IntoReader, 504 | R2: IntoReader, 505 | R2: Value, 506 | { 507 | let mut outer = outer.into_reader(); 508 | Eventual::spawn(|mut writer| async move { 509 | // Always need to get the first outer eventual. If there 510 | // is none, then there are no values and this can return because 511 | // there is never anything else to write. 512 | let mut inner = outer.next().await?.into_reader(); 513 | loop { 514 | select! { 515 | next = outer.next() => { 516 | // If we get a new source, replace the current one. 517 | if let Ok(next) = next { 518 | inner = next.into_reader(); 519 | } else { 520 | // If we get here it means there will never be any more 521 | // sources. Exhaust the current one, then break. 522 | loop { 523 | writer.write(inner.next().await?); 524 | } 525 | } 526 | } 527 | next = inner.next() => { 528 | // If we get a new value, write it. 529 | if let Ok(next) = next { 530 | writer.write(next); 531 | } else { 532 | // If the current source runs out of values, always 533 | // try to move on to the next source. 534 | inner = outer.next().await?.into_reader(); 535 | } 536 | } 537 | } 538 | } 539 | }) 540 | } 541 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, PartialEq, Eq, Debug)] 2 | pub struct Closed; 3 | -------------------------------------------------------------------------------- /src/eventual/change.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use crate::Ptr; 4 | use std::{ 5 | collections::HashSet, 6 | hash::{Hash, Hasher}, 7 | mem, 8 | ops::DerefMut, 9 | ptr, 10 | sync::{Arc, Mutex}, 11 | task::Waker, 12 | }; 13 | 14 | #[cfg(feature = "trace")] 15 | mod busy { 16 | use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; 17 | use tokio::sync::Notify; 18 | 19 | pub static BUSY_COUNT: AtomicUsize = AtomicUsize::new(0); 20 | pub static BUSY_WAKER: Notify = Notify::const_new(); 21 | 22 | pub fn set_busy() { 23 | BUSY_COUNT.fetch_add(1, SeqCst); 24 | } 25 | pub fn clear_busy() { 26 | let prev = BUSY_COUNT.fetch_sub(1, SeqCst); 27 | debug_assert!(prev != 0); 28 | if prev == 1 { 29 | BUSY_WAKER.notify_waiters(); 30 | } 31 | } 32 | 33 | /// Ready once all eventuals readers are waiting on a new value. _Generally_ 34 | /// speaking, it is possible to ensure that a change has propagated through 35 | /// an eventuals pipeline using this method. However, there is no guarantee 36 | /// that this will complete in a timely fashion if ever. A sufficiently 37 | /// layered pipeline that is always moving values through may never be idle. 38 | /// So, this is only useful in isolated tests. 39 | pub async fn idle() { 40 | loop { 41 | let notified = BUSY_WAKER.notified(); 42 | if BUSY_COUNT.load(SeqCst) == 0 { 43 | return; 44 | } 45 | notified.await 46 | } 47 | } 48 | } 49 | 50 | #[cfg(feature = "trace")] 51 | pub use busy::idle; 52 | 53 | struct Busy(T); 54 | 55 | impl Busy { 56 | fn new(value: T) -> Self { 57 | #[cfg(feature = "trace")] 58 | busy::set_busy(); 59 | Self(value) 60 | } 61 | 62 | fn unbusy(mut self) -> T { 63 | 64 | let inner = unsafe {ptr::read(&mut self.0)}; 65 | mem::forget(self); 66 | #[cfg(feature = "trace")] 67 | busy::clear_busy(); 68 | 69 | inner 70 | 71 | } 72 | } 73 | 74 | impl Deref for Busy { 75 | type Target = T; 76 | fn deref(&self) -> &Self::Target { 77 | &self.0 78 | } 79 | } 80 | 81 | impl Drop for Busy { 82 | fn drop(&mut self) { 83 | #[cfg(feature = "trace")] 84 | busy::clear_busy(); 85 | } 86 | } 87 | 88 | enum ChangeVal { 89 | None(Busy<()>), 90 | Value(Busy), 91 | Finalized(Busy>), 92 | Waker(Waker), 93 | } 94 | 95 | pub enum ChangeValNoWake { 96 | None, 97 | Value(T), 98 | Finalized(Option), 99 | } 100 | 101 | pub struct Change { 102 | inner: Ptr>>, 103 | } 104 | 105 | pub struct ChangeReader { 106 | pub change: Change, 107 | pub unsubscribe_from: Arc>, 108 | } 109 | 110 | impl Drop for ChangeReader { 111 | fn drop(&mut self) { 112 | let mut lock = self.unsubscribe_from.subscribers.lock().unwrap(); 113 | let mut updated: HashSet<_> = lock.deref().deref().clone(); 114 | if updated.remove(&self.change) { 115 | *lock = Arc::new(updated); 116 | } 117 | } 118 | } 119 | 120 | impl Change 121 | where 122 | T: Value, 123 | { 124 | pub fn new() -> Self { 125 | Self { 126 | inner: Ptr::new(Mutex::new(ChangeVal::None(Busy::new(())))), 127 | } 128 | } 129 | 130 | pub fn poll( 131 | &self, 132 | cmp: &Option>, 133 | cx: &mut Context, 134 | ) -> Option> { 135 | let mut lock = self.inner.lock().unwrap(); 136 | 137 | // Move the value out pre-emptively to keep things sane for the borrow checker. 138 | // Depending on the branch ahead we'll swap in different values. 139 | let value = mem::replace(lock.deref_mut(), ChangeVal::None(Busy::new(()))); 140 | 141 | match value { 142 | // If there is a new value and it is different than our previously 143 | // observed value return it. Otherwise fall back to waking later. 144 | ChangeVal::Value(value) => { 145 | let value = value.unbusy(); 146 | let value = Some(Ok(value)); 147 | if cmp != &value { 148 | return value; 149 | } 150 | } 151 | // If the eventual is finalized from the writer end make sure that the final value 152 | // (if any) is returned once as though it were a normal value. Then (possibly on 153 | // a subsequent poll) return the Err. 154 | ChangeVal::Finalized(value) => { 155 | if let Some(value) = value.unbusy() { 156 | let value = Some(Ok(value)); 157 | if cmp != &value { 158 | *lock = ChangeVal::Finalized(Busy::new(None)); 159 | return value; 160 | } 161 | } 162 | return Some(Err(Closed)); 163 | } 164 | // There is no update. The waker may need to be re-scheduled. 165 | ChangeVal::None(_) | ChangeVal::Waker(_) => {} 166 | } 167 | *lock = ChangeVal::Waker(cx.waker().clone()); 168 | None 169 | } 170 | 171 | pub fn set_value(&self, value: &Mutex>) { 172 | let prev = { 173 | // To avoid race conditions BOTH locks MUST be held. This insures 174 | // that if new values are pushed while subscribers are being 175 | // notified there cannot be a time that a subscriber is notified 176 | // with the old value. Instead, it might be notified with the new 177 | // value twice. Notice that the former is apocalyptic (missed 178 | // updates) and the later just drains some performance for an extra 179 | // equality check on the receiving end. 180 | let value = value.lock().unwrap(); 181 | let mut inner = self.inner.lock().unwrap(); 182 | 183 | // Move out of inner early for borrow checker. 184 | let prev = mem::replace(inner.deref_mut(), ChangeVal::None(Busy::new(()))); 185 | 186 | match value.deref() { 187 | ChangeValNoWake::None => { 188 | // Prev must be None. The only time set_value is called when 189 | // value is None is when the value has never before been set 190 | // (therefore prev is not ChangeValue::Finalized or 191 | // ChangeVal::Value) and the ChangeVal has no waker because 192 | // we are now adding it to the subscriber list. 193 | // If this assert fails, we would want `*inner = prev;`. 194 | debug_assert!(matches!(prev, ChangeVal::None(_))); 195 | 196 | // Since we know this is None, there is no need to check 197 | // for the waker (below) 198 | return; 199 | } 200 | // There is an update. 201 | ChangeValNoWake::Value(value) => { 202 | // The previous value must not have been finalized. 203 | // It is not possible to move from a finalized state to 204 | // then have updates. 205 | debug_assert!(!matches!(prev, ChangeVal::Finalized(_))); 206 | // Set the value. 207 | *inner = ChangeVal::Value(Busy::new(value.clone())); 208 | } 209 | // If closing, this is more tricky because we want to preserve 210 | // the last update (if any) so that the final value propagates 211 | // all the way through. 212 | ChangeValNoWake::Finalized(finalized) => { 213 | // Verify that it's not copying the final value over again 214 | // because in racey situations it may have been copied once 215 | // then had the value consumed. It wouldn't be the end of the 216 | // world to reset the finalized state, but would result in 217 | // some unnecessary work. 218 | if !matches!(prev, ChangeVal::Finalized(_)) { 219 | *inner = ChangeVal::Finalized(Busy::new(finalized.clone())); 220 | } 221 | } 222 | }; 223 | 224 | prev 225 | 226 | // Drop locks before calling wake() 227 | }; 228 | 229 | // Race conditions here are OK. The worst that can happen 230 | // is that Tasks are woken up unnecessarily. They would 231 | // just return Pending the second time. 232 | if let ChangeVal::Waker(waker) = prev { 233 | waker.wake(); 234 | } 235 | } 236 | } 237 | 238 | impl Clone for Change { 239 | fn clone(&self) -> Self { 240 | Self { 241 | inner: self.inner.clone(), 242 | } 243 | } 244 | } 245 | 246 | impl Eq for Change {} 247 | impl PartialEq for Change { 248 | fn eq(&self, other: &Self) -> bool { 249 | self.inner.eq(&other.inner) 250 | } 251 | } 252 | 253 | impl Hash for Change { 254 | fn hash(&self, state: &mut H) { 255 | self.inner.hash(state) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/eventual/eventual.rs: -------------------------------------------------------------------------------- 1 | use std::ops::DerefMut; 2 | 3 | use super::change::{ChangeReader, ChangeValNoWake}; 4 | use super::shared_state::SharedState; 5 | use super::*; 6 | use crate::IntoReader; 7 | use futures::channel::oneshot; 8 | use futures::never::Never; 9 | use tokio::select; 10 | 11 | /// The entry point for getting the latest snapshots of values 12 | /// written by an EventualWriter. It supports multiple styles 13 | /// of observation. 14 | pub struct Eventual { 15 | state: Arc>, 16 | } 17 | 18 | impl Eventual 19 | where 20 | T: Value, 21 | { 22 | /// Create a reader/writer pair. 23 | pub fn new() -> (EventualWriter, Self) { 24 | let (sender, receiver) = oneshot::channel(); 25 | let state = Arc::new(SharedState::new(sender)); 26 | (EventualWriter::new(&state, receiver), Eventual { state }) 27 | } 28 | 29 | /// Create an eventual having a final value. This is useful for creating 30 | /// "mock" eventuals to pass into consumers. 31 | pub fn from_value(value: T) -> Self { 32 | let (mut writer, eventual) = Eventual::new(); 33 | writer.write(value); 34 | eventual 35 | } 36 | 37 | /// A helper for spawning a task which writes to an eventual. 38 | /// These are used extensively within the library for eventuals 39 | /// which update continuously over time. 40 | pub fn spawn(f: F) -> Self 41 | where 42 | F: 'static + Send + FnOnce(EventualWriter) -> Fut, 43 | Fut: Future> + Send, 44 | { 45 | let (writer, eventual) = Eventual::new(); 46 | tokio::spawn(async move { 47 | select!( 48 | _ = writer.closed() => {} 49 | _ = async { f(writer).await } => {} 50 | ); 51 | }); 52 | eventual 53 | } 54 | 55 | /// Subscribe to present and future snapshots of the value in this Eventual. 56 | /// Generally speaking the observations of snapshots take into account the 57 | /// state of the reader such that: 58 | /// * The same observation is not made redundantly (same value twice in a 59 | /// row) 60 | /// * The observations always move forward in time 61 | /// * The final observation will always be eventually consistent with the 62 | /// final write. 63 | pub fn subscribe(&self) -> EventualReader { 64 | EventualReader::new(self.state.clone()) 65 | } 66 | 67 | /// Get a future that resolves with a snapshot of the present value of the 68 | /// Eventual, if any, or a future snapshot if none is available. Which 69 | /// snapshot is returned depends on when the Future is polled (as opposed 70 | /// to when the Future is created) 71 | pub fn value(&self) -> ValueFuture { 72 | let change = self.state.clone().subscribe(); 73 | ValueFuture { 74 | change: Some(change), 75 | } 76 | } 77 | 78 | /// Get a snapshot of the current value of this Eventual, if any, 79 | /// without waiting. 80 | pub fn value_immediate(&self) -> Option { 81 | match self.state.last_write.lock().unwrap().deref_mut() { 82 | ChangeValNoWake::None => None, 83 | ChangeValNoWake::Value(t) => Some(t.clone()), 84 | ChangeValNoWake::Finalized(t) => t.clone(), 85 | } 86 | } 87 | 88 | #[cfg(feature = "trace")] 89 | pub fn subscriber_count(&self) -> usize { 90 | self.state.subscribers.lock().unwrap().len() 91 | } 92 | } 93 | 94 | pub struct ValueFuture { 95 | change: Option>, 96 | } 97 | 98 | impl Future for ValueFuture 99 | where 100 | T: Value, 101 | { 102 | type Output = Result; 103 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 104 | let update = self.change.as_mut().unwrap().change.poll(&None, cx); 105 | match update { 106 | None => Poll::Pending, 107 | Some(value) => { 108 | self.change = None; 109 | Poll::Ready(value) 110 | } 111 | } 112 | } 113 | } 114 | 115 | impl Clone for Eventual { 116 | #[inline] 117 | fn clone(&self) -> Self { 118 | Self { 119 | state: self.state.clone(), 120 | } 121 | } 122 | } 123 | 124 | impl IntoReader for &'_ Eventual 125 | where 126 | T: Value, 127 | { 128 | type Output = T; 129 | #[inline] 130 | fn into_reader(self) -> EventualReader { 131 | self.subscribe() 132 | } 133 | } 134 | 135 | impl IntoReader for Eventual 136 | where 137 | T: Value, 138 | { 139 | type Output = T; 140 | #[inline] 141 | fn into_reader(self) -> EventualReader { 142 | self.subscribe() 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/eventual/eventual_ext.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use futures::Future; 3 | use std::time::Duration; 4 | 5 | /// Fluent style API extensions for any Eventual reader. 6 | pub trait EventualExt: Sized + IntoReader { 7 | #[inline] 8 | fn map(self, f: F) -> Eventual 9 | where 10 | F: 'static + Send + FnMut(Self::Output) -> Fut, 11 | O: Value, 12 | Fut: Send + Future, 13 | { 14 | map(self, f) 15 | } 16 | 17 | #[inline] 18 | fn throttle(self, duration: Duration) -> Eventual { 19 | throttle(self, duration) 20 | } 21 | 22 | #[inline] 23 | fn pipe(self, f: F) -> PipeHandle 24 | where 25 | F: 'static + Send + FnMut(Self::Output), 26 | { 27 | pipe(self, f) 28 | } 29 | 30 | #[inline] 31 | fn pipe_async(self, f: F) -> PipeHandle 32 | where 33 | F: 'static + Send + FnMut(Self::Output) -> Fut, 34 | Fut: Send + Future, 35 | { 36 | pipe_async(self, f) 37 | } 38 | 39 | #[inline] 40 | fn map_with_retry(self, f: F, on_err: E) -> Eventual 41 | where 42 | F: 'static + Send + FnMut(Self::Output) -> Fut, 43 | E: 'static + Send + Sync + FnMut(Err) -> FutE, 44 | Ok: Value, 45 | Err: Value, 46 | Fut: Send + Future>, 47 | FutE: Send + Future, 48 | { 49 | map_with_retry(self, f, on_err) 50 | } 51 | 52 | #[inline] 53 | fn init_with(self, value: Self::Output) -> Eventual { 54 | init_with(self, value) 55 | } 56 | } 57 | 58 | impl EventualExt for E where E: IntoReader {} 59 | 60 | pub trait TryEventualExt: Sized + IntoReader> 61 | where 62 | Ok: Value, 63 | Err: Value, 64 | { 65 | #[inline] 66 | #[deprecated = "Not deterministic. This is a special case of filter. Retry should be better"] 67 | fn handle_errors(self, f: F) -> Eventual 68 | where 69 | F: 'static + Send + FnMut(Err), 70 | { 71 | #[allow(deprecated)] 72 | handle_errors(self, f) 73 | } 74 | } 75 | 76 | impl TryEventualExt for E 77 | where 78 | E: IntoReader>, 79 | Ok: Value, 80 | Err: Value, 81 | { 82 | } 83 | -------------------------------------------------------------------------------- /src/eventual/mod.rs: -------------------------------------------------------------------------------- 1 | use super::Closed; 2 | use std::{ 3 | future::Future, 4 | ops::Deref, 5 | pin::Pin, 6 | sync::Arc, 7 | task::{Context, Poll}, 8 | }; 9 | 10 | mod change; 11 | mod eventual; 12 | mod eventual_ext; 13 | mod ptr; 14 | mod reader; 15 | mod shared_state; 16 | mod writer; 17 | 18 | pub(self) use {crate::Value, shared_state::*}; 19 | 20 | pub use { 21 | eventual::Eventual, 22 | eventual_ext::{EventualExt, TryEventualExt}, 23 | ptr::Ptr, 24 | reader::EventualReader, 25 | writer::EventualWriter, 26 | }; 27 | 28 | #[cfg(feature = "trace")] 29 | pub use change::idle; 30 | -------------------------------------------------------------------------------- /src/eventual/ptr.rs: -------------------------------------------------------------------------------- 1 | use by_address::ByAddress; 2 | use std::{ 3 | borrow::Borrow, cmp::Ordering, convert::AsRef, error::Error, fmt, hash::Hash, ops::Deref, 4 | sync::Arc, 5 | }; 6 | 7 | /// This type is a thin wrapper around T to enable cheap clone and comparisons. 8 | /// Internally it is an Arc that is compared by address instead of by the 9 | /// implementation of the pointed to value. 10 | /// 11 | /// Additionally, Ptr implements Error where T: Error. This makes working with 12 | /// TryEventuals easier since many error types do not impl Value, but when 13 | /// wrapped in Ptr do. 14 | /// 15 | /// One thing to be aware of is that because values are compared by address 16 | /// subscribers may be triggered unnecessarily in some contexts. If this is 17 | /// undesirable use Arc instead. 18 | /// 19 | /// This type is not specifically Eventual related, but is a useful pattern. 20 | #[repr(transparent)] 21 | #[derive(Debug, Default)] 22 | pub struct Ptr { 23 | inner: ByAddress>, 24 | } 25 | 26 | impl Ptr { 27 | #[inline] 28 | pub fn new(wrapped: T) -> Self { 29 | Self { 30 | inner: ByAddress(Arc::new(wrapped)), 31 | } 32 | } 33 | } 34 | 35 | impl Deref for Ptr { 36 | type Target = T; 37 | #[inline] 38 | fn deref(&self) -> &Self::Target { 39 | self.inner.deref() 40 | } 41 | } 42 | 43 | impl Borrow for Ptr { 44 | #[inline] 45 | fn borrow(&self) -> &T { 46 | self.inner.borrow() 47 | } 48 | } 49 | 50 | impl AsRef for Ptr { 51 | #[inline] 52 | fn as_ref(&self) -> &T { 53 | self.inner.as_ref() 54 | } 55 | } 56 | 57 | impl Hash for Ptr { 58 | #[inline] 59 | fn hash(&self, state: &mut H) { 60 | self.inner.hash(state) 61 | } 62 | } 63 | 64 | impl Ord for Ptr { 65 | #[inline] 66 | fn cmp(&self, other: &Self) -> Ordering { 67 | self.inner.cmp(&other.inner) 68 | } 69 | } 70 | 71 | impl PartialOrd for Ptr { 72 | #[inline] 73 | fn partial_cmp(&self, other: &Self) -> Option { 74 | self.inner.partial_cmp(&other.inner) 75 | } 76 | } 77 | 78 | impl PartialEq for Ptr { 79 | #[inline] 80 | fn eq(&self, other: &Self) -> bool { 81 | self.inner.eq(&other.inner) 82 | } 83 | } 84 | 85 | impl Clone for Ptr { 86 | #[inline] 87 | fn clone(&self) -> Self { 88 | Self { 89 | inner: self.inner.clone(), 90 | } 91 | } 92 | } 93 | 94 | impl Eq for Ptr {} 95 | 96 | impl fmt::Display for Ptr 97 | where 98 | T: fmt::Display, 99 | { 100 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 101 | self.inner.fmt(f) 102 | } 103 | } 104 | 105 | impl Error for Ptr 106 | where 107 | T: Error, 108 | { 109 | // TODO: Consider. Ptr is supposed to be a thin wrapper around error so it 110 | // can be "transient" and treated as an error. But this API offers the 111 | // option of making it be more like a wrapped error that acknowledges it 112 | // originated from the original error. Semantically that seems like a 113 | // different thing (Ptr is not a new error caused by a previous one!) 114 | // but at the same time that might make the backtrace accessible whereas 115 | // here it cannot be because backtrace is a nightly API. 116 | fn source(&self) -> Option<&(dyn Error + 'static)> { 117 | self.inner.source() 118 | } 119 | } 120 | 121 | impl From for Ptr { 122 | #[inline] 123 | fn from(t: T) -> Self { 124 | Self::new(t) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/eventual/reader.rs: -------------------------------------------------------------------------------- 1 | use super::{change::ChangeReader, *}; 2 | use crate::{error::Closed, IntoReader}; 3 | 4 | // It's tempting here to provide some API that treats the Eventual like a 5 | // Stream. That would be bad though, because it would expose all the APIs that 6 | // come with stream. For example, someone could call `.map` on a Stream, but 7 | // that would be bad because `.map` on Stream and `.map` on Eventual have very 8 | // different semantics. In general, Stream has very different semantics. It even 9 | // has a size_hint - a Stream is a progressively available Vec (distinct 10 | // values), but an Eventual is an eventually consistent and updating "latest" 11 | // value which infers no sequence and may drop intermediate values. 12 | pub struct EventualReader { 13 | change: ChangeReader, 14 | prev: Option>, 15 | } 16 | 17 | impl IntoReader for EventualReader 18 | where 19 | T: Value, 20 | { 21 | type Output = T; 22 | fn into_reader(self) -> EventualReader { 23 | self 24 | } 25 | } 26 | 27 | pub struct Next<'a, T> { 28 | eventual: &'a mut EventualReader, 29 | } 30 | 31 | impl<'a, T> Future for Next<'a, T> 32 | where 33 | T: Value, 34 | { 35 | type Output = Result; 36 | // TODO: This is currently checking for a pushed value, but that will require 37 | // eg: map() to run in separate tasks. It might be desirable to have this poll 38 | // the future that would produce values. But... that may be very complex. A 39 | // refactor may be necessary. 40 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 41 | let update = self.eventual.change.change.poll(&self.eventual.prev, cx); 42 | match update { 43 | None => Poll::Pending, 44 | Some(value) => { 45 | self.eventual.prev = Some(value.clone()); 46 | Poll::Ready(value) 47 | } 48 | } 49 | } 50 | } 51 | 52 | impl EventualReader 53 | where 54 | T: Value, 55 | { 56 | pub fn next(&mut self) -> Next { 57 | Next { eventual: self } 58 | } 59 | 60 | pub(crate) fn new(state: Arc>) -> Self { 61 | let change = state.subscribe(); 62 | 63 | EventualReader { change, prev: None } 64 | } 65 | 66 | /// This function is pretty tricky. Be sure you know what you are doing. 67 | pub(crate) fn force_dirty(&mut self) { 68 | self.prev = None; 69 | self.change.unsubscribe_from.notify_one(&self.change.change); 70 | } 71 | } 72 | 73 | // The cloned reader resumes from the same state as the source. 74 | impl Clone for EventualReader 75 | where 76 | T: Value, 77 | { 78 | fn clone(&self) -> Self { 79 | Self { 80 | prev: self.prev.clone(), 81 | // This ends up being pre-notified with the latest value, but that's 82 | // ok because it still gets de-duped when checking against 83 | // self.prev. This is nice, because otherwise all the locking is 84 | // really hard (impossible?) to get right. 85 | // 86 | // The thing to make sure we get right is that the reader is effectively 87 | // in the same state as the reader it's being cloned from. Assuming 88 | // no future writes, calling .next().poll() for both should produce 89 | // the same result. 90 | change: self.change.unsubscribe_from.clone().subscribe(), 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/eventual/shared_state.rs: -------------------------------------------------------------------------------- 1 | use futures::channel::oneshot::Sender; 2 | use std::{ 3 | collections::HashSet, 4 | ops::Deref, 5 | sync::{Arc, Mutex}, 6 | }; 7 | 8 | use super::{ 9 | change::{Change, ChangeReader, ChangeValNoWake}, 10 | *, 11 | }; 12 | 13 | pub struct SharedState { 14 | // This takes a cue from the .NET event implementation 15 | // which bets that: 16 | // Typically the number of subscribers is relatively small 17 | // Modifying the subscriber list is less frequent than posting events 18 | // These taken together advocate for a snapshot-at-time style of concurrency 19 | // which makes snapshotting very cheap but updates expensive. 20 | // 21 | // There is maybe a case for some kind of persistent immutable data structure 22 | // here (finally, for once in my career...). `im` on crates.io is a candidate. 23 | // (Lookout though, `im` uses MPL license which is incompatible with MIT) 24 | // It would probably require a lot of subscribers before paying off - which 25 | // itself requires heavy linear costs. So, I am skeptical still. 26 | pub subscribers: Mutex>>>, 27 | pub last_write: Mutex>, 28 | writer_notify: Option>, 29 | } 30 | 31 | impl Drop for SharedState { 32 | fn drop(&mut self) { 33 | if let Some(notify) = self.writer_notify.take() { 34 | // Ignore the possible error because that means 35 | // the other side is dropped. The point of notify 36 | // is to drop. 37 | let _ignore = notify.send(()); 38 | } 39 | } 40 | } 41 | 42 | impl SharedState 43 | where 44 | T: Value, 45 | { 46 | pub fn new(writer_notify: Sender<()>) -> Self { 47 | Self { 48 | subscribers: Mutex::new(Arc::new(HashSet::new())), 49 | last_write: Mutex::new(ChangeValNoWake::None), 50 | writer_notify: Some(writer_notify), 51 | } 52 | } 53 | 54 | pub fn notify_all(&self) { 55 | let snapshot = { 56 | let lock = self.subscribers.lock().unwrap(); 57 | lock.deref().clone() 58 | }; 59 | for subscriber in snapshot.iter() { 60 | self.notify_one(subscriber) 61 | } 62 | } 63 | 64 | pub fn notify_one(&self, subscriber: &Change) { 65 | subscriber.set_value(&self.last_write); 66 | } 67 | 68 | pub fn subscribe(self: Arc) -> ChangeReader { 69 | let change: Change = Change::new(); 70 | { 71 | let mut lock = self.subscribers.lock().unwrap(); 72 | let mut updated: HashSet<_> = lock.deref().deref().clone(); 73 | updated.insert(change.clone()); 74 | *lock = Arc::new(updated); 75 | } 76 | // Must notify AFTER it's in the subscriber list to avoid missing updates. 77 | self.notify_one(&change); 78 | ChangeReader { 79 | change, 80 | unsubscribe_from: self, 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/eventual/writer.rs: -------------------------------------------------------------------------------- 1 | use futures::{channel::oneshot::Receiver, future::Shared}; 2 | 3 | use super::{change::ChangeValNoWake, *}; 4 | use crate::error::Closed; 5 | use futures::FutureExt; 6 | use std::{ 7 | mem, 8 | ops::DerefMut, 9 | sync::{Arc, Weak}, 10 | }; 11 | 12 | pub struct EventualWriter 13 | where 14 | T: Value, 15 | { 16 | state: Weak>, 17 | closed: Shared>, 18 | } 19 | 20 | impl Drop for EventualWriter 21 | where 22 | T: Value, 23 | { 24 | fn drop(&mut self) { 25 | let _ignore = self.write_private(Err(Closed)); 26 | } 27 | } 28 | 29 | impl EventualWriter 30 | where 31 | T: Value, 32 | { 33 | pub(crate) fn new(state: &Arc>, closed: Receiver<()>) -> Self { 34 | Self { 35 | state: Arc::downgrade(state), 36 | closed: closed.shared(), 37 | } 38 | } 39 | 40 | pub fn closed(&self) -> impl 'static + Future + Send + Unpin { 41 | self.closed.clone() 42 | } 43 | 44 | pub fn write(&mut self, value: T) { 45 | self.write_private(Ok(value)) 46 | } 47 | 48 | fn write_private(&mut self, value: Result) { 49 | if let Some(state) = self.state.upgrade() { 50 | // See also b045e23a-f445-456f-a686-7e80de621cf2 51 | { 52 | let mut prev = state.last_write.lock().unwrap(); 53 | 54 | if let Ok(value) = value { 55 | *prev = ChangeValNoWake::Value(value); 56 | } else { 57 | match mem::replace(prev.deref_mut(), ChangeValNoWake::None) { 58 | ChangeValNoWake::None => { 59 | *prev = ChangeValNoWake::Finalized(None); 60 | } 61 | ChangeValNoWake::Value(value) => { 62 | *prev = ChangeValNoWake::Finalized(Some(value)); 63 | } 64 | ChangeValNoWake::Finalized(_) => unreachable!(), 65 | } 66 | } 67 | } 68 | state.notify_all(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod eventual; 2 | pub use eventual::*; 3 | pub mod error; 4 | pub use error::Closed; 5 | mod combinators; 6 | pub use combinators::*; 7 | 8 | // This is a convenience trait to make it easy to pass either an Eventual or an 9 | // EventualReader into functions. 10 | pub trait IntoReader { 11 | type Output: Value; 12 | fn into_reader(self) -> EventualReader; 13 | } 14 | 15 | pub trait Value: 'static + Send + Clone + Eq {} 16 | impl Value for T where T: 'static + Send + Clone + Eq {} 17 | -------------------------------------------------------------------------------- /tests/eventual.rs: -------------------------------------------------------------------------------- 1 | use eventuals::*; 2 | use futures::poll; 3 | use std::{sync::Arc, task::Poll, time::Duration}; 4 | use tokio::{join, test, time::sleep}; 5 | 6 | #[test] 7 | async fn dropped_writer_closes() { 8 | let (writer, eventual) = Eventual::::new(); 9 | let mut read_0 = eventual.subscribe(); 10 | drop(writer); 11 | assert_eq!(read_0.next().await, Err(Closed)); 12 | } 13 | 14 | #[test] 15 | async fn can_observe_value_written_after_subscribe() { 16 | let (mut writer, eventual) = Eventual::new(); 17 | let mut read_0 = eventual.subscribe(); 18 | writer.write(5); 19 | assert_eq!(read_0.next().await, Ok(5)); 20 | } 21 | 22 | #[test] 23 | async fn can_observe_value_written_before_subscribe() { 24 | let eventual = Eventual::from_value(5); 25 | let mut read_0 = eventual.subscribe(); 26 | assert_eq!(read_0.next().await, Ok(5)); 27 | assert_eq!(read_0.next().await, Err(Closed)); 28 | } 29 | 30 | #[test] 31 | async fn only_most_recent_value_is_observed() { 32 | let (mut writer, eventual) = Eventual::new(); 33 | let mut read_0 = eventual.subscribe(); 34 | writer.write(5); 35 | writer.write(10); 36 | assert_eq!(read_0.next().await, Ok(10)); 37 | } 38 | 39 | #[test] 40 | async fn drop_doesnt_interfere() { 41 | let (mut writer, eventual) = Eventual::::new(); 42 | assert_eq!(eventual.subscriber_count(), 0); 43 | let mut read_0 = eventual.subscribe(); 44 | assert_eq!(eventual.subscriber_count(), 1); 45 | let mut read_1 = eventual.subscribe(); 46 | assert_eq!(eventual.subscriber_count(), 2); 47 | writer.write(5); 48 | writer.write(10); 49 | assert_eq!(read_0.next().await, Ok(10)); 50 | drop(read_0); 51 | assert_eq!(eventual.subscriber_count(), 1); 52 | writer.write(1); 53 | // The main point of the test is this line - after 54 | // dropping one subscriber we still have our subscriber. 55 | assert_eq!(read_1.next().await, Ok(1)); 56 | drop(read_1); 57 | // It is also useful to verify that the above test passed 58 | // even though drop is in fact working. 59 | assert_eq!(eventual.subscriber_count(), 0); 60 | } 61 | 62 | #[test] 63 | async fn can_message_pass() { 64 | let (mut writer_a, eventual_a) = Eventual::::new(); 65 | let (mut writer_b, eventual_b) = Eventual::::new(); 66 | 67 | let mut eventual_a = eventual_a.subscribe(); 68 | let mut eventual_b = eventual_b.subscribe(); 69 | 70 | let b = tokio::spawn(async move { 71 | let mut sum = 0; 72 | while let Ok(v) = eventual_a.next().await { 73 | sum += v; 74 | writer_b.write(v + 1); 75 | } 76 | sum 77 | }); 78 | 79 | let a = tokio::spawn(async move { 80 | writer_a.write(0); 81 | let first = eventual_b.next().await.unwrap(); 82 | writer_a.write(first + 1); 83 | let second = eventual_b.next().await.unwrap(); 84 | writer_a.write(second + 1); 85 | assert_eq!(eventual_b.next().await, Ok(5)); 86 | drop(writer_a); 87 | assert_eq!(eventual_b.next().await, Err(Closed)); 88 | }); 89 | 90 | let (a, b) = join!(a, b); 91 | assert!(a.is_ok()); 92 | assert_eq!(b.unwrap(), 6); 93 | } 94 | 95 | // Ensures that eventuals will drop all the way down the chain "immediately" 96 | #[test] 97 | async fn chained_eventuals_drop() { 98 | let (mut writer, source) = Eventual::new(); 99 | let source = Arc::new(source); 100 | let mut new_source = source.clone(); 101 | let mut i = 0; 102 | let mapped = loop { 103 | new_source = Arc::new(new_source.subscribe().map(|v: u32| async move { v + 1 })); 104 | i += 1; 105 | if i == 25 { 106 | break new_source; 107 | } 108 | }; 109 | 110 | assert_eq!(source.subscriber_count(), 1); 111 | assert_eq!(mapped.subscriber_count(), 0); 112 | 113 | writer.write(5); 114 | assert_eq!(mapped.value().await, Ok(30)); 115 | 116 | assert_eq!(source.subscriber_count(), 1); 117 | drop(mapped); 118 | // Dropping doesn't happen on the same thread, but 119 | // it still should happen before we write a value. 120 | sleep(Duration::from_millis(1)).await; 121 | assert_eq!(source.subscriber_count(), 0); 122 | } 123 | 124 | #[test] 125 | async fn clone_reader() { 126 | let (mut writer, eventual) = Eventual::new(); 127 | writer.write(99); 128 | let mut read_0 = eventual.subscribe(); 129 | let mut read_1 = read_0.clone(); 130 | assert_eq!(read_1.next().await, Ok(99)); 131 | 132 | let poll = poll!(read_1.clone().next()); 133 | assert_eq!(Poll::Pending, poll); 134 | 135 | drop(writer); 136 | assert_eq!(read_0.clone().next().await, Ok(99)); 137 | assert_eq!(read_0.next().await, Ok(99)); 138 | assert_eq!(read_0.clone().next().await, Err(Closed)); 139 | } 140 | 141 | #[test] 142 | async fn init_with_has_value() { 143 | let (mut writer, values) = Eventual::new(); 144 | let mut values = values.init_with(5).subscribe(); 145 | assert_eq!(values.next().await, Ok(5)); 146 | writer.write(10); 147 | assert_eq!(values.next().await, Ok(10)); 148 | } 149 | 150 | // TODO: Test that closed is received twice in a row rather than getting stuck. 151 | -------------------------------------------------------------------------------- /tests/handle_errors.rs: -------------------------------------------------------------------------------- 1 | #![allow(deprecated)] 2 | 3 | use eventuals::*; 4 | use std::{ 5 | error::Error, 6 | future, 7 | sync::{Arc, Mutex}, 8 | }; 9 | use tokio::{sync::Notify, test}; 10 | 11 | #[test] 12 | async fn basic() { 13 | let (mut writer, numbers) = Eventual::::new(); 14 | let notify_read = Arc::new(Notify::new()); 15 | let notify_write1 = notify_read.clone(); 16 | let notify_write2 = notify_read.clone(); 17 | 18 | let errors = Arc::new(Mutex::new(vec![])); 19 | let errors_writer = errors.clone(); 20 | 21 | let validated: Eventual> = numbers.map(|n| match n % 2 { 22 | 0 => future::ready(Ok(n)), 23 | _ => future::ready(Err(n)), 24 | }); 25 | 26 | let even_numbers = handle_errors(validated, move |err: u32| { 27 | println!("Err: {}", err); 28 | let mut errors = errors_writer.lock().unwrap(); 29 | errors.push(err.clone()); 30 | notify_write1.notify_one() 31 | }); 32 | let _pipe = even_numbers 33 | .subscribe() 34 | .pipe(move |_| notify_write2.notify_one()); 35 | 36 | for n in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] { 37 | writer.write(n); 38 | // This test requires the notify because the result is not immediately 39 | // available. We need to wait for handle_errors to complete. In general, 40 | // Eventuals do not guarantee that the latest value is available 41 | // immediately but just that it will eventually become the latest value. 42 | // This is not a problem for real use-cases but is for tests. So, without 43 | // this notify we can see a previous value. 44 | notify_read.notified().await; 45 | assert_eq!(even_numbers.value().await.ok().unwrap(), (n / 2) * 2); 46 | } 47 | 48 | assert_eq!(*errors.lock().unwrap(), vec![1, 3, 5, 7, 9]); 49 | } 50 | 51 | #[test] 52 | async fn complex_err_with_ptr() { 53 | let (mut string_writer, strings) = Eventual::<&'static str>::new(); 54 | 55 | // Exists to wrap the err in some "dynamic" error type that does not 56 | // impl Value 57 | fn bad_parse(s: &str) -> Result> { 58 | let result: Result = s.parse(); 59 | result.map_err(|e| e.into()) 60 | } 61 | 62 | // But if we take the sad dynamic error type and use Ptr on it 63 | // we impl Value. 64 | let numbers: Eventual> = 65 | strings.map(|s| async move { bad_parse(s).map_err(Ptr::new) }); 66 | 67 | let (mut errors_writer, only_errors) = Eventual::new(); 68 | let only_numbers = numbers 69 | .subscribe() 70 | .handle_errors(move |e| errors_writer.write(e)); 71 | 72 | string_writer.write("1"); 73 | 74 | assert_eq!(only_numbers.value().await, Ok(1)); 75 | string_writer.write("a"); 76 | assert_eq!( 77 | &format!("{}", only_errors.value().await.unwrap()), 78 | "invalid digit found in string" 79 | ); 80 | assert_eq!(only_numbers.value().await, Ok(1)); 81 | } 82 | -------------------------------------------------------------------------------- /tests/idle.rs: -------------------------------------------------------------------------------- 1 | use eventuals::*; 2 | use std::time::{Duration, Instant}; 3 | use tokio::test; 4 | use tokio::time::sleep; 5 | 6 | #[test] 7 | async fn never_idle_in_map() { 8 | let (mut writer, reader) = Eventual::::new(); 9 | let mapped = reader.subscribe().map(|_v| async move { 10 | idle().await; 11 | panic!("Past idle"); 12 | }); 13 | 14 | writer.write(1); 15 | 16 | // The panic should not be hit. 17 | sleep(Duration::from_millis(10)).await; 18 | 19 | // We can still drop the map, which should make the pipeline idle. 20 | drop(mapped); 21 | idle().await; 22 | } 23 | 24 | #[test] 25 | async fn idle_after_map() { 26 | let duration = Duration::from_millis(10); 27 | 28 | idle().await; 29 | let start = Instant::now(); 30 | let mapped = Eventual::from_value(5).map(move |v| async move { 31 | sleep(duration).await; 32 | v 33 | }); 34 | idle().await; 35 | assert!(Instant::now() - start > duration); 36 | assert_eq!(mapped.value_immediate(), Some(5)); 37 | drop(mapped); 38 | } 39 | -------------------------------------------------------------------------------- /tests/join.rs: -------------------------------------------------------------------------------- 1 | use eventuals::*; 2 | use tokio::test; 3 | 4 | #[test] 5 | async fn joins_values() { 6 | let (mut a_writer, a) = Eventual::<&'static str>::new(); 7 | let (mut b_writer, b) = Eventual::::new(); 8 | 9 | a_writer.write("a"); 10 | b_writer.write(1); 11 | let mut ab = join((a, b)).subscribe(); 12 | 13 | assert_eq!(Ok(("a", 1)), ab.next().await); 14 | 15 | // Since it's a second code path for the "post-completion" updates 16 | // go ahead and write again. 17 | a_writer.write("A"); 18 | assert_eq!(Ok(("A", 1)), ab.next().await); 19 | 20 | b_writer.write(2); 21 | assert_eq!(Ok(("A", 2)), ab.next().await); 22 | } 23 | -------------------------------------------------------------------------------- /tests/map.rs: -------------------------------------------------------------------------------- 1 | use eventuals::*; 2 | use lazy_static::lazy_static; 3 | use std::{ 4 | sync::{ 5 | atomic::{AtomicU32, Ordering::SeqCst}, 6 | Arc, Mutex, 7 | }, 8 | time::{Duration, Instant}, 9 | }; 10 | use tokio::test; 11 | use tokio::{select, time::sleep}; 12 | 13 | #[test] 14 | async fn basic() { 15 | let (mut writer, eventual) = Eventual::::new(); 16 | writer.write(5); 17 | 18 | // Format the value and save it in an Arc for 19 | let format_value = |v| async move { Arc::new(format!("{}", v)) }; 20 | let mut mapped = eventual.map(format_value).subscribe(); 21 | 22 | assert_eq!(&mapped.next().await.ok().unwrap().as_str(), &"5"); 23 | 24 | writer.write(10); 25 | assert_eq!(&mapped.next().await.ok().unwrap().as_str(), &"10"); 26 | 27 | writer.write(10); // Same value, de-duplicated. 28 | drop(writer); 29 | assert_eq!(mapped.next().await, Err(Closed)) 30 | } 31 | 32 | #[test] 33 | async fn with_retry_works_eventually() { 34 | let (mut writer, nums) = Eventual::new(); 35 | writer.write(1); 36 | 37 | lazy_static! { 38 | static ref LOCK: Mutex<()> = Mutex::new(()); 39 | static ref TRIES: AtomicU32 = AtomicU32::new(0); 40 | } 41 | 42 | // In case this test is run more than once or concurrently for some reason, these 43 | // need to be here to ensure on the test is run consistently. 44 | let _lock = LOCK.lock().unwrap(); 45 | TRIES.store(0, SeqCst); 46 | 47 | let start = Instant::now(); 48 | let inviolable = nums.subscribe().map_with_retry( 49 | // Attempt 5 times on the same value before succeeding. 50 | move |n| async move { 51 | assert_eq!(n, 1); 52 | let attempt = TRIES.fetch_add(1, SeqCst); 53 | if attempt < 4 { 54 | Err(attempt) 55 | } else { 56 | Ok("ok") 57 | } 58 | }, 59 | // Sleep 1ms between failures. 60 | |_| sleep(Duration::from_millis(1)), 61 | ); 62 | 63 | // Assert that this eventually works 64 | assert_eq!(inviolable.value().await.unwrap(), "ok"); 65 | let end = Instant::now(); 66 | // Verify the sleeps. In practice this ends up much 67 | // larger than 5ms. 68 | assert!(end - start >= Duration::from_millis(5)); 69 | } 70 | 71 | #[test] 72 | async fn with_retry_gets_new_value() { 73 | let (mut writer, nums) = Eventual::::new(); 74 | writer.write(1); 75 | 76 | let inviolable = nums.map_with_retry( 77 | move |n| async move { 78 | match n { 79 | 1 => Err(()), 80 | _ => Ok(()), 81 | } 82 | }, 83 | // Sleep "forever". In practice this could be a short sleep 84 | // but we want to show that if a new value is available it 85 | // is used rather than reconstructing the pipeline. 86 | |_| sleep(Duration::from_secs(1000000000000)), 87 | ); 88 | 89 | // Demonstrate that inviolable does not produce a value. At this point retry 90 | // should be waiting for either a new value or the new eventual but gets 91 | // neither. 92 | select! { 93 | _ = inviolable.value() => { 94 | panic!("Nooooooooo!"); 95 | } 96 | _ = sleep(Duration::from_millis(10)) => {} 97 | }; 98 | // Show that when a new value is written we don't have to wait indefinitely 99 | // for the new eventual. 100 | writer.write(2); 101 | assert_eq!(inviolable.value().await.unwrap(), ()); 102 | } 103 | -------------------------------------------------------------------------------- /tests/pipe.rs: -------------------------------------------------------------------------------- 1 | use eventuals::*; 2 | use std::sync::Arc; 3 | use tokio::{ 4 | sync::{Mutex, Notify}, 5 | test, time, 6 | }; 7 | 8 | struct NotifyOnDrop { 9 | notify: Arc, 10 | } 11 | impl Drop for NotifyOnDrop { 12 | fn drop(&mut self) { 13 | self.notify.notify_one(); 14 | } 15 | } 16 | 17 | impl NotifyOnDrop { 18 | fn new() -> (Arc, Self) { 19 | let notify = Arc::new(Notify::new()); 20 | let on_drop = Self { 21 | notify: notify.clone(), 22 | }; 23 | (notify, on_drop) 24 | } 25 | } 26 | 27 | #[test] 28 | async fn produces_side_effect() { 29 | let (mut handle_writer, handle) = Eventual::new(); 30 | let (mut writer, eventual) = Eventual::new(); 31 | 32 | let _pipe = eventual.pipe(move |v| { 33 | handle_writer.write(v); 34 | }); 35 | 36 | writer.write(1); 37 | 38 | assert_eq!(Ok(1), handle.subscribe().next().await); 39 | } 40 | 41 | #[test] 42 | async fn produces_async_side_effect() { 43 | let (handle_writer, handle) = Eventual::new(); 44 | let (mut writer, eventual) = Eventual::new(); 45 | 46 | let handle_writer = Arc::new(Mutex::new(handle_writer)); 47 | let _pipe = eventual.pipe_async(move |v| { 48 | let handle_writer = handle_writer.clone(); 49 | async move { 50 | handle_writer.lock().await.write(v); 51 | } 52 | }); 53 | 54 | writer.write(1); 55 | 56 | assert_eq!(Ok(1), handle.subscribe().next().await); 57 | } 58 | 59 | #[test] 60 | async fn stops_after_drop_handle() { 61 | let (mut writer, eventual) = Eventual::new(); 62 | let (notify, notify_on_drop) = NotifyOnDrop::new(); 63 | 64 | let pipe = eventual.pipe(move |v| { 65 | if v == 2 { 66 | panic!(); 67 | } 68 | // Notifies if it either passed the panic, 69 | // or will never be called again. 70 | notify_on_drop.notify.notify_one(); 71 | }); 72 | 73 | // This test passing depends on the notifies. In part this is because 74 | // the pipe is in a spawned task. If we want to remove the first notify so 75 | // that pipe stops _immediately_ we may have to have pipe check a weak 76 | // reference to the reader each time it acts. Or, use some version of 77 | // select! that prefers cancellation over writing in spawn. 78 | writer.write(1); 79 | notify.notified().await; 80 | drop(pipe); 81 | notify.notified().await; 82 | // We know this can't panic, because we have been notified that the 83 | // closure has been dropped and can't be called again. Unfortunately 84 | // I can't think of a good way to verify it didn't panic. But, surely 85 | // it doesn't. 86 | writer.write(2); 87 | } 88 | 89 | #[test] 90 | async fn forever_cleans_up_when_writer_closed() { 91 | let (mut writer, eventual) = Eventual::new(); 92 | let (mut acker, ack) = Eventual::new(); 93 | let mut ack = ack.subscribe(); 94 | let (notify, notify_on_drop) = NotifyOnDrop::new(); 95 | 96 | eventual 97 | .pipe(move |v| { 98 | acker.write(v); 99 | let _keep = ¬ify_on_drop; 100 | }) 101 | .forever(); 102 | 103 | writer.write(1); 104 | drop(writer); 105 | 106 | // If this is notified it means that forever() cleans itself up when the writer stops. 107 | notify.notified().await; 108 | // This ensures that the last value was in fact passed through to pipe so that 109 | // a stale value is not the last observed. 110 | assert_eq!(ack.next().await, Ok(1)); 111 | } 112 | 113 | #[test] 114 | async fn forever_keeps_handle_alive() { 115 | let (mut writer, reader) = Eventual::::new(); 116 | let (mut notifier, notify) = Eventual::<()>::new(); 117 | let mut notify = notify.subscribe(); 118 | reader 119 | .pipe(move |_| { 120 | notifier.write(()); 121 | }) 122 | .forever(); 123 | 124 | time::sleep(time::Duration::from_millis(100)).await; 125 | writer.write(2); 126 | // Check that the pipe handle hasn't been dropped. If it were to drop, then 127 | // the notifier would also be closed. 128 | assert_eq!(notify.next().await, Ok(())); 129 | drop(writer); 130 | // Now the pipe handle and the notifier should be dropped. 131 | assert_eq!(notify.next().await, Err(Closed)); 132 | } 133 | -------------------------------------------------------------------------------- /tests/select.rs: -------------------------------------------------------------------------------- 1 | #![allow(deprecated)] 2 | use eventuals::*; 3 | use tokio::test; 4 | 5 | #[test] 6 | async fn it_works() { 7 | let (mut a_writer, a) = Eventual::<&'static str>::new(); 8 | let (mut b_writer, b) = Eventual::<&'static str>::new(); 9 | 10 | let either = select((a, b)); 11 | 12 | let mut values = either.subscribe(); 13 | a_writer.write("a"); 14 | assert_eq!(values.next().await, Ok("a")); 15 | b_writer.write("b"); 16 | assert_eq!(values.next().await, Ok("b")); 17 | 18 | drop(b_writer); 19 | drop(a_writer); 20 | assert_eq!(values.next().await, Err(Closed)); 21 | } 22 | -------------------------------------------------------------------------------- /tests/timer.rs: -------------------------------------------------------------------------------- 1 | use eventuals::*; 2 | use std::time::Duration; 3 | use tokio::test; 4 | 5 | #[test] 6 | async fn timer_awaits() { 7 | let interval = Duration::from_millis(2); 8 | let timer = timer(interval); 9 | let mut reader = timer.subscribe(); 10 | let start = reader.next().await.unwrap(); 11 | let end = reader.next().await.unwrap(); 12 | assert!(end - start >= interval); 13 | } 14 | --------------------------------------------------------------------------------