├── .gitignore ├── README.tpl ├── Cargo.toml ├── README.md ├── .travis.yml └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | # {{crate}} 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/async-option.svg)](https://crates.io/crates/async-option) 4 | [![Documentation](https://docs.rs/async-option/badge.svg)](https://docs.rs/async-option/) 5 | [![Build Status](https://travis-ci.com/jonhoo/async-option.svg?branch=master)](https://travis-ci.com/jonhoo/async-option) 6 | [![Codecov](https://codecov.io/github/jonhoo/async-option/coverage.svg?branch=master)](https://codecov.io/gh/jonhoo/async-option) 7 | 8 | {{readme}} 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-option" 3 | version = "0.1.1" 4 | authors = ["Jon Gjengset "] 5 | edition = "2018" 6 | license = "MIT" 7 | homepage = "https://github.com/jonhoo/async-option" 8 | repository = "https://github.com/jonhoo/async-option" 9 | description = "Essentially an asynchronous Async>>" 10 | categories = ["asynchronous"] 11 | 12 | [badges] 13 | travis-ci = { repository = "jonhoo/async-option" } 14 | codecov = { repository = "jonhoo/async-option", branch = "master", service = "github" } 15 | maintenance = { status = "passively-maintained" } 16 | 17 | [dependencies] 18 | futures = "0.1" 19 | tokio-sync = "0.1" 20 | 21 | [dev-dependencies] 22 | tokio-mock-task = "0.1" 23 | tokio = "0.1" 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # async-option 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/async-option.svg)](https://crates.io/crates/async-option) 4 | [![Documentation](https://docs.rs/async-option/badge.svg)](https://docs.rs/async-option/) 5 | [![Build Status](https://travis-ci.com/jonhoo/async-option.svg?branch=master)](https://travis-ci.com/jonhoo/async-option) 6 | [![Codecov](https://codecov.io/github/jonhoo/async-option/coverage.svg?branch=master)](https://codecov.io/gh/jonhoo/async-option) 7 | 8 | This crate provides an asynchronous, atomic `Option` type. 9 | 10 | At a high level, this crate is exactly like `Arc>>`, except with support for 11 | asynchronous operations. Given an [`Aption`], you can call [`poll_put`] to attempt to place 12 | a value into the `Option`, or `poll_take` to take a value out of the `Option`. Both methods 13 | will return `Async::NotReady` if the `Option` is occupied or empty respectively, and will at 14 | that point have scheduled for the current task to be notified when the `poll_*` call may 15 | succeed in the future. `Aption` can also be used as a `Sink` and `Stream` by effectively operating as a single-element channel. 17 | 18 | An `Aption` can also be closed using [`poll_close`]. Any `poll_put` after a `poll_close` 19 | will fail, and the next `poll_take` will return the current value (if any), and from then on 20 | `poll_take` will return an error. 21 | 22 | [`Aption`]: struct.Aption.html 23 | [`poll_put`]: struct.Aption.html#method.poll_put 24 | [`poll_take`]: struct.Aption.html#method.poll_take 25 | [`poll_close`]: struct.Aption.html#method.poll_close 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | 8 | # always test things that aren't pushes (like PRs) 9 | # never test tags or pushes to non-master branches (wait for PR) 10 | # https://github.com/travis-ci/travis-ci/issues/2200#issuecomment-441395545) 11 | if: type != push OR (tag IS blank AND branch = master) 12 | 13 | # an entry in stage=test will be generated for each rust/os combination. 14 | # each entry will run these commands. 15 | script: 16 | - cargo test 17 | jobs: 18 | allow_failures: 19 | - rust: nightly 20 | include: 21 | - &check 22 | stage: check # do a pre-screen to make sure this is even worth testing 23 | script: cargo check --all-targets 24 | rust: stable 25 | os: linux 26 | - <<: *check # also test oldest known-good stable 27 | rust: 1.33.0 28 | - stage: test # then run the tests 29 | rust: stable 30 | os: osx 31 | - rust: stable 32 | os: windows 33 | - &linux 34 | stage: test 35 | rust: stable 36 | os: linux 37 | - <<: *linux 38 | rust: beta 39 | - <<: *linux 40 | rust: nightly 41 | - stage: lint # we lint on beta to future-proof 42 | name: "Rust: beta, rustfmt" 43 | rust: beta 44 | os: linux 45 | script: 46 | - rustup component add rustfmt-preview 47 | - cargo fmt -v -- --check 48 | - name: "Rust: nightly, rustfmt" # and on nightly with allow_fail 49 | rust: nightly 50 | os: linux 51 | script: 52 | - rustup component add rustfmt-preview 53 | - cargo fmt -v -- --check 54 | - name: "Rust: beta, clippy" 55 | rust: beta 56 | os: linux 57 | script: 58 | - rustup component add clippy-preview 59 | - touch ./src/lib.rs && cargo clippy -- -D warnings 60 | - name: "Rust: nightly, clippy" 61 | rust: nightly 62 | os: linux 63 | script: 64 | - rustup component add clippy-preview 65 | - touch ./src/lib.rs && cargo clippy -- -D warnings 66 | - <<: *linux 67 | stage: coverage 68 | rust: nightly 69 | env: CACHE_NAME=coverage 70 | sudo: required 71 | script: 72 | - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install --force cargo-tarpaulin || true 73 | - cargo tarpaulin --out Xml 74 | - bash <(curl -s https://codecov.io/bash) 75 | - cargo clean -p async-option # ensure we don't cache build for coverage 76 | stages: 77 | - check 78 | - test 79 | - lint 80 | - coverage 81 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides an asynchronous, atomic `Option` type. 2 | //! 3 | //! At a high level, this crate is exactly like `Arc>>`, except with support for 4 | //! asynchronous operations. Given an [`Aption`], you can call [`poll_put`] to attempt to place 5 | //! a value into the `Option`, or `poll_take` to take a value out of the `Option`. Both methods 6 | //! will return `Async::NotReady` if the `Option` is occupied or empty respectively, and will at 7 | //! that point have scheduled for the current task to be notified when the `poll_*` call may 8 | //! succeed in the future. `Aption` can also be used as a `Sink` and `Stream` by effectively operating as a single-element channel. 10 | //! 11 | //! An `Aption` can also be closed using [`poll_close`]. Any `poll_put` after a `poll_close` 12 | //! will fail, and the next `poll_take` will return the current value (if any), and from then on 13 | //! `poll_take` will return an error. 14 | //! 15 | //! [`Aption`]: struct.Aption.html 16 | //! [`poll_put`]: struct.Aption.html#method.poll_put 17 | //! [`poll_take`]: struct.Aption.html#method.poll_take 18 | //! [`poll_close`]: struct.Aption.html#method.poll_close 19 | 20 | #![deny( 21 | unused_extern_crates, 22 | missing_debug_implementations, 23 | missing_docs, 24 | unreachable_pub 25 | )] 26 | #![cfg_attr(test, deny(warnings))] 27 | 28 | use futures::{task, try_ready, Async, AsyncSink, Future, Poll, Sink, StartSend, Stream}; 29 | use std::cell::UnsafeCell; 30 | use std::sync::Arc; 31 | use std::{fmt, mem}; 32 | use tokio_sync::semaphore; 33 | 34 | /// Indicates that an [`Aption`] has been closed, and no further operations are available on it. 35 | /// 36 | /// [`Aption`]: struct.Aption.html 37 | #[derive(Debug, Clone, Hash, Eq, PartialEq)] 38 | pub struct Closed; 39 | 40 | /// An asynchronous, atomic `Option` type. 41 | /// 42 | /// See the [crate-level documentation] for details. 43 | /// 44 | /// [crate-level documentation]: ../ 45 | #[derive(Debug)] 46 | pub struct Aption { 47 | inner: Arc>, 48 | permit: semaphore::Permit, 49 | } 50 | 51 | impl Clone for Aption { 52 | fn clone(&self) -> Self { 53 | Aption { 54 | inner: self.inner.clone(), 55 | permit: semaphore::Permit::new(), 56 | } 57 | } 58 | } 59 | 60 | #[allow(missing_docs)] 61 | pub fn new() -> Aption { 62 | let m = Arc::new(Inner { 63 | semaphore: semaphore::Semaphore::new(1), 64 | value: UnsafeCell::new(CellValue::None), 65 | put_task: task::AtomicTask::new(), 66 | take_task: task::AtomicTask::new(), 67 | }); 68 | 69 | Aption { 70 | inner: m.clone(), 71 | permit: semaphore::Permit::new(), 72 | } 73 | } 74 | 75 | enum CellValue { 76 | Some(T), 77 | None, 78 | Fin(Option), 79 | } 80 | 81 | impl CellValue { 82 | fn is_none(&self) -> bool { 83 | if let CellValue::None = *self { 84 | true 85 | } else { 86 | false 87 | } 88 | } 89 | 90 | fn take(&mut self) -> Option { 91 | match mem::replace(self, CellValue::None) { 92 | CellValue::None => None, 93 | CellValue::Some(t) => Some(t), 94 | CellValue::Fin(f) => { 95 | // retore Fin bit 96 | mem::replace(self, CellValue::Fin(None)); 97 | f 98 | } 99 | } 100 | } 101 | } 102 | 103 | struct Inner { 104 | semaphore: semaphore::Semaphore, 105 | value: UnsafeCell>, 106 | put_task: task::AtomicTask, 107 | take_task: task::AtomicTask, 108 | } 109 | 110 | // we never expose &T, only ever &mut T, so we only require T: Send 111 | unsafe impl Sync for Inner {} 112 | unsafe impl Send for Inner {} 113 | 114 | impl fmt::Debug for Inner { 115 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 116 | write!(f, "AptionInner") 117 | } 118 | } 119 | 120 | struct TakeFuture(Option>); 121 | impl Future for TakeFuture { 122 | type Item = (Aption, T); 123 | type Error = Closed; 124 | 125 | fn poll(&mut self) -> Poll { 126 | let t = try_ready!(self 127 | .0 128 | .as_mut() 129 | .expect("called poll after future resolved") 130 | .poll_take()); 131 | Ok(Async::Ready((self.0.take().unwrap(), t))) 132 | } 133 | } 134 | 135 | struct PutFuture(Option>, Option); 136 | impl Future for PutFuture { 137 | type Item = Aption; 138 | type Error = T; 139 | 140 | fn poll(&mut self) -> Poll { 141 | let t = self.1.take().expect("called poll after future resolved"); 142 | match self 143 | .0 144 | .as_mut() 145 | .expect("called poll after future resolved") 146 | .poll_put(t) 147 | { 148 | Ok(AsyncSink::Ready) => Ok(Async::Ready(self.0.take().unwrap())), 149 | Ok(AsyncSink::NotReady(t)) => { 150 | self.1 = Some(t); 151 | Ok(Async::NotReady) 152 | } 153 | Err(t) => Err(t), 154 | } 155 | } 156 | } 157 | 158 | impl Aption { 159 | /// Returns a `Future` that resolves when a value is successfully taken from the `Aption`. 160 | pub fn take(self) -> impl Future { 161 | TakeFuture(Some(self)) 162 | } 163 | 164 | /// Returns a `Future` that resolves when the given value is successfully placed in the 165 | /// `Aption`. 166 | pub fn put(self, t: T) -> impl Future { 167 | PutFuture(Some(self), Some(t)) 168 | } 169 | } 170 | 171 | impl Aption { 172 | /// Attempt to take the value contained in the `Aption`. 173 | /// 174 | /// Returns `NotReady` if no value is available, and schedules the current task to be woken up 175 | /// when one might be. 176 | /// 177 | /// Returns an error if the `Aption` has been closed with `Aption::poll_close`. 178 | pub fn poll_take(&mut self) -> Poll { 179 | try_ready!(self 180 | .permit 181 | .poll_acquire(&self.inner.semaphore) 182 | .map_err(|_| unreachable!("semaphore dropped while we have an Arc to it"))); 183 | 184 | // we have the lock -- is there a value? 185 | let value = unsafe { &mut *self.inner.value.get() }; 186 | 187 | let v = value.take(); 188 | if v.is_none() { 189 | // no, sadly not... 190 | // has it been closed altogether? 191 | if let CellValue::Fin(None) = *value { 192 | // it has! nothing more to do except release the permit 193 | // don't even have to wake anyone up, since we didn't take anything 194 | self.permit.release(&self.inner.semaphore); 195 | return Err(Closed); 196 | } 197 | 198 | // we're going to have to wait for someone to put a value. 199 | // we need to do this _before_ releasing the lock, 200 | // otherwise we might miss a quick notify. 201 | self.inner.take_task.register(); 202 | } 203 | 204 | // give up the lock for someone to put 205 | self.permit.release(&self.inner.semaphore); 206 | 207 | if let Some(t) = v { 208 | // let waiting putters know that they can now put 209 | self.inner.put_task.notify(); 210 | Ok(Async::Ready(t)) 211 | } else { 212 | Ok(Async::NotReady) 213 | } 214 | } 215 | 216 | /// Attempt to put a value into the `Aption`. 217 | /// 218 | /// Returns `NotReady` if there's already a value there, and schedules the current task to be 219 | /// woken up when the `Aption` is free again. 220 | /// 221 | /// Returns an error if the `Aption` has been closed with `Aption::poll_close`. 222 | pub fn poll_put(&mut self, t: T) -> Result, T> { 223 | match self.permit.poll_acquire(&self.inner.semaphore) { 224 | Ok(Async::Ready(())) => {} 225 | Ok(Async::NotReady) => { 226 | return Ok(AsyncSink::NotReady(t)); 227 | } 228 | Err(_) => { 229 | unreachable!("semaphore dropped while we have an Arc to it"); 230 | } 231 | } 232 | 233 | // we have the lock! 234 | let value = unsafe { &mut *self.inner.value.get() }; 235 | 236 | // has the channel already been closed? 237 | if let CellValue::Fin(_) = *value { 238 | // it has, so we're not going to get to send our value 239 | // we do have to release the lock though 240 | self.permit.release(&self.inner.semaphore); 241 | return Err(t); 242 | } 243 | 244 | // is there already a value there? 245 | if value.is_none() { 246 | // no, we're home free! 247 | *value = CellValue::Some(t); 248 | 249 | // give up the lock so someone can take 250 | self.permit.release(&self.inner.semaphore); 251 | 252 | // and notify any waiting takers 253 | self.inner.take_task.notify(); 254 | 255 | Ok(AsyncSink::Ready) 256 | } else { 257 | // yes, sadly, so we can't put... 258 | 259 | // we're going to have to wait for someone to take the existing value. 260 | // we need to do this _before_ releasing the lock, 261 | // otherwise we might miss a quick notify. 262 | self.inner.put_task.register(); 263 | 264 | // give up the lock so someone can take 265 | self.permit.release(&self.inner.semaphore); 266 | 267 | Ok(AsyncSink::NotReady(t)) 268 | } 269 | } 270 | 271 | /// Indicate the `Aption` as closed so that no future puts are permitted. 272 | /// 273 | /// Once this method succeeds, every subsequent call to `Aption::poll_put` will return an 274 | /// error. If there is currently a value in the `Aption`, the next call to `Aption::poll_take` 275 | /// will return that value. Any later calls to `Aption::poll_take` will return an error. 276 | pub fn poll_close(&mut self) -> Poll<(), ()> { 277 | try_ready!(self 278 | .permit 279 | .poll_acquire(&self.inner.semaphore) 280 | .map_err(|_| unreachable!("semaphore dropped while we have an Arc to it"))); 281 | 282 | // we have the lock -- wrap whatever value is there in Fin 283 | let value = unsafe { &mut *self.inner.value.get() }; 284 | let v = value.take(); 285 | *value = CellValue::Fin(v); 286 | 287 | // if the value is None, we've closed successfully! 288 | let ret = if let CellValue::Fin(None) = *value { 289 | Async::Ready(()) 290 | } else { 291 | // otherwise, we'll have to wait for someone to take 292 | // and again, *before* we release the lock 293 | self.inner.put_task.register(); 294 | Async::NotReady 295 | }; 296 | 297 | // give up the lock so someone can take 298 | self.permit.release(&self.inner.semaphore); 299 | 300 | // and notify any waiting takers 301 | self.inner.take_task.notify(); 302 | 303 | Ok(ret) 304 | } 305 | } 306 | 307 | impl Sink for Aption { 308 | type SinkItem = T; 309 | type SinkError = T; 310 | 311 | fn start_send(&mut self, item: Self::SinkItem) -> StartSend { 312 | self.poll_put(item) 313 | } 314 | 315 | fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { 316 | self.poll_close() 317 | .map_err(|_| unreachable!("failed to close because already closed elsewhere")) 318 | } 319 | } 320 | 321 | impl Stream for Aption { 322 | type Item = T; 323 | type Error = (); 324 | 325 | fn poll(&mut self) -> Poll, Self::Error> { 326 | match self.poll_take() { 327 | Ok(Async::Ready(v)) => Ok(Async::Ready(Some(v))), 328 | Ok(Async::NotReady) => Ok(Async::NotReady), 329 | Err(Closed) => { 330 | // error on take just means it's been closed 331 | Ok(Async::Ready(None)) 332 | } 333 | } 334 | } 335 | } 336 | 337 | #[cfg(test)] 338 | mod test { 339 | use super::*; 340 | use tokio_mock_task::MockTask; 341 | 342 | #[test] 343 | fn basic() { 344 | let mut mt = MockTask::new(); 345 | 346 | let mut a = new::(); 347 | assert_eq!(mt.enter(|| a.poll_take()), Ok(Async::NotReady)); 348 | assert!(!mt.is_notified()); 349 | assert_eq!(mt.enter(|| a.poll_put(42)), Ok(AsyncSink::Ready)); 350 | assert!(mt.is_notified()); // taker is notified 351 | assert_eq!(mt.enter(|| a.poll_take()), Ok(Async::Ready(42))); 352 | 353 | assert_eq!(mt.enter(|| a.poll_take()), Ok(Async::NotReady)); 354 | assert!(!mt.is_notified()); 355 | assert_eq!(mt.enter(|| a.poll_put(43)), Ok(AsyncSink::Ready)); 356 | assert!(mt.is_notified()); // taker is notified 357 | assert_eq!(mt.enter(|| a.poll_put(44)), Ok(AsyncSink::NotReady(44))); 358 | assert!(!mt.is_notified()); 359 | assert_eq!(mt.enter(|| a.poll_take()), Ok(Async::Ready(43))); 360 | assert!(mt.is_notified()); // putter is notified 361 | assert_eq!(mt.enter(|| a.poll_take()), Ok(Async::NotReady)); 362 | assert!(!mt.is_notified()); 363 | assert_eq!(mt.enter(|| a.poll_put(44)), Ok(AsyncSink::Ready)); 364 | assert!(mt.is_notified()); 365 | 366 | // close fails since there's still a message to be sent 367 | assert_eq!(mt.enter(|| a.poll_close()), Ok(Async::NotReady)); 368 | assert_eq!(mt.enter(|| a.poll_take()), Ok(Async::Ready(44))); 369 | assert!(mt.is_notified()); // closer is notified 370 | assert_eq!(mt.enter(|| a.poll_close()), Ok(Async::Ready(()))); 371 | assert!(!mt.is_notified()); 372 | assert_eq!(mt.enter(|| a.poll_take()), Err(Closed)); 373 | } 374 | 375 | #[test] 376 | fn sink_stream() { 377 | use tokio::prelude::*; 378 | 379 | let a = new::(); 380 | let (mut tx, rx) = tokio_sync::mpsc::unbounded_channel(); 381 | tokio::run(future::lazy(move || { 382 | tokio::spawn( 383 | rx.forward(a.clone().sink_map_err(|_| unreachable!())) 384 | .map(|_| ()) 385 | .map_err(|_| unreachable!()), 386 | ); 387 | 388 | // send a bunch of things, and make sure we get them all 389 | tx.try_send(1).unwrap(); 390 | tx.try_send(2).unwrap(); 391 | tx.try_send(3).unwrap(); 392 | tx.try_send(4).unwrap(); 393 | tx.try_send(5).unwrap(); 394 | drop(tx); 395 | 396 | a.collect() 397 | .inspect(|v| { 398 | assert_eq!(v, &[1, 2, 3, 4, 5]); 399 | }) 400 | .map(|_| ()) 401 | })); 402 | } 403 | 404 | #[test] 405 | fn futures() { 406 | use tokio::prelude::*; 407 | 408 | let a = new::(); 409 | tokio::run(future::lazy(move || { 410 | a.put(42) 411 | .map_err(|_| unreachable!()) 412 | .and_then(|a| a.take()) 413 | .map_err(|_| unreachable!()) 414 | .and_then(|(a, v)| { 415 | assert_eq!(v, 42); 416 | a.put(43) 417 | }) 418 | .map_err(|_| unreachable!()) 419 | .and_then(|a| a.take()) 420 | .map_err(|_| unreachable!()) 421 | .inspect(|(_, v)| { 422 | assert_eq!(*v, 43); 423 | }) 424 | .map(|_| ()) 425 | })); 426 | } 427 | 428 | #[test] 429 | fn notified_on_empty_drop() { 430 | let mut mt = MockTask::new(); 431 | 432 | let mut a = new::(); 433 | assert_eq!(mt.enter(|| a.poll_take()), Ok(Async::NotReady)); 434 | assert!(!mt.is_notified()); 435 | assert_eq!(mt.enter(|| a.poll_close()), Ok(Async::Ready(()))); 436 | assert!(mt.is_notified()); 437 | assert_eq!(mt.enter(|| a.poll_take()), Err(Closed)); 438 | } 439 | } 440 | --------------------------------------------------------------------------------