want-0.2.0/.gitignore010066400017500001750000000000371325254413700127030ustar0000000000000000 /target **/*.rs.bk Cargo.lock want-0.2.0/Cargo.toml.orig010066400017500001750000000006651344423776500136220ustar0000000000000000[package] name = "want" version = "0.2.0" # remember to update html_root_url description = "Detect when another Future wants a result." keywords = ["futures", "channel"] authors = ["Sean McArthur "] license = "MIT" repository = "https://github.com/seanmonstar/want" homepage = "https://github.com/seanmonstar/want" documentation = "https://docs.rs/want" [dependencies] futures = "0.1" log = "0.4" try-lock = "0.2" want-0.2.0/Cargo.toml0000644000000017250000000000000100460ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g. crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "want" version = "0.2.0" authors = ["Sean McArthur "] description = "Detect when another Future wants a result." homepage = "https://github.com/seanmonstar/want" documentation = "https://docs.rs/want" keywords = ["futures", "channel"] license = "MIT" repository = "https://github.com/seanmonstar/want" [dependencies.futures] version = "0.1" [dependencies.log] version = "0.4" [dependencies.try-lock] version = "0.2" want-0.2.0/Cargo.toml.orig0000644000000017260000000000000110060ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "want" version = "0.2.0" authors = ["Sean McArthur "] description = "Detect when another Future wants a result." homepage = "https://github.com/seanmonstar/want" documentation = "https://docs.rs/want" keywords = ["futures", "channel"] license = "MIT" repository = "https://github.com/seanmonstar/want" [dependencies.futures] version = "0.1" [dependencies.log] version = "0.4" [dependencies.try-lock] version = "0.2" want-0.2.0/LICENSE010066400017500001750000000020471344424025000117130ustar0000000000000000Copyright (c) 2018-2019 Sean McArthur Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. want-0.2.0/README.md010066400017500001750000000016411344424320300121650ustar0000000000000000# Want - [Crates.io](https://crates.io/crates/want) - [Docs](https://docs.rs/want) A `Future`s channel-like utility to signal when a value is wanted. Futures are supposed to be lazy, and only starting work if `Future::poll` is called. The same is true of `Stream`s, but when using a channel as a `Stream`, it can be hard to know if the receiver is ready for the next value. Put another way, given a `(tx, rx)` from `futures::sync::mpsc::channel()`, how can the sender (`tx`) know when the receiver (`rx`) actually wants more work to be produced? Just because there is room in the channel buffer doesn't mean the work would be used by the receiver. This is where something like `want` comes in. Added to a channel, you can make sure that the `tx` only creates the message and sends it when the `rx` has `poll()` for it, and the buffer was empty. ## License `want` is provided under the MIT license. See [LICENSE](LICENSE). want-0.2.0/benches/throughput.rs010066400017500001750000000003721343332625100150770ustar0000000000000000#![feature(test)] extern crate test; extern crate want; #[bench] fn throughput(b: &mut test::Bencher) { let (mut gv, mut tk) = want::new(); b.iter(move || { tk.want(); assert!(gv.poll_want().unwrap().is_ready()); }); } want-0.2.0/src/lib.rs010066400017500001750000000404571344424301200126170ustar0000000000000000#![doc(html_root_url = "https://docs.rs/want/0.2.0")] #![deny(warnings)] #![deny(missing_docs)] #![deny(missing_debug_implementations)] //! A Futures channel-like utility to signal when a value is wanted. //! //! Futures are supposed to be lazy, and only starting work if `Future::poll` //! is called. The same is true of `Stream`s, but when using a channel as //! a `Stream`, it can be hard to know if the receiver is ready for the next //! value. //! //! Put another way, given a `(tx, rx)` from `futures::sync::mpsc::channel()`, //! how can the sender (`tx`) know when the receiver (`rx`) actually wants more //! work to be produced? Just because there is room in the channel buffer //! doesn't mean the work would be used by the receiver. //! //! This is where something like `want` comes in. Added to a channel, you can //! make sure that the `tx` only creates the message and sends it when the `rx` //! has `poll()` for it, and the buffer was empty. //! //! # Example //! //! ``` //! extern crate futures; //! extern crate want; //! //! use futures::{Async, Stream}; //! //! // Some message that is expensive to produce. //! struct Expensive; //! //! let (mut tx, mut rx) = futures::sync::mpsc::channel(0); //! let (mut gv, mut tk) = want::new(); //! //! # fn spawn(_t: T) {} //! # fn can_we_print_msg() -> Result<(), ()> { Ok(()) } //! //! // Our receiving task... //! spawn(futures::future::poll_fn(move || loop { //! //! match rx.poll() { //! Ok(Async::Ready(Some(_msg))) => { //! println!("got a message"); //! }, //! Ok(Async::Ready(None)) => { //! println!("DONE"); //! return Ok::<_, ()>(Async::Ready(())); //! }, //! Ok(Async::NotReady) => { //! // Maybe something comes up that prevents us from ever //! // using the expensive message. //! // //! // Without `want`, the "send" task may have started to //! // produce the expensive message even though we wouldn't //! // be able to use it. //! can_we_print_msg()?; //! //! // But we can use it! So tell the `want` channel. //! tk.want(); //! }, //! Err(()) => unreachable!("mpsc doesn't error"), //! } //! })); //! //! // Our sending task //! spawn(futures::future::poll_fn(move || { //! // It's expensive to create a new message, so we wait until the //! // receiving end truly *wants* the message. //! match gv.poll_want() { //! Ok(Async::Ready(())) => { //! // They want it, let's go! //! tx.try_send(Expensive).unwrap(); //! Ok(Async::Ready(())) //! }, //! Ok(Async::NotReady) => Ok(Async::NotReady), //! Err(_) => { //! // Looks like they will never want it... //! Err(()) //! } //! } //! })); //! //! # fn main() {} //! ``` extern crate futures; #[macro_use] extern crate log; extern crate try_lock; use std::fmt; use std::mem; use std::sync::Arc; use std::sync::atomic::AtomicUsize; // SeqCst is the only ordering used to ensure accessing the state and // TryLock are never re-ordered. use std::sync::atomic::Ordering::SeqCst; use futures::{Async, Poll}; use futures::task::{self, Task}; use try_lock::TryLock; /// Create a new `want` channel. pub fn new() -> (Giver, Taker) { let inner = Arc::new(Inner { state: AtomicUsize::new(State::Idle.into()), task: TryLock::new(None), }); let inner2 = inner.clone(); ( Giver { inner: inner, }, Taker { inner: inner2, }, ) } /// An entity that gives a value when wanted. pub struct Giver { inner: Arc, } /// An entity that wants a value. pub struct Taker { inner: Arc, } /// A cloneable `Giver`. /// /// It differs from `Giver` in that you cannot poll for `want`. It's only /// usable as a cancellation watcher. #[derive(Clone)] pub struct SharedGiver { inner: Arc, } /// The `Taker` has canceled its interest in a value. pub struct Closed { _inner: (), } #[derive(Clone, Copy, Debug)] enum State { Idle, Want, Give, Closed, } impl From for usize { fn from(s: State) -> usize { match s { State::Idle => 0, State::Want => 1, State::Give => 2, State::Closed => 3, } } } impl From for State { fn from(num: usize) -> State { match num { 0 => State::Idle, 1 => State::Want, 2 => State::Give, 3 => State::Closed, _ => unreachable!("unknown state: {}", num), } } } struct Inner { state: AtomicUsize, task: TryLock>, } // ===== impl Giver ====== impl Giver { /// Poll whether the `Taker` has registered interest in another value. /// /// - If the `Taker` has called `want()`, this returns `Async::Ready(())`. /// - If the `Taker` has not called `want()` since last poll, this /// returns `Async::NotReady`, and parks the current task to be notified /// when the `Taker` does call `want()`. /// - If the `Taker` has canceled (or dropped), this returns `Closed`. /// /// After knowing that the Taker is wanting, the state can be reset by /// calling [`give`](Giver::give). pub fn poll_want(&mut self) -> Poll<(), Closed> { loop { let state = self.inner.state.load(SeqCst).into(); match state { State::Want => { trace!("poll_want: taker wants!"); return Ok(Async::Ready(())); }, State::Closed => { trace!("poll_want: closed"); return Err(Closed { _inner: () }); }, State::Idle | State::Give => { // Taker doesn't want anything yet, so park. if let Some(mut locked) = self.inner.task.try_lock_order(SeqCst, SeqCst) { // While we have the lock, try to set to GIVE. let old = self.inner.state.compare_and_swap( state.into(), State::Give.into(), SeqCst, ); // If it's still the first state (Idle or Give), park current task. if old == state.into() { let park = locked.as_ref() .map(|t| !t.will_notify_current()) .unwrap_or(true); if park { let old = mem::replace(&mut *locked, Some(task::current())); drop(locked); old.map(|prev_task| { // there was an old task parked here. // it might be waiting to be notified, // so poke it before dropping. prev_task.notify(); }); } return Ok(Async::NotReady) } // Otherwise, something happened! Go around the loop again. } else { // if we couldn't take the lock, then a Taker has it. // The *ONLY* reason is because it is in the process of notifying us // of its want. // // We need to loop again to see what state it was changed to. } }, } } } /// Mark the state as idle, if the Taker currently is wanting. /// /// Returns true if Taker was wanting, false otherwise. #[inline] pub fn give(&self) -> bool { // only set to IDLE if it is still Want self.inner.state.compare_and_swap( State::Want.into(), State::Idle.into(), SeqCst, ) == State::Want.into() } /// Check if the `Taker` has called `want()` without parking a task. /// /// This is safe to call outside of a futures task context, but other /// means of being notified is left to the user. #[inline] pub fn is_wanting(&self) -> bool { self.inner.state.load(SeqCst) == State::Want.into() } /// Check if the `Taker` has canceled interest without parking a task. #[inline] pub fn is_canceled(&self) -> bool { self.inner.state.load(SeqCst) == State::Closed.into() } /// Converts this into a `SharedGiver`. #[inline] pub fn shared(self) -> SharedGiver { SharedGiver { inner: self.inner, } } } impl fmt::Debug for Giver { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Giver") .field("state", &self.inner.state()) .finish() } } // ===== impl SharedGiver ====== impl SharedGiver { /// Check if the `Taker` has called `want()` without parking a task. /// /// This is safe to call outside of a futures task context, but other /// means of being notified is left to the user. #[inline] pub fn is_wanting(&self) -> bool { self.inner.state.load(SeqCst) == State::Want.into() } /// Check if the `Taker` has canceled interest without parking a task. #[inline] pub fn is_canceled(&self) -> bool { self.inner.state.load(SeqCst) == State::Closed.into() } } impl fmt::Debug for SharedGiver { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("SharedGiver") .field("state", &self.inner.state()) .finish() } } // ===== impl Taker ====== impl Taker { /// Signal to the `Giver` that the want is canceled. /// /// This is useful to tell that the channel is closed if you cannot /// drop the value yet. #[inline] pub fn cancel(&mut self) { trace!("signal: {:?}", State::Closed); self.signal(State::Closed) } /// Signal to the `Giver` that a value is wanted. #[inline] pub fn want(&mut self) { debug_assert!( self.inner.state.load(SeqCst) != State::Closed.into(), "want called after cancel" ); trace!("signal: {:?}", State::Want); self.signal(State::Want) } #[inline] fn signal(&mut self, state: State) { let old_state = self.inner.state.swap(state.into(), SeqCst).into(); match old_state { State::Idle | State::Want | State::Closed => (), State::Give => { loop { if let Some(mut locked) = self.inner.task.try_lock_order(SeqCst, SeqCst) { if let Some(task) = locked.take() { drop(locked); trace!("signal found waiting giver, notifying"); task.notify(); } return; } else { // if we couldn't take the lock, then a Giver has it. // The *ONLY* reason is because it is in the process of parking. // // We need to loop and take the lock so we can notify this task. } } }, } } } impl Drop for Taker { #[inline] fn drop(&mut self) { self.signal(State::Closed); } } impl fmt::Debug for Taker { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Taker") .field("state", &self.inner.state()) .finish() } } // ===== impl Closed ====== impl fmt::Debug for Closed { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Closed") .finish() } } // ===== impl Inner ====== impl Inner { #[inline] fn state(&self) -> State { self.state.load(SeqCst).into() } } #[cfg(test)] mod tests { use std::thread; use futures::{Async, Stream}; use futures::future::{poll_fn, Future}; use futures::sync::{mpsc, oneshot}; use super::*; #[test] fn want_ready() { let (mut gv, mut tk) = new(); tk.want(); assert!(gv.poll_want().unwrap().is_ready()); } #[test] fn want_notify_0() { let (mut gv, mut tk) = new(); let (tx, rx) = oneshot::channel(); thread::spawn(move || { tk.want(); // use a oneshot to keep this thread alive // until other thread was notified of want rx.wait().expect("rx"); }); poll_fn(|| { gv.poll_want() }).wait().expect("wait"); assert!(gv.is_wanting(), "still wanting after poll_want success"); assert!(gv.give(), "give is true when wanting"); assert!(!gv.is_wanting(), "no longer wanting after give"); assert!(!gv.is_canceled(), "give doesn't cancel"); assert!(!gv.give(), "give is false if not wanting"); tx.send(()).expect("tx"); } /// This tests that if the Giver moves tasks after parking, /// it will still wake up the correct task. #[test] fn want_notify_moving_tasks() { use std::sync::Arc; use futures::executor::{spawn, Notify, NotifyHandle}; struct WantNotify; impl Notify for WantNotify { fn notify(&self, _id: usize) { } } fn n() -> NotifyHandle { Arc::new(WantNotify).into() } let (mut gv, mut tk) = new(); let mut s = spawn(poll_fn(move || { gv.poll_want() })); // Register with t1 as the task::current() let t1 = n(); assert!(s.poll_future_notify(&t1, 1).unwrap().is_not_ready()); thread::spawn(move || { thread::sleep(::std::time::Duration::from_millis(100)); tk.want(); }); // And now, move to a ThreadNotify task. s.into_inner().wait().expect("poll_want"); } #[test] fn cancel() { // explicit let (mut gv, mut tk) = new(); assert!(!gv.is_canceled()); tk.cancel(); assert!(gv.is_canceled()); assert!(gv.poll_want().is_err()); // implicit let (mut gv, tk) = new(); assert!(!gv.is_canceled()); drop(tk); assert!(gv.is_canceled()); assert!(gv.poll_want().is_err()); // notifies let (mut gv, tk) = new(); thread::spawn(move || { let _tk = tk; // and dropped }); poll_fn(move || { gv.poll_want() }).wait().expect_err("wait"); } #[test] fn stress() { let nthreads = 5; let nwants = 100; for _ in 0..nthreads { let (mut gv, mut tk) = new(); let (mut tx, mut rx) = mpsc::channel(0); // rx thread thread::spawn(move || { let mut cnt = 0; poll_fn(move || { while cnt < nwants { let n = match rx.poll().expect("rx poll") { Async::Ready(n) => n.expect("rx opt"), Async::NotReady => { tk.want(); return Ok(Async::NotReady); }, }; assert_eq!(cnt, n); cnt += 1; } Ok::<_, ()>(Async::Ready(())) }).wait().expect("rx wait"); }); // tx thread thread::spawn(move || { let mut cnt = 0; let nsent = poll_fn(move || { loop { while let Ok(()) = tx.try_send(cnt) { cnt += 1; } match gv.poll_want() { Ok(Async::Ready(_)) => (), Ok(Async::NotReady) => return Ok::<_, ()>(Async::NotReady), Err(_) => return Ok(Async::Ready(cnt)), } } }).wait().expect("tx wait"); assert_eq!(nsent, nwants); }).join().expect("thread join"); } } } want-0.2.0/.cargo_vcs_info.json0000644000000001120000000000000120350ustar00{ "git": { "sha1": "0034cd2a6664c7d0e0791938a10c8687f0521225" } }