From 84a4cd01c5102b67d6891fa1a187fd72ef194e06 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Wed, 17 Jan 2018 21:48:18 -0800 Subject: [PATCH 01/70] Generalize "unfold" combinator. unfold functions now must return a Future of an Option rather than an Option of a future. This allows users to hold off deciding whether or not a value should be returned. --- src/stream/unfold.rs | 38 ++++++++++++++++++++------------------ tests/unfold.rs | 11 ++++++----- tests/unsync.rs | 2 +- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/stream/unfold.rs b/src/stream/unfold.rs index ac427b8c3b..356f1ff448 100644 --- a/src/stream/unfold.rs +++ b/src/stream/unfold.rs @@ -13,7 +13,7 @@ use stream::Stream; /// for the returned `Future` to complete with `(a, b)`. It will then yield the /// value `a`, and use `b` as the next internal state. /// -/// If the closure returns `None` instead of `Some(Future)`, then the `unfold()` +/// If the future returns `None` instead of `Some`, then the `unfold()` /// will stop producing items and return `Ok(Async::Ready(None))` in future /// calls to `poll()`. /// @@ -33,22 +33,23 @@ use stream::Stream; /// use futures::future::{self, Future}; /// /// let mut stream = stream::unfold(0, |state| { -/// if state <= 2 { -/// let next_state = state + 1; -/// let yielded = state * 2; -/// let fut = future::ok::<_, u32>((yielded, next_state)); -/// Some(fut) -/// } else { -/// None -/// } +/// future::ok::<_, u32>( +/// if state <= 2 { +/// let next_state = state + 1; +/// let yielded = state * 2; +/// Some((yielded, next_state)) +/// } else { +/// None +/// } +/// ) /// }); /// /// let result = stream.collect().wait(); /// assert_eq!(result, Ok(vec![0, 2, 4])); /// ``` pub fn unfold(init: T, f: F) -> Unfold - where F: FnMut(T) -> Option, - Fut: IntoFuture, + where F: FnMut(T) -> Fut, + Fut: IntoFuture>, { Unfold { f: f, @@ -67,8 +68,8 @@ pub struct Unfold where Fut: IntoFuture { } impl Stream for Unfold - where F: FnMut(T) -> Option, - Fut: IntoFuture, + where F: FnMut(T) -> Fut, + Fut: IntoFuture>, { type Item = It; type Error = Fut::Error; @@ -79,17 +80,18 @@ impl Stream for Unfold // State::Empty may happen if the future returned an error State::Empty => { return Ok(Async::Ready(None)); } State::Ready(state) => { - match (self.f)(state) { - Some(fut) => { self.state = State::Processing(fut.into_future()); } - None => { return Ok(Async::Ready(None)); } - } + let fut = (self.f)(state); + self.state = State::Processing(fut.into_future()); } State::Processing(mut fut) => { match fut.poll()? { - Async:: Ready((item, next_state)) => { + Async::Ready(Some((item, next_state))) => { self.state = State::Ready(next_state); return Ok(Async::Ready(Some(item))); } + Async::Ready(None) => { + return Ok(Async::Ready(None)); + } Async::NotReady => { self.state = State::Processing(fut); return Ok(Async::NotReady); diff --git a/tests/unfold.rs b/tests/unfold.rs index 1669a18aa5..eef2716053 100644 --- a/tests/unfold.rs +++ b/tests/unfold.rs @@ -3,6 +3,7 @@ extern crate futures; mod support; use futures::stream; +use futures::future::{ok, Either}; use support::*; @@ -10,10 +11,10 @@ use support::*; fn unfold1() { let mut stream = stream::unfold(0, |state| { if state <= 2 { - let res: Result<_,()> = Ok((state * 2, state + 1)); - Some(delay_future(res)) + let res = ok::<_, ()>(Some((state * 2, state + 1))); + Either::A(delay_future(res)) } else { - None + Either::B(ok(None)) } }); // Creates the future with the closure @@ -37,9 +38,9 @@ fn unfold1() { fn unfold_err1() { let mut stream = stream::unfold(0, |state| { if state <= 2 { - Some(Ok((state * 2, state + 1))) + Ok(Some((state * 2, state + 1))) } else { - Some(Err(-1)) + Err(-1) } }); sassert_next(&mut stream, 0); diff --git a/tests/unsync.rs b/tests/unsync.rs index e86b95ca5b..5af933c1ec 100644 --- a/tests/unsync.rs +++ b/tests/unsync.rs @@ -116,7 +116,7 @@ fn mpsc_send_unpark() { #[test] fn spawn_sends_items() { let core = Core::new(); - let stream = unfold(0, |i| Some(Ok::<_,u8>((i, i + 1)))); + let stream = unfold(0, |i| Ok::<_,u8>(Some((i, i + 1)))); let rx = mpsc::spawn(stream, &core, 1); assert_eq!(core.run(rx.take(4).collect()).unwrap(), [0, 1, 2, 3]); From db7799587562627e03ba011058cb4f02645f76fb Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Wed, 17 Jan 2018 23:34:12 -0800 Subject: [PATCH 02/70] Replace the Future impl for Option with IntoFuture --- src/future/option.rs | 32 ++++++++++++++++++++++++++------ tests/all.rs | 4 ++-- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/future/option.rs b/src/future/option.rs index 1b204d376a..fddb8a2044 100644 --- a/src/future/option.rs +++ b/src/future/option.rs @@ -1,13 +1,33 @@ //! Definition of the `Option` (optional step) combinator -use {Future, Poll, Async}; +use {Future, IntoFuture, Poll, Async}; +use std::option; -impl Future for Option where F: Future { - type Item = Option; - type Error = E; +/// An optional future. +/// +/// Created by `Option::into_future`. +#[derive(Debug, Clone)] +#[must_use = "futures do nothing unless polled"] +pub struct Option { + inner: option::Option, +} + +impl IntoFuture for option::Option where F: Future { + type Future = Option; + type Item = option::Option; + type Error = F::Error; + + fn into_future(self) -> Self::Future { + Option { inner: self } + } +} + +impl Future for Option where F: Future { + type Item = option::Option; + type Error = F::Error; - fn poll(&mut self) -> Poll, E> { - match *self { + fn poll(&mut self) -> Poll { + match self.inner { None => Ok(Async::Ready(None)), Some(ref mut x) => x.poll().map(|x| x.map(Some)), } diff --git a/tests/all.rs b/tests/all.rs index bdd67315c5..da88485715 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -356,8 +356,8 @@ fn select2() { #[test] fn option() { - assert_eq!(Ok(Some(())), Some(ok::<(), ()>(())).wait()); - assert_eq!(Ok(None), > as Future>::wait(None)); + assert_eq!(Ok(Some(())), Some(ok::<(), ()>(())).into_future().wait()); + assert_eq!(Ok(None), None::>.into_future().wait()); } #[test] From 121769c03fbe74c1c8842de4604d888a418fd8b4 Mon Sep 17 00:00:00 2001 From: Diggory Blake Date: Thu, 8 Feb 2018 17:27:33 +0000 Subject: [PATCH 03/70] Fix docs for task::notify --- src/task_impl/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/task_impl/mod.rs b/src/task_impl/mod.rs index 132173459f..0a3e917df6 100644 --- a/src/task_impl/mod.rs +++ b/src/task_impl/mod.rs @@ -109,7 +109,7 @@ impl Task { /// Indicate that the task should attempt to poll its future in a timely /// fashion. /// - /// It's typically guaranteed that, for each call to `notify`, `poll` will + /// It's typically guaranteed that, after calling `notify`, `poll` will /// be called at least once subsequently (unless the future has terminated). /// If the task is currently polling its future when `notify` is called, it /// must poll the future *again* afterwards, ensuring that all relevant From 7243cfe6ce370c2638ea6e885e6742b62a60fa34 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 10 Feb 2018 08:59:25 -0800 Subject: [PATCH 04/70] Migrate repository links --- CHANGELOG.md | 2 +- Cargo.toml | 8 ++++---- README.md | 4 ++-- futures-cpupool/Cargo.toml | 4 ++-- futures-cpupool/README.md | 4 ++-- src/future/mod.rs | 4 ++-- src/lib.rs | 2 +- src/stream/mod.rs | 6 +++--- src/sync/oneshot.rs | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15b7d1166d..0d0c673176 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -234,7 +234,7 @@ Notable deprecations in the 0.1.4 release that will be deleted in an eventual * The `failed` constructor is now `err`. * The `done` constructor is now `result`. -As always, please report bugs to https://github.com/alexcrichton/futures-rs and +As always, please report bugs to https://github.com/rust-lang-nursery/futures-rs and we always love feedback! If you've got situations we don't cover, combinators you'd like to see, or slow code, please let us know! diff --git a/Cargo.toml b/Cargo.toml index c3e7760ecc..416bb93cc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,8 @@ authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" keywords = ["futures", "async", "future"] -repository = "https://github.com/alexcrichton/futures-rs" -homepage = "https://github.com/alexcrichton/futures-rs" +repository = "https://github.com/rust-lang-nursery/futures-rs" +homepage = "https://github.com/rust-lang-nursery/futures-rs" documentation = "https://docs.rs/futures" description = """ An implementation of futures and streams featuring zero allocations, @@ -15,8 +15,8 @@ composability, and iterator-like interfaces. categories = ["asynchronous"] [badges] -travis-ci = { repository = "alexcrichton/futures-rs" } -appveyor = { repository = "alexcrichton/futures-rs" } +travis-ci = { repository = "rust-lang-nursery/futures-rs" } +appveyor = { repository = "rust-lang-nursery/futures-rs" } [dependencies] diff --git a/README.md b/README.md index de3b99c021..aeb3fd4e22 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ This library is an implementation of **zero-cost futures** in Rust. -[![Build Status](https://travis-ci.org/alexcrichton/futures-rs.svg?branch=master)](https://travis-ci.org/alexcrichton/futures-rs) -[![Build status](https://ci.appveyor.com/api/projects/status/yl5w3ittk4kggfsh?svg=true)](https://ci.appveyor.com/project/alexcrichton/futures-rs) +[![Build Status](https://travis-ci.org/rust-lang-nursery/futures-rs.svg?branch=master)](https://travis-ci.org/rust-lang-nursery/futures-rs) +[![Build status](https://ci.appveyor.com/api/projects/status/yl5w3ittk4kggfsh?svg=true)](https://ci.appveyor.com/project/rust-lang-nursery/futures-rs) [![Crates.io](https://img.shields.io/crates/v/futures.svg?maxAge=2592000)](https://crates.io/crates/futures) [Documentation](https://docs.rs/futures) diff --git a/futures-cpupool/Cargo.toml b/futures-cpupool/Cargo.toml index fdeb541896..fee4c0799f 100644 --- a/futures-cpupool/Cargo.toml +++ b/futures-cpupool/Cargo.toml @@ -3,8 +3,8 @@ name = "futures-cpupool" version = "0.1.8" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" -repository = "https://github.com/alexcrichton/futures-rs" -homepage = "https://github.com/alexcrichton/futures-rs" +repository = "https://github.com/rust-lang-nursery/futures-rs" +homepage = "https://github.com/rust-lang-nursery/futures-rs" documentation = "https://docs.rs/futures-cpupool" description = """ An implementation of thread pools which hand out futures to the results of the diff --git a/futures-cpupool/README.md b/futures-cpupool/README.md index b022994fb5..9037b2e18e 100644 --- a/futures-cpupool/README.md +++ b/futures-cpupool/README.md @@ -3,8 +3,8 @@ A library for creating futures representing work happening concurrently on a dedicated thread pool. -[![Build Status](https://travis-ci.org/alexcrichton/futures-rs.svg?branch=master)](https://travis-ci.org/alexcrichton/futures-rs) -[![Build status](https://ci.appveyor.com/api/projects/status/yl5w3ittk4kggfsh?svg=true)](https://ci.appveyor.com/project/alexcrichton/futures-rs) +[![Build Status](https://travis-ci.org/rust-lang-nursery/futures-rs.svg?branch=master)](https://travis-ci.org/rust-lang-nursery/futures-rs) +[![Build status](https://ci.appveyor.com/api/projects/status/yl5w3ittk4kggfsh?svg=true)](https://ci.appveyor.com/project/rust-lang-nursery/futures-rs) [Documentation](https://docs.rs/futures-cpupool) diff --git a/src/future/mod.rs b/src/future/mod.rs index 7cccd907b0..063322e29d 100644 --- a/src/future/mod.rs +++ b/src/future/mod.rs @@ -102,7 +102,7 @@ if_std! { #[doc(hidden)] #[deprecated(note = "removed without replacement, recommended to use a \ local extension trait or function if needed, more \ - details in https://github.com/alexcrichton/futures-rs/issues/228")] + details in https://github.com/rust-lang-nursery/futures-rs/issues/228")] pub type BoxFuture = ::std::boxed::Box + Send>; impl Future for ::std::boxed::Box { @@ -323,7 +323,7 @@ pub trait Future { #[doc(hidden)] #[deprecated(note = "removed without replacement, recommended to use a \ local extension trait or function if needed, more \ - details in https://github.com/alexcrichton/futures-rs/issues/228")] + details in https://github.com/rust-lang-nursery/futures-rs/issues/228")] #[allow(deprecated)] fn boxed(self) -> BoxFuture where Self: Sized + Send + 'static diff --git a/src/lib.rs b/src/lib.rs index ac27d3bc5f..59e4874b2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -153,7 +153,7 @@ //! Some more information can also be found in the [README] for now, but //! otherwise feel free to jump in to the docs below! //! -//! [README]: https://github.com/alexcrichton/futures-rs#futures-rs +//! [README]: https://github.com/rust-lang-nursery/futures-rs#futures-rs #![no_std] #![deny(missing_docs, missing_debug_implementations)] diff --git a/src/stream/mod.rs b/src/stream/mod.rs index 566d143b94..b674aefdcd 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -128,7 +128,7 @@ if_std! { #[doc(hidden)] #[deprecated(note = "removed without replacement, recommended to use a \ local extension trait or function if needed, more \ - details in https://github.com/alexcrichton/futures-rs/issues/228")] + details in https://github.com/rust-lang-nursery/futures-rs/issues/228")] pub type BoxStream = ::std::boxed::Box + Send>; impl Stream for ::std::boxed::Box { @@ -179,7 +179,7 @@ if_std! { /// entirely. If one of these use cases suits you perfectly and not the other, /// please feel welcome to comment on [the issue][being considered]! /// -/// [being considered]: https://github.com/alexcrichton/futures-rs/issues/206 +/// [being considered]: https://github.com/rust-lang-nursery/futures-rs/issues/206 pub trait Stream { /// The type of item this stream will yield on success. type Item; @@ -271,7 +271,7 @@ pub trait Stream { #[doc(hidden)] #[deprecated(note = "removed without replacement, recommended to use a \ local extension trait or function if needed, more \ - details in https://github.com/alexcrichton/futures-rs/issues/228")] + details in https://github.com/rust-lang-nursery/futures-rs/issues/228")] #[allow(deprecated)] fn boxed(self) -> BoxStream where Self: Sized + Send + 'static, diff --git a/src/sync/oneshot.rs b/src/sync/oneshot.rs index d95883d3ba..6db7cb5837 100644 --- a/src/sync/oneshot.rs +++ b/src/sync/oneshot.rs @@ -206,7 +206,7 @@ impl Inner { // under the hood. If it instead used `Release` / `Acquire` ordering, // then it would not necessarily synchronize with `inner.complete` // and deadlock might be possible, as was observed in - // https://github.com/alexcrichton/futures-rs/pull/219. + // https://github.com/rust-lang-nursery/futures-rs/pull/219. self.complete.store(true, SeqCst); if let Some(mut slot) = self.rx_task.try_lock() { if let Some(task) = slot.take() { From 9c7f6b2922a0a42f65c99201de8af69baeb7763f Mon Sep 17 00:00:00 2001 From: Ben Ashford Date: Wed, 14 Feb 2018 08:29:53 +0000 Subject: [PATCH 05/70] Remove unneccessary poll_complete when emptying buffer --- src/sink/buffer.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sink/buffer.rs b/src/sink/buffer.rs index 034d571cb2..419579d9a0 100644 --- a/src/sink/buffer.rs +++ b/src/sink/buffer.rs @@ -49,9 +49,6 @@ impl Buffer { if let AsyncSink::NotReady(item) = self.sink.start_send(item)? { self.buf.push_front(item); - // ensure that we attempt to complete any pushes we've started - self.sink.poll_complete()?; - return Ok(Async::NotReady); } } From 90cc4aaeb3b027e279f2b2a4621c39f6edcb101b Mon Sep 17 00:00:00 2001 From: Dominik Werder Date: Thu, 15 Feb 2018 10:01:51 +0100 Subject: [PATCH 06/70] Fix if condition in Sender::drop() The if clause was never reached because there was always at least one weak reference remaining, which is our own. --- src/unsync/mpsc.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/unsync/mpsc.rs b/src/unsync/mpsc.rs index 68cf6b8cb9..ba0d52dc98 100644 --- a/src/unsync/mpsc.rs +++ b/src/unsync/mpsc.rs @@ -110,7 +110,11 @@ impl Drop for Sender { Some(shared) => shared, None => return, }; - if Rc::weak_count(&shared) == 0 { + // The number of existing `Weak` indicates if we are possibly the last + // `Sender`. If we are the last, we possibly must notify a blocked + // `Receiver`. `self.shared` is always one of the `Weak` to this shared + // data. Therefore the smallest possible Rc::weak_count(&shared) is 1. + if Rc::weak_count(&shared) == 1 { if let Some(task) = shared.borrow_mut().blocked_recv.take() { // Wake up receiver as its stream has ended task.notify(); From bb654f0b2c423edbc848fdc4f7327ce5d885f22e Mon Sep 17 00:00:00 2001 From: Dominik Werder Date: Thu, 15 Feb 2018 11:46:29 +0100 Subject: [PATCH 07/70] Add test case for issue #766 --- tests/unsync.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/unsync.rs b/tests/unsync.rs index d09a6522e3..4f101eb50f 100644 --- a/tests/unsync.rs +++ b/tests/unsync.rs @@ -201,3 +201,22 @@ fn spawn_kill_dead_stream() { }, } } + +/// Test case for PR #768 (issue #766). +#[test] +fn dropped_sender_of_unused_channel_notifies_receiver() { + let core = Core::new(); + let (tx, rx) = mpsc::channel::(1); + let future_1: Box> = Box::new( + futures::stream::iter_ok(vec![]) + .forward(tx) + .map_err(|_: mpsc::SendError| ()) + .map(|_| ()) + ); + let future_2: Box> = Box::new( + rx.fold((), |_, _| Ok(())).map(|_|()) + ); + // The order of the tested futures is important to test fix of PR #768. + // We want future_2 to poll on the Receiver before the Sender is dropped. + core.run(futures::future::join_all(vec![future_2, future_1])).unwrap(); +} From 02bcbc9d6659f4e6a641fa8b030c75fab1c71aed Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 15 Feb 2018 14:00:09 +0100 Subject: [PATCH 08/70] add is_done to future::Fuse --- src/future/fuse.rs | 12 ++++++++++++ tests/fuse.rs | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/future/fuse.rs b/src/future/fuse.rs index e37eace0c1..05ad3d5afa 100644 --- a/src/future/fuse.rs +++ b/src/future/fuse.rs @@ -19,6 +19,18 @@ pub fn new(f: A) -> Fuse { } } +impl Fuse { + /// Returns whether the underlying future has finished or not. + /// + /// If this method returns `true`, then all future calls to `poll` + /// are guaranteed to return `Ok(Async::NotReady)`. If this returns + /// false, then the underlying future has not been driven to + /// completion. + pub fn is_done(&self) -> bool { + self.future.is_none() + } +} + impl Future for Fuse { type Item = A::Item; type Error = A::Error; diff --git a/tests/fuse.rs b/tests/fuse.rs index a1e6cee2f1..177d914e19 100644 --- a/tests/fuse.rs +++ b/tests/fuse.rs @@ -13,3 +13,27 @@ fn fuse() { assert!(future.poll_future_notify(¬ify_panic(), 0).unwrap().is_ready()); assert!(future.poll_future_notify(¬ify_panic(), 0).unwrap().is_not_ready()); } + +#[test] +fn fuse_is_done() { + use futures::future::{Fuse, FutureResult}; + + struct Wrapped(Fuse>); + + impl Future for Wrapped { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll<(), ()> { + assert!(!self.0.is_done()); + assert_eq!(self.0.poll().unwrap(), Async::Ready(2)); + assert!(self.0.is_done()); + assert_eq!(self.0.poll().unwrap(), Async::NotReady); + assert!(self.0.is_done()); + + Ok(Async::Ready(())) + } + } + + assert!(Wrapped(ok::(2).fuse()).wait().is_ok()); +} \ No newline at end of file From f006128bddaf963fca06b1b11c3b935be417b2ef Mon Sep 17 00:00:00 2001 From: Dominik Werder Date: Thu, 15 Feb 2018 19:09:23 +0100 Subject: [PATCH 09/70] Extend test case with the opposite future join order --- tests/unsync.rs | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/tests/unsync.rs b/tests/unsync.rs index 4f101eb50f..6493d8adc0 100644 --- a/tests/unsync.rs +++ b/tests/unsync.rs @@ -207,16 +207,28 @@ fn spawn_kill_dead_stream() { fn dropped_sender_of_unused_channel_notifies_receiver() { let core = Core::new(); let (tx, rx) = mpsc::channel::(1); - let future_1: Box> = Box::new( - futures::stream::iter_ok(vec![]) - .forward(tx) - .map_err(|_: mpsc::SendError| ()) - .map(|_| ()) - ); - let future_2: Box> = Box::new( - rx.fold((), |_, _| Ok(())).map(|_|()) - ); + let make_future_1 = |tx| -> Box> { + Box::new(futures::stream::iter_ok(vec![]) + .forward(tx) + .map_err(|_: mpsc::SendError| ()) + .map(|_| ()) + ) + }; + let make_future_2 = |rx: mpsc::Receiver| -> Box> { + Box::new(rx.fold((), |_, _| Ok(())).map(|_| ())) + }; + // The order of the tested futures is important to test fix of PR #768. // We want future_2 to poll on the Receiver before the Sender is dropped. - core.run(futures::future::join_all(vec![future_2, future_1])).unwrap(); + core.run(futures::future::join_all(vec![ + make_future_2(rx), + make_future_1(tx), + ])).unwrap(); + + // Test the opposite order as well: + let (tx, rx) = mpsc::channel::(1); + core.run(futures::future::join_all(vec![ + make_future_1(tx), + make_future_2(rx), + ])).unwrap(); } From 9d2e026a6362e1706856f490b54773705185bc52 Mon Sep 17 00:00:00 2001 From: Dominik Werder Date: Fri, 16 Feb 2018 08:08:26 +0100 Subject: [PATCH 10/70] Add timeout to the test case, clean up --- tests/unsync.rs | 71 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/tests/unsync.rs b/tests/unsync.rs index 6493d8adc0..8bd5d21cd4 100644 --- a/tests/unsync.rs +++ b/tests/unsync.rs @@ -202,33 +202,64 @@ fn spawn_kill_dead_stream() { } } + /// Test case for PR #768 (issue #766). +/// The issue was: +/// Given that an empty channel is polled by the Receiver, and the only Sender +/// gets dropped without sending anything, then the Receiver would get stuck. + #[test] fn dropped_sender_of_unused_channel_notifies_receiver() { let core = Core::new(); - let (tx, rx) = mpsc::channel::(1); - let make_future_1 = |tx| -> Box> { - Box::new(futures::stream::iter_ok(vec![]) - .forward(tx) - .map_err(|_: mpsc::SendError| ()) - .map(|_| ()) - ) + type FUTURE = Box>; + + // Constructs the channel which we want to test, and two futures which + // act on that channel. + let pair = |reverse| -> Vec { + // This is the channel which we want to test. + let (tx, rx) = mpsc::channel::(1); + let mut futures: Vec = vec![ + Box::new(futures::stream::iter_ok(vec![]) + .forward(tx) + .map_err(|_: mpsc::SendError| ()) + .map(|_| 42) + ), + Box::new(rx.fold((), |_, _| Ok(())) + .map(|_| 24) + ), + ]; + if reverse { + futures.reverse(); + } + futures }; - let make_future_2 = |rx: mpsc::Receiver| -> Box> { - Box::new(rx.fold((), |_, _| Ok(())).map(|_| ())) + + let make_test_future = |reverse| -> Box, Error=()>> { + let f = futures::future::join_all(pair(reverse)); + + // Use a timeout. This is not meant to test the `sync::oneshot` but + // merely uses it to implement this timeout. + let (timeout_tx, timeout_rx) = futures::sync::oneshot::channel::>(); + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_millis(1000)); + let x = timeout_tx.send(vec![0]); + assert!(x.is_err(), "Test timed out."); + }); + + Box::new(f.select(timeout_rx.map_err(|_|())) + .map_err(|x| x.0) + .map(|x| x.0) + ) }; // The order of the tested futures is important to test fix of PR #768. // We want future_2 to poll on the Receiver before the Sender is dropped. - core.run(futures::future::join_all(vec![ - make_future_2(rx), - make_future_1(tx), - ])).unwrap(); - - // Test the opposite order as well: - let (tx, rx) = mpsc::channel::(1); - core.run(futures::future::join_all(vec![ - make_future_1(tx), - make_future_2(rx), - ])).unwrap(); + let result = core.run(make_test_future(false)); + assert!(result.is_ok()); + assert_eq!(vec![42, 24], result.unwrap()); + + // Test also the other ordering: + let result = core.run(make_test_future(true)); + assert!(result.is_ok()); + assert_eq!(vec![24, 42], result.unwrap()); } From f283a98fa343fbd39fecd76eae2e0d76e5ad8222 Mon Sep 17 00:00:00 2001 From: Tatsuyuki Ishi Date: Sun, 25 Feb 2018 13:43:48 +0900 Subject: [PATCH 11/70] Fix no_std build --- src/future/option.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/future/option.rs b/src/future/option.rs index fddb8a2044..c53f2e59fe 100644 --- a/src/future/option.rs +++ b/src/future/option.rs @@ -1,7 +1,7 @@ //! Definition of the `Option` (optional step) combinator use {Future, IntoFuture, Poll, Async}; -use std::option; +use core::option; /// An optional future. /// From 7f31843a7fc2392556d367f5f8c8821060c950ac Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Tue, 27 Feb 2018 16:56:21 -0800 Subject: [PATCH 12/70] produce docs for 0.2 branch --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f3bf51c67f..b7b6f5cb37 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ matrix: before_script: - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH after_success: - - travis-cargo doc-upload + - travis-cargo doc-upload --branch 0.2 - os: linux rust: 1.15.0 script: cargo test From 3cf324279f8dd87cdeb1cd74ec964c718befd9af Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Tue, 27 Feb 2018 17:10:13 -0800 Subject: [PATCH 13/70] remove doc auto-upload --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index b7b6f5cb37..448cb6cfb4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,6 @@ matrix: - rust: beta - rust: nightly env: BENCH=1 - before_script: - - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH - after_success: - - travis-cargo doc-upload --branch 0.2 - os: linux rust: 1.15.0 script: cargo test From 16b6dbb851ecdbeab24cd2589e827e9181732718 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Mon, 19 Mar 2018 15:29:41 -0700 Subject: [PATCH 14/70] First pass at rewriting `AtomicTask` --- src/task_impl/atomic_task.rs | 185 +++++++++++++++++------------------ 1 file changed, 89 insertions(+), 96 deletions(-) diff --git a/src/task_impl/atomic_task.rs b/src/task_impl/atomic_task.rs index a89e20c70f..0001e434a3 100644 --- a/src/task_impl/atomic_task.rs +++ b/src/task_impl/atomic_task.rs @@ -1,11 +1,9 @@ -#![allow(dead_code)] - use super::Task; use core::fmt; use core::cell::UnsafeCell; use core::sync::atomic::AtomicUsize; -use core::sync::atomic::Ordering::{Acquire, Release}; +use core::sync::atomic::Ordering::{Acquire, Release, AcqRel}; /// A synchronization primitive for task notification. /// @@ -31,32 +29,14 @@ pub struct AtomicTask { task: UnsafeCell>, } -/// Initial state, the `AtomicTask` is currently not being used. -/// -/// The value `2` is picked specifically because it between the write lock & -/// read lock values. Since the read lock is represented by an incrementing -/// counter, this enables an atomic fetch_sub operation to be used for releasing -/// a lock. -const WAITING: usize = 2; - -/// The `register` function has determined that the task is no longer current. -/// This implies that `AtomicTask::register` is being called from a different -/// task than is represented by the currently stored task. The write lock is -/// obtained to update the task cell. -const LOCKED_WRITE: usize = 0; - -/// At least one call to `notify` happened concurrently to `register` updating -/// the task cell. This state is detected when `register` exits the mutation -/// code and signals to `register` that it is responsible for notifying its own -/// task. -const LOCKED_WRITE_NOTIFIED: usize = 1; - - -/// The `notify` function has locked access to the task cell for notification. -/// -/// The constant is left here mostly for documentation reasons. -#[allow(dead_code)] -const LOCKED_READ: usize = 3; +/// Idle state +const WAITING: usize = 0; + +/// A new task value is being registered with the `AtomicTask` cell. +const REGISTERING: usize = 0b01; + +/// The task currently registered with the `AtomicTask` cell is being notified. +const NOTIFYING: usize = 0b10; impl AtomicTask { /// Create an `AtomicTask` initialized with the given `Task` @@ -73,6 +53,13 @@ impl AtomicTask { /// Registers the current task to be notified on calls to `notify`. /// + /// This is the same as calling `register_task` with `task::current()`. + pub fn register(&self) { + self.register_task(super::current()); + } + + /// Registers the provided task to be notified on calls to `notify`. + /// /// The new task will take place of any previous tasks that were registered /// by previous calls to `register`. Any calls to `notify` that happen after /// a call to `register` (as defined by the memory ordering rules), will @@ -86,40 +73,67 @@ impl AtomicTask { /// idea. Concurrent calls to `register` will attempt to register different /// tasks to be notified. One of the callers will win and have its task set, /// but there is no guarantee as to which caller will succeed. - pub fn register(&self) { - // Get a new task handle - let task = super::current(); - - match self.state.compare_and_swap(WAITING, LOCKED_WRITE, Acquire) { + pub fn register_task(&self, task: Task) { + match self.state.compare_and_swap(WAITING, REGISTERING, Acquire) { WAITING => { unsafe { - // Locked acquired, update the task cell - *self.task.get() = Some(task); - - // Release the lock. If the state transitioned to - // `LOCKED_NOTIFIED`, this means that an notify has been - // signaled, so notify the task. - if LOCKED_WRITE_NOTIFIED == self.state.swap(WAITING, Release) { - (*self.task.get()).as_ref().unwrap().notify(); + // Locked acquired, update the waker cell + *self.task.get() = Some(task.clone()); + + // Release the lock. If the state transitioned to include + // the `NOTIFYING` bit, this means that a notify has been + // called concurrently, so we have to remove the task and + // notify it.` + // + // Start by assuming that the state is `REGISTERING` as this + // is what we jut set it to. + let mut curr = REGISTERING; + + // If a task has to be notified, it will be set here. + let mut notify = None; + + loop { + let res = self.state.compare_exchange( + curr, WAITING, AcqRel, Acquire); + + match res { + Ok(_) => { + // The atomic exchange was successful, now break + // out of the loop os that task stored in + // `notify` can be notified (if set). + break; + } + Err(actual) => { + // Update `curr` for the next iteration of the + // loop + curr = actual; + } + } + + // Since we aren't using the weak variant of the atomic + // operation, the only possible option for `curr` is to + // also include the `NOTIFYING` bit. + debug_assert_eq!(curr, curr | NOTIFYING); + + // Take the task to notify once the atomic operation has + // completed. + notify = (*self.task.get()).take(); + } + + if let Some(task) = notify { + task.notify(); } } } - LOCKED_WRITE | LOCKED_WRITE_NOTIFIED => { - // A thread is concurrently calling `register`. This shouldn't - // happen as it doesn't really make much sense, but it isn't - // unsafe per se. Since two threads are concurrently trying to - // update the task, it's undefined which one "wins" (no ordering - // guarantees), so we can just do nothing. - } - state => { - debug_assert!(state != LOCKED_WRITE, "unexpected state LOCKED_WRITE"); - debug_assert!(state != LOCKED_WRITE_NOTIFIED, "unexpected state LOCKED_WRITE_NOTIFIED"); - - // Currently in a read locked state, this implies that `notify` - // is currently being called on the old task handle. So, we call - // notify on the new task handle + NOTIFYING => { + // Currently in the process of notifying the task, i.e., + // `notify` is currently being called on the old task handle. + // So, we call notify on the new task handle task.notify(); } + _ => { + // TODO: Assert valid state + } } } @@ -127,49 +141,28 @@ impl AtomicTask { /// /// If `register` has not been called yet, then this does nothing. pub fn notify(&self) { - let mut curr = WAITING; - - loop { - if curr == LOCKED_WRITE { - // Transition the state to LOCKED_NOTIFIED - let actual = self.state.compare_and_swap(LOCKED_WRITE, LOCKED_WRITE_NOTIFIED, Release); - - if curr == actual { - // Success, return - return; - } - - // update current state variable and try again - curr = actual; - - } else if curr == LOCKED_WRITE_NOTIFIED { - // Currently in `LOCKED_WRITE_NOTIFIED` state, nothing else to do. - return; - - } else { - // Currently in a LOCKED_READ state, so attempt to increment the - // lock count. - let actual = self.state.compare_and_swap(curr, curr + 1, Acquire); - - // Locked acquired - if actual == curr { - // Notify the task - unsafe { - if let Some(ref task) = *self.task.get() { - task.notify(); - } - } + // AcqRel ordering is used in order to acquire the value of the `task` + // cell as well as to establish a `release` ordering with whatever + // memory the `AtomicTask` is associated with. + match self.state.fetch_or(NOTIFYING, AcqRel) { + WAITING => { + // The notifying lock has been acquired. + let task = unsafe { (*self.task.get()).take() }; - // Release the lock - self.state.fetch_sub(1, Release); + // Release the lock + self.state.fetch_and(!NOTIFYING, Release); - // Done - return; + if let Some(task) = task { + task.notify(); } - - // update current state variable and try again - curr = actual; - + } + _ => { + // There is a concurrent thread currently updating the + // associated task. + // + // Nothing more to do as the `NOTIFYING` bit has been set + // + // TODO: Validate the state } } } From 6563e9b4cb04c47b04c3da4118b995361ca0983b Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Tue, 20 Mar 2018 10:33:54 -0700 Subject: [PATCH 15/70] Address feedback --- src/task_impl/atomic_task.rs | 62 ++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/src/task_impl/atomic_task.rs b/src/task_impl/atomic_task.rs index 0001e434a3..6c3422cf84 100644 --- a/src/task_impl/atomic_task.rs +++ b/src/task_impl/atomic_task.rs @@ -90,38 +90,38 @@ impl AtomicTask { let mut curr = REGISTERING; // If a task has to be notified, it will be set here. - let mut notify = None; + let mut notify: Option = None; loop { let res = self.state.compare_exchange( - curr, WAITING, AcqRel, Acquire); + curr, WAITING, Release, Acquire); match res { Ok(_) => { - // The atomic exchange was successful, now break - // out of the loop os that task stored in - // `notify` can be notified (if set). - break; + // The atomic exchange was successful, now + // notify the task (if set) and return. + if let Some(task) = notify { + task.notify(); + } + + return; } Err(actual) => { + // This branch can only be reached if a + // concurrent thread called `notify`. In this + // case, `actual` **must** be `REGISTERING | + // `NOTIFYING`. + debug_assert_eq!(curr, REGISTERING | NOTIFYING); + + // Take the task to notify once the atomic operation has + // completed. + notify = (*self.task.get()).take(); + // Update `curr` for the next iteration of the // loop curr = actual; } } - - // Since we aren't using the weak variant of the atomic - // operation, the only possible option for `curr` is to - // also include the `NOTIFYING` bit. - debug_assert_eq!(curr, curr | NOTIFYING); - - // Take the task to notify once the atomic operation has - // completed. - notify = (*self.task.get()).take(); - } - - if let Some(task) = notify { - task.notify(); } } } @@ -131,8 +131,17 @@ impl AtomicTask { // So, we call notify on the new task handle task.notify(); } - _ => { - // TODO: Assert valid state + state => { + // In this case, a concurrent thread is holding the + // "registering" lock. This probably indicates a bug in the + // caller's code as racing to call `register` doesn't make much + // sense. + // + // We just want to maintain memory safety. It is ok to drop the + // call to `register`. + debug_assert!( + state == REGISTERING || + state == REGISTERING | NOTIFYING); } } } @@ -156,13 +165,18 @@ impl AtomicTask { task.notify(); } } - _ => { + state => { // There is a concurrent thread currently updating the // associated task. // - // Nothing more to do as the `NOTIFYING` bit has been set + // Nothing more to do as the `NOTIFYING` bit has been set. It + // doesn't matter if there are concurrent registering threads or + // not. // - // TODO: Validate the state + debug_assert!( + state == REGISTERING || + state == REGISTERING | NOTIFYING || + state == NOTIFYING); } } } From 1dacb2e047d54f654dd37c5840871d498e1f567d Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Tue, 20 Mar 2018 11:04:31 -0700 Subject: [PATCH 16/70] Document the implementation --- src/task_impl/atomic_task.rs | 98 +++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/src/task_impl/atomic_task.rs b/src/task_impl/atomic_task.rs index 6c3422cf84..3e21deb0a9 100644 --- a/src/task_impl/atomic_task.rs +++ b/src/task_impl/atomic_task.rs @@ -29,6 +29,102 @@ pub struct AtomicTask { task: UnsafeCell>, } +// `AtomicTask` is a multi-consumer, single-producer transfer cell. The cell +// stores a `Task` value produced by calls to `register` and many threads can +// race to take the task (to notify it) by calling `notify. +// +// If a new `Task` instance is produced by calling `register` before an existing +// one is consumed, then the existing one is overwritten. +// +// While `AtomicTask` is single-producer, the implementation ensures memory +// safety. In the event of concurrent calls to `register`, there will be a +// single winner whose task will get stored in the cell. The losers will not +// have their tasks notified. As such, callers should ensure to add +// synchronization to calls to `register`. +// +// The implementation uses a single `AtomicUsize` value to coordinate access to +// the `Task` cell. There are two bits that are operated on independently. These +// are represented by `REGISTERING` and `NOTIFYING`. +// +// The `REGISTERING` bit is set when a producer enters the critical section. The +// `NOTIFYING` bit is set when a consumer enters the critical section. Neither +// bit being set is represented by `WAITING`. +// +// A thread obtains an exclusive lock on the task cell by transitioning the +// state from `WAITING` to `REGISTERING` or `NOTIFYING`, depending on the +// operation the thread wishes to perform. When this transition is made, it is +// guaranteed that no other thread will access the task cell. +// +// # Registering +// +// On a call to `register`, an attempt to transition the state from WAITING to +// REGISTERING is made. On success, the caller obtains a lock on the task cell. +// +// If the lock is obtained, then the thread sets the task cell to the task +// provided as an argument. Then it attempts to transition the state back from +// `REGISTERING` -> `WAITING`. +// +// If this transition is successful, then the registering process is complete +// and the next call to `notify` will observe the task. +// +// If the transition fails, then there was a concurrent call to `notify` that +// was unable to access the task cell (due to the registering thread holding the +// lock). To handle this, the registering thread removes the task it just set +// from the cell and calls `notify` on it. This call to notify represents the +// attempt to notify by the other thread (that set the `NOTIFYING` bit). The +// state is then transitioned from `REGISTERING | NOTIFYING` back to `WAITING`. +// This transition must succeed because, at this point, the state cannot be +// transitioned by another thread. +// +// # Notifying +// +// On a call to `notify`, an attempt to transition the state from `WAITING` to +// `NOTIFYING` is made. On success, the caller obtains a lock on the task cell. +// +// If the lock is obtained, then the thread takes ownership of the current value +// in teh task cell, and calls `notify` on it. The state is then transitioned +// back to `WAITING`. This transition must succeed as, at this point, the state +// cannot be transitioned by another thread. +// +// If the thread is unable to obtain the lock, the `NOTIFYING` bit is still. +// This is because it has either been set by the current thread but the previous +// value included the `REGISTERING` bit **or** a concurrent thread is in the +// `NOTIFYING` critical section. Either way, no action must be taken. +// +// If the current thread is the only concurrent call to `notify` and another +// thread is in the `register` critical section, when the other thread **exits** +// the `register` critical section, it will observe the `NOTIFYING` bit and +// handle the notify itself. +// +// If another thread is in the `notify` critical section, then it will handle +// notifying the task. +// +// # A potential race (is safely handled). +// +// Imagine the following situation: +// +// * Thread A obtains the `notify` lock and notifies a task. +// +// * Before thread A releases the `notify` lock, the notified task is scheduled. +// +// * Thread B attempts to notify the task. In theory this should result in the +// task being notified, but it cannot because thread A still holds the notify +// lock. +// +// This case is handled by requiring users of `AtomicTask` to call `register` +// **before** attempting to observe the application state change that resulted +// in the task being notified. The notifiers also change the application state +// before calling notify. +// +// Because of this, the task will do one of two things. +// +// 1) Observe the application state change that Thread B is notifying on. In +// this case, it is OK for Thread B's notification to be lost. +// +// 2) Call register before attempting to observe the application state. Since +// Thread A still holds the `notify` lock, the call to `register` will result +// in the task notifying itself and get scheduled again. + /// Idle state const WAITING: usize = 0; @@ -111,7 +207,7 @@ impl AtomicTask { // concurrent thread called `notify`. In this // case, `actual` **must** be `REGISTERING | // `NOTIFYING`. - debug_assert_eq!(curr, REGISTERING | NOTIFYING); + debug_assert_eq!(actual, REGISTERING | NOTIFYING); // Take the task to notify once the atomic operation has // completed. From 1c7fbe17e581e4ed1fc4e6d8b4df59ab6ede0bf2 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Tue, 20 Mar 2018 11:27:12 -0700 Subject: [PATCH 17/70] The success ordering must be greater than the error ordering. Also, copy / paste fail --- src/task_impl/atomic_task.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/task_impl/atomic_task.rs b/src/task_impl/atomic_task.rs index 3e21deb0a9..46881b9eca 100644 --- a/src/task_impl/atomic_task.rs +++ b/src/task_impl/atomic_task.rs @@ -190,7 +190,7 @@ impl AtomicTask { loop { let res = self.state.compare_exchange( - curr, WAITING, Release, Acquire); + curr, WAITING, AcqRel, Acquire); match res { Ok(_) => { From 5d1adc21e6d10e1897c83fc7e6a06220f8eab673 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Tue, 20 Mar 2018 14:52:38 -0700 Subject: [PATCH 18/70] 0.1.19 Release --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 416bb93cc4..4a3730b79b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "futures" -version = "0.1.18" +version = "0.1.19" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" From c5c976d9ee90311ac517c2b5ffe685081709da7c Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Tue, 27 Mar 2018 15:10:57 +0200 Subject: [PATCH 19/70] Revert "Replace the Future impl for Option with IntoFuture" This reverts commit db7799587562627e03ba011058cb4f02645f76fb. --- Cargo.toml | 2 +- src/future/option.rs | 32 ++++++-------------------------- tests/all.rs | 4 ++-- 3 files changed, 9 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4a3730b79b..33a5152984 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "futures" -version = "0.1.19" +version = "0.1.20" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" diff --git a/src/future/option.rs b/src/future/option.rs index c53f2e59fe..1b204d376a 100644 --- a/src/future/option.rs +++ b/src/future/option.rs @@ -1,33 +1,13 @@ //! Definition of the `Option` (optional step) combinator -use {Future, IntoFuture, Poll, Async}; -use core::option; +use {Future, Poll, Async}; -/// An optional future. -/// -/// Created by `Option::into_future`. -#[derive(Debug, Clone)] -#[must_use = "futures do nothing unless polled"] -pub struct Option { - inner: option::Option, -} - -impl IntoFuture for option::Option where F: Future { - type Future = Option; - type Item = option::Option; - type Error = F::Error; - - fn into_future(self) -> Self::Future { - Option { inner: self } - } -} - -impl Future for Option where F: Future { - type Item = option::Option; - type Error = F::Error; +impl Future for Option where F: Future { + type Item = Option; + type Error = E; - fn poll(&mut self) -> Poll { - match self.inner { + fn poll(&mut self) -> Poll, E> { + match *self { None => Ok(Async::Ready(None)), Some(ref mut x) => x.poll().map(|x| x.map(Some)), } diff --git a/tests/all.rs b/tests/all.rs index da88485715..bdd67315c5 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -356,8 +356,8 @@ fn select2() { #[test] fn option() { - assert_eq!(Ok(Some(())), Some(ok::<(), ()>(())).into_future().wait()); - assert_eq!(Ok(None), None::>.into_future().wait()); + assert_eq!(Ok(Some(())), Some(ok::<(), ()>(())).wait()); + assert_eq!(Ok(None), > as Future>::wait(None)); } #[test] From 08697c8ad9998b396f2abf6c2063acc60889cc43 Mon Sep 17 00:00:00 2001 From: Dan Glastonbury Date: Wed, 4 Apr 2018 14:35:32 +1000 Subject: [PATCH 20/70] Allow creation of CpuPool to be fallible. To allow error handling in the face of errors returned by thread creation. --- futures-cpupool/src/lib.rs | 40 +++++++++++++++++++++++++++------- futures-cpupool/tests/smoke.rs | 21 ++++++++---------- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/futures-cpupool/src/lib.rs b/futures-cpupool/src/lib.rs index 0614368ba3..a08c2d99d1 100644 --- a/futures-cpupool/src/lib.rs +++ b/futures-cpupool/src/lib.rs @@ -19,7 +19,7 @@ //! # fn main() { //! //! // Create a worker thread pool with four threads -//! let pool = CpuPool::new(4); +//! let pool = CpuPool::new(4).unwrap(); //! //! // Execute some work on the thread pool, optionally closing over data. //! let a = pool.spawn(long_running_future(2)); @@ -40,6 +40,7 @@ extern crate futures; extern crate num_cpus; +use std::io; use std::panic::{self, AssertUnwindSafe}; use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; @@ -147,16 +148,24 @@ impl CpuPool { /// /// ```rust /// # use futures_cpupool::{Builder, CpuPool}; + /// # use std::io; /// # - /// # fn new(size: usize) -> CpuPool { + /// # fn new(size: usize) -> io::Result { /// Builder::new().pool_size(size).create() /// # } /// ``` /// + /// # Errors + /// + /// This method yields an [`io::Result`] to capture any failure to + /// create the thread at the OS level. + /// + /// [`io::Result`]: https://doc.rust-lang.org/stable/std/io/type.Result.html + /// /// # Panics /// /// Panics if `size == 0`. - pub fn new(size: usize) -> CpuPool { + pub fn new(size: usize) -> io::Result { Builder::new().pool_size(size).create() } @@ -167,12 +176,20 @@ impl CpuPool { /// /// ```rust /// # use futures_cpupool::{Builder, CpuPool}; + /// # use std::io; /// # - /// # fn new_num_cpus() -> CpuPool { + /// # fn new_num_cpus() -> io::Result { /// Builder::new().create() /// # } /// ``` - pub fn new_num_cpus() -> CpuPool { + /// + /// # Errors + /// + /// This method yields an [`io::Result`] to capture any failure to + /// create the thread at the OS level. + /// + /// [`io::Result`]: https://doc.rust-lang.org/stable/std/io/type.Result.html + pub fn new_num_cpus() -> io::Result { Builder::new().create() } @@ -398,10 +415,17 @@ impl Builder { /// Create CpuPool with configured parameters /// + /// # Errors + /// + /// This method yields an [`io::Result`] to capture any failure to + /// create the thread at the OS level. + /// + /// [`io::Result`]: https://doc.rust-lang.org/stable/std/io/type.Result.html + /// /// # Panics /// /// Panics if `pool_size == 0`. - pub fn create(&mut self) -> CpuPool { + pub fn create(&mut self) -> io::Result { let (tx, rx) = mpsc::channel(); let pool = CpuPool { inner: Arc::new(Inner { @@ -424,9 +448,9 @@ impl Builder { if self.stack_size > 0 { thread_builder = thread_builder.stack_size(self.stack_size); } - thread_builder.spawn(move || inner.work(after_start, before_stop)).unwrap(); + thread_builder.spawn(move || inner.work(after_start, before_stop))?; } - return pool + Ok(pool) } } diff --git a/futures-cpupool/tests/smoke.rs b/futures-cpupool/tests/smoke.rs index 1b267f2f02..31e60974a4 100644 --- a/futures-cpupool/tests/smoke.rs +++ b/futures-cpupool/tests/smoke.rs @@ -6,7 +6,7 @@ use std::thread; use std::time::Duration; use futures::future::Future; -use futures_cpupool::{CpuPool, Builder}; +use futures_cpupool::{Builder, CpuPool}; fn done(t: T) -> Box + Send> { Box::new(futures::future::ok(t)) @@ -14,7 +14,7 @@ fn done(t: T) -> Box + Send> { #[test] fn join() { - let pool = CpuPool::new(2); + let pool = CpuPool::new(2).unwrap(); let a = pool.spawn(done(1)); let b = pool.spawn(done(2)); let res = a.join(b).map(|(a, b)| a + b).wait(); @@ -24,7 +24,7 @@ fn join() { #[test] fn select() { - let pool = CpuPool::new(2); + let pool = CpuPool::new(2).unwrap(); let a = pool.spawn(done(1)); let b = pool.spawn(done(2)); let (item1, next) = a.select(b).wait().ok().unwrap(); @@ -48,7 +48,7 @@ fn threads_go_away() { thread_local!(static FOO: A = A); - let pool = CpuPool::new(2); + let pool = CpuPool::new(2).unwrap(); let _handle = pool.spawn_fn(|| { FOO.with(|_| ()); Ok::<(), ()>(()) @@ -57,7 +57,7 @@ fn threads_go_away() { for _ in 0..100 { if CNT.load(Ordering::SeqCst) == 1 { - return + return; } thread::sleep(Duration::from_millis(10)); } @@ -81,10 +81,9 @@ fn lifecycle_test() { .pool_size(4) .after_start(after_start) .before_stop(before_stop) - .create(); - let _handle = pool.spawn_fn(|| { - Ok::<(), ()>(()) - }); + .create() + .unwrap(); + let _handle = pool.spawn_fn(|| Ok::<(), ()>(())); drop(pool); for _ in 0..100 { @@ -99,9 +98,7 @@ fn lifecycle_test() { #[test] fn thread_name() { - let pool = Builder::new() - .name_prefix("my-pool-") - .create(); + let pool = Builder::new().name_prefix("my-pool-").create().unwrap(); let future = pool.spawn_fn(|| { assert!(thread::current().name().unwrap().starts_with("my-pool-")); Ok::<(), ()>(()) From 48eacfcdeda8f9806b046cc654d9f43bb23edc4c Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Thu, 5 Apr 2018 10:56:29 -0700 Subject: [PATCH 21/70] Revert "Allow creation of CpuPool to be fallible." This reverts commit 08697c8ad9998b396f2abf6c2063acc60889cc43. --- futures-cpupool/src/lib.rs | 40 +++++++--------------------------- futures-cpupool/tests/smoke.rs | 21 ++++++++++-------- 2 files changed, 20 insertions(+), 41 deletions(-) diff --git a/futures-cpupool/src/lib.rs b/futures-cpupool/src/lib.rs index a08c2d99d1..0614368ba3 100644 --- a/futures-cpupool/src/lib.rs +++ b/futures-cpupool/src/lib.rs @@ -19,7 +19,7 @@ //! # fn main() { //! //! // Create a worker thread pool with four threads -//! let pool = CpuPool::new(4).unwrap(); +//! let pool = CpuPool::new(4); //! //! // Execute some work on the thread pool, optionally closing over data. //! let a = pool.spawn(long_running_future(2)); @@ -40,7 +40,6 @@ extern crate futures; extern crate num_cpus; -use std::io; use std::panic::{self, AssertUnwindSafe}; use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; @@ -148,24 +147,16 @@ impl CpuPool { /// /// ```rust /// # use futures_cpupool::{Builder, CpuPool}; - /// # use std::io; /// # - /// # fn new(size: usize) -> io::Result { + /// # fn new(size: usize) -> CpuPool { /// Builder::new().pool_size(size).create() /// # } /// ``` /// - /// # Errors - /// - /// This method yields an [`io::Result`] to capture any failure to - /// create the thread at the OS level. - /// - /// [`io::Result`]: https://doc.rust-lang.org/stable/std/io/type.Result.html - /// /// # Panics /// /// Panics if `size == 0`. - pub fn new(size: usize) -> io::Result { + pub fn new(size: usize) -> CpuPool { Builder::new().pool_size(size).create() } @@ -176,20 +167,12 @@ impl CpuPool { /// /// ```rust /// # use futures_cpupool::{Builder, CpuPool}; - /// # use std::io; /// # - /// # fn new_num_cpus() -> io::Result { + /// # fn new_num_cpus() -> CpuPool { /// Builder::new().create() /// # } /// ``` - /// - /// # Errors - /// - /// This method yields an [`io::Result`] to capture any failure to - /// create the thread at the OS level. - /// - /// [`io::Result`]: https://doc.rust-lang.org/stable/std/io/type.Result.html - pub fn new_num_cpus() -> io::Result { + pub fn new_num_cpus() -> CpuPool { Builder::new().create() } @@ -415,17 +398,10 @@ impl Builder { /// Create CpuPool with configured parameters /// - /// # Errors - /// - /// This method yields an [`io::Result`] to capture any failure to - /// create the thread at the OS level. - /// - /// [`io::Result`]: https://doc.rust-lang.org/stable/std/io/type.Result.html - /// /// # Panics /// /// Panics if `pool_size == 0`. - pub fn create(&mut self) -> io::Result { + pub fn create(&mut self) -> CpuPool { let (tx, rx) = mpsc::channel(); let pool = CpuPool { inner: Arc::new(Inner { @@ -448,9 +424,9 @@ impl Builder { if self.stack_size > 0 { thread_builder = thread_builder.stack_size(self.stack_size); } - thread_builder.spawn(move || inner.work(after_start, before_stop))?; + thread_builder.spawn(move || inner.work(after_start, before_stop)).unwrap(); } - Ok(pool) + return pool } } diff --git a/futures-cpupool/tests/smoke.rs b/futures-cpupool/tests/smoke.rs index 31e60974a4..1b267f2f02 100644 --- a/futures-cpupool/tests/smoke.rs +++ b/futures-cpupool/tests/smoke.rs @@ -6,7 +6,7 @@ use std::thread; use std::time::Duration; use futures::future::Future; -use futures_cpupool::{Builder, CpuPool}; +use futures_cpupool::{CpuPool, Builder}; fn done(t: T) -> Box + Send> { Box::new(futures::future::ok(t)) @@ -14,7 +14,7 @@ fn done(t: T) -> Box + Send> { #[test] fn join() { - let pool = CpuPool::new(2).unwrap(); + let pool = CpuPool::new(2); let a = pool.spawn(done(1)); let b = pool.spawn(done(2)); let res = a.join(b).map(|(a, b)| a + b).wait(); @@ -24,7 +24,7 @@ fn join() { #[test] fn select() { - let pool = CpuPool::new(2).unwrap(); + let pool = CpuPool::new(2); let a = pool.spawn(done(1)); let b = pool.spawn(done(2)); let (item1, next) = a.select(b).wait().ok().unwrap(); @@ -48,7 +48,7 @@ fn threads_go_away() { thread_local!(static FOO: A = A); - let pool = CpuPool::new(2).unwrap(); + let pool = CpuPool::new(2); let _handle = pool.spawn_fn(|| { FOO.with(|_| ()); Ok::<(), ()>(()) @@ -57,7 +57,7 @@ fn threads_go_away() { for _ in 0..100 { if CNT.load(Ordering::SeqCst) == 1 { - return; + return } thread::sleep(Duration::from_millis(10)); } @@ -81,9 +81,10 @@ fn lifecycle_test() { .pool_size(4) .after_start(after_start) .before_stop(before_stop) - .create() - .unwrap(); - let _handle = pool.spawn_fn(|| Ok::<(), ()>(())); + .create(); + let _handle = pool.spawn_fn(|| { + Ok::<(), ()>(()) + }); drop(pool); for _ in 0..100 { @@ -98,7 +99,9 @@ fn lifecycle_test() { #[test] fn thread_name() { - let pool = Builder::new().name_prefix("my-pool-").create().unwrap(); + let pool = Builder::new() + .name_prefix("my-pool-") + .create(); let future = pool.spawn_fn(|| { assert!(thread::current().name().unwrap().starts_with("my-pool-")); Ok::<(), ()>(()) From ca3f634d65ac49a3f02e7e17f782a7239667c679 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Thu, 3 May 2018 16:27:52 +0200 Subject: [PATCH 22/70] Implement Clone for Sink combinators where it makes sense --- src/sink/from_err.rs | 2 +- src/sink/map_err.rs | 2 +- src/sink/with.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sink/from_err.rs b/src/sink/from_err.rs index 92c218fe46..4880c30ef4 100644 --- a/src/sink/from_err.rs +++ b/src/sink/from_err.rs @@ -5,7 +5,7 @@ use {Sink, Poll, StartSend}; /// A sink combinator to change the error type of a sink. /// /// This is created by the `Sink::from_err` method. -#[derive(Debug)] +#[derive(Clone, Debug)] #[must_use = "futures do nothing unless polled"] pub struct SinkFromErr { sink: S, diff --git a/src/sink/map_err.rs b/src/sink/map_err.rs index cccad399e5..25c168c071 100644 --- a/src/sink/map_err.rs +++ b/src/sink/map_err.rs @@ -3,7 +3,7 @@ use sink::Sink; use {Poll, StartSend, Stream}; /// Sink for the `Sink::sink_map_err` combinator. -#[derive(Debug)] +#[derive(Clone,Debug)] #[must_use = "sinks do nothing unless polled"] pub struct SinkMapErr { sink: S, diff --git a/src/sink/with.rs b/src/sink/with.rs index b0d5c54c65..3326b6e49c 100644 --- a/src/sink/with.rs +++ b/src/sink/with.rs @@ -7,7 +7,7 @@ use stream::Stream; /// Sink for the `Sink::with` combinator, chaining a computation to run *prior* /// to pushing a value into the underlying sink. -#[derive(Debug)] +#[derive(Clone, Debug)] #[must_use = "sinks do nothing unless polled"] pub struct With where S: Sink, @@ -20,7 +20,7 @@ pub struct With _phantom: PhantomData, } -#[derive(Debug)] +#[derive(Clone, Debug)] enum State { Empty, Process(Fut), From ba6949ac8e6eb610cf565e457879c27e6e934146 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Mon, 2 Apr 2018 10:08:43 -0700 Subject: [PATCH 23/70] Revert "Generalize "unfold" combinator." This reverts commit 84a4cd01c5102b67d6891fa1a187fd72ef194e06. --- src/stream/unfold.rs | 38 ++++++++++++++++++-------------------- tests/unfold.rs | 11 +++++------ tests/unsync.rs | 2 +- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/stream/unfold.rs b/src/stream/unfold.rs index 356f1ff448..ac427b8c3b 100644 --- a/src/stream/unfold.rs +++ b/src/stream/unfold.rs @@ -13,7 +13,7 @@ use stream::Stream; /// for the returned `Future` to complete with `(a, b)`. It will then yield the /// value `a`, and use `b` as the next internal state. /// -/// If the future returns `None` instead of `Some`, then the `unfold()` +/// If the closure returns `None` instead of `Some(Future)`, then the `unfold()` /// will stop producing items and return `Ok(Async::Ready(None))` in future /// calls to `poll()`. /// @@ -33,23 +33,22 @@ use stream::Stream; /// use futures::future::{self, Future}; /// /// let mut stream = stream::unfold(0, |state| { -/// future::ok::<_, u32>( -/// if state <= 2 { -/// let next_state = state + 1; -/// let yielded = state * 2; -/// Some((yielded, next_state)) -/// } else { -/// None -/// } -/// ) +/// if state <= 2 { +/// let next_state = state + 1; +/// let yielded = state * 2; +/// let fut = future::ok::<_, u32>((yielded, next_state)); +/// Some(fut) +/// } else { +/// None +/// } /// }); /// /// let result = stream.collect().wait(); /// assert_eq!(result, Ok(vec![0, 2, 4])); /// ``` pub fn unfold(init: T, f: F) -> Unfold - where F: FnMut(T) -> Fut, - Fut: IntoFuture>, + where F: FnMut(T) -> Option, + Fut: IntoFuture, { Unfold { f: f, @@ -68,8 +67,8 @@ pub struct Unfold where Fut: IntoFuture { } impl Stream for Unfold - where F: FnMut(T) -> Fut, - Fut: IntoFuture>, + where F: FnMut(T) -> Option, + Fut: IntoFuture, { type Item = It; type Error = Fut::Error; @@ -80,18 +79,17 @@ impl Stream for Unfold // State::Empty may happen if the future returned an error State::Empty => { return Ok(Async::Ready(None)); } State::Ready(state) => { - let fut = (self.f)(state); - self.state = State::Processing(fut.into_future()); + match (self.f)(state) { + Some(fut) => { self.state = State::Processing(fut.into_future()); } + None => { return Ok(Async::Ready(None)); } + } } State::Processing(mut fut) => { match fut.poll()? { - Async::Ready(Some((item, next_state))) => { + Async:: Ready((item, next_state)) => { self.state = State::Ready(next_state); return Ok(Async::Ready(Some(item))); } - Async::Ready(None) => { - return Ok(Async::Ready(None)); - } Async::NotReady => { self.state = State::Processing(fut); return Ok(Async::NotReady); diff --git a/tests/unfold.rs b/tests/unfold.rs index eef2716053..1669a18aa5 100644 --- a/tests/unfold.rs +++ b/tests/unfold.rs @@ -3,7 +3,6 @@ extern crate futures; mod support; use futures::stream; -use futures::future::{ok, Either}; use support::*; @@ -11,10 +10,10 @@ use support::*; fn unfold1() { let mut stream = stream::unfold(0, |state| { if state <= 2 { - let res = ok::<_, ()>(Some((state * 2, state + 1))); - Either::A(delay_future(res)) + let res: Result<_,()> = Ok((state * 2, state + 1)); + Some(delay_future(res)) } else { - Either::B(ok(None)) + None } }); // Creates the future with the closure @@ -38,9 +37,9 @@ fn unfold1() { fn unfold_err1() { let mut stream = stream::unfold(0, |state| { if state <= 2 { - Ok(Some((state * 2, state + 1))) + Some(Ok((state * 2, state + 1))) } else { - Err(-1) + Some(Err(-1)) } }); sassert_next(&mut stream, 0); diff --git a/tests/unsync.rs b/tests/unsync.rs index 8bd5d21cd4..3d11085980 100644 --- a/tests/unsync.rs +++ b/tests/unsync.rs @@ -139,7 +139,7 @@ fn mpsc_send_unpark() { #[test] fn spawn_sends_items() { let core = Core::new(); - let stream = unfold(0, |i| Ok::<_,u8>(Some((i, i + 1)))); + let stream = unfold(0, |i| Some(Ok::<_,u8>((i, i + 1)))); let rx = mpsc::spawn(stream, &core, 1); assert_eq!(core.run(rx.take(4).collect()).unwrap(), [0, 1, 2, 3]); From 5a0c7b1aa598a727b8ab45e1bf404d0c5982e48a Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Mon, 9 Jul 2018 11:32:50 -0700 Subject: [PATCH 24/70] Update version to 0.1.22 This is in preparation for the 0.1.22 release. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 33a5152984..62d4352406 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "futures" -version = "0.1.20" +version = "0.1.22" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" From 8965117895dff48de18e70918fd375a041a93877 Mon Sep 17 00:00:00 2001 From: David Kellum Date: Mon, 9 Jul 2018 15:23:13 -0700 Subject: [PATCH 25/70] Note that CHANGELOG.md (0.1 branch) is no longer maintained github: cc #933 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d0c673176..616282329e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +**Note**: This CHANGELOG is no longer maintained for newer 0.1.x releases. +See instead the github release tags and individual git commits. + +----- + # 0.1.17 - 2017-10-31 * Add a `close` method on `sink::Wait` From 15dbc97f30bdd62f6ba0e622438feaa89ab0b0ef Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Thu, 12 Jul 2018 15:11:26 -0700 Subject: [PATCH 26/70] shared: fix unsound Send and Sync impl for Shared --- src/future/shared.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/future/shared.rs b/src/future/shared.rs index 3f7e3afd27..25417ef071 100644 --- a/src/future/shared.rs +++ b/src/future/shared.rs @@ -235,8 +235,20 @@ impl Notify for Notifier { } } -unsafe impl Sync for Inner {} -unsafe impl Send for Inner {} +// The `F` is synchronized by a lock, so `F` doesn't need +// to be `Sync`. However, its `Item` or `Error` are exposed +// through an `Arc` but not lock, so they must be `Send + Sync`. +unsafe impl Send for Inner + where F: Future + Send, + F::Item: Send + Sync, + F::Error: Send + Sync, +{} + +unsafe impl Sync for Inner + where F: Future + Send, + F::Item: Send + Sync, + F::Error: Send + Sync, +{} impl fmt::Debug for Inner where F: Future + fmt::Debug, From 1328fc9e8af5737183df477c7501e6ea24ff2053 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Thu, 12 Jul 2018 22:07:02 -0700 Subject: [PATCH 27/70] 0.1.23 Release Soundness fixes to the `Shared` combinator --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 62d4352406..81895e3c06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "futures" -version = "0.1.22" +version = "0.1.23" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" From 674eb3d003ee89453ed274ded9a2a50f311803b0 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Wed, 29 Aug 2018 04:52:46 -0700 Subject: [PATCH 28/70] Deprecate `executor::Executor` and `executor::Run` These two APIs have long been deprecated, they just have not been annotated as such. This patch also hides a number of deprecated functions to help keep the generated documentation cleaner. --- futures-cpupool/src/lib.rs | 5 ++++ src/executor.rs | 1 + src/task_impl/std/mod.rs | 58 ++++++++++---------------------------- 3 files changed, 21 insertions(+), 43 deletions(-) diff --git a/futures-cpupool/src/lib.rs b/futures-cpupool/src/lib.rs index 0614368ba3..90b6168bb7 100644 --- a/futures-cpupool/src/lib.rs +++ b/futures-cpupool/src/lib.rs @@ -50,6 +50,7 @@ use std::fmt; use futures::{IntoFuture, Future, Poll, Async}; use futures::future::{lazy, Executor, ExecuteError}; use futures::sync::oneshot::{channel, Sender, Receiver}; +#[allow(deprecated)] use futures::executor::{self, Run, Executor as OldExecutor}; /// A thread pool intended to run CPU intensive work. @@ -132,6 +133,7 @@ pub struct CpuFuture { } enum Message { + #[allow(deprecated)] Run(Run), Close, } @@ -211,6 +213,7 @@ impl CpuPool { tx: Some(tx), keep_running_flag: keep_running_flag.clone(), }; + #[allow(deprecated)] executor::spawn(sender).execute(self.inner.clone()); CpuFuture { inner: rx , keep_running_flag: keep_running_flag.clone() } } @@ -239,6 +242,7 @@ impl Executor for CpuPool where F: Future + Send + 'static, { fn execute(&self, future: F) -> Result<(), ExecuteError> { + #[allow(deprecated)] executor::spawn(future).execute(self.inner.clone()); Ok(()) } @@ -279,6 +283,7 @@ impl Drop for CpuPool { } } +#[allow(deprecated)] impl OldExecutor for Inner { fn execute(&self, run: Run) { self.send(Message::Run(run)) diff --git a/src/executor.rs b/src/executor.rs index b6b6d422a8..365642f770 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -8,6 +8,7 @@ //! [online]: https://tokio.rs/docs/going-deeper-futures/tasks/ #[allow(deprecated)] +#[doc(hidden)] #[cfg(feature = "use_std")] pub use task_impl::{Unpark, Executor, Run}; diff --git a/src/task_impl/std/mod.rs b/src/task_impl/std/mod.rs index 2472c8124e..f5469c686c 100644 --- a/src/task_impl/std/mod.rs +++ b/src/task_impl/std/mod.rs @@ -211,19 +211,7 @@ impl TaskUnpark { } impl Spawn { - /// Polls the internal future, scheduling notifications to be sent to the - /// `unpark` argument. - /// - /// This method will poll the internal future, testing if it's completed - /// yet. The `unpark` argument is used as a sink for notifications sent to - /// this future. That is, while the future is being polled, any call to - /// `task::park()` will return a handle that contains the `unpark` - /// specified. - /// - /// If this function returns `NotReady`, then the `unpark` should have been - /// scheduled to receive a notification when poll can be called again. - /// Otherwise if `Ready` or `Err` is returned, the `Spawn` task can be - /// safely destroyed. + #[doc(hidden)] #[deprecated(note = "recommended to use `poll_future_notify` instead")] #[allow(deprecated)] pub fn poll_future(&mut self, unpark: Arc) -> Poll { @@ -248,25 +236,10 @@ impl Spawn { }) } - /// A specialized function to request running a future to completion on the - /// specified executor. - /// - /// This function only works for futures whose item and error types are `()` - /// and also implement the `Send` and `'static` bounds. This will submit - /// units of work (instances of `Run`) to the `exec` argument provided - /// necessary to drive the future to completion. - /// - /// When the future would block, it's arranged that when the future is again - /// ready it will submit another unit of work to the `exec` provided. This - /// will happen in a loop until the future has completed. - /// - /// This method is not appropriate for all futures, and other kinds of - /// executors typically provide a similar function with perhaps relaxed - /// bounds as well. - /// - /// Note that this method is likely to be deprecated in favor of the - /// `futures::Executor` trait and `execute` method, but if this'd cause - /// difficulty for you please let us know! + + #[doc(hidden)] + #[deprecated] + #[allow(deprecated)] pub fn execute(self, exec: Arc) where F: Future + Send + 'static, { @@ -285,9 +258,9 @@ impl Spawn { } impl Spawn { - /// Like `poll_future`, except polls the underlying stream. #[deprecated(note = "recommended to use `poll_stream_notify` instead")] #[allow(deprecated)] + #[doc(hidden)] pub fn poll_stream(&mut self, unpark: Arc) -> Poll, S::Error> { self.enter(BorrowedUnpark::Old(&unpark), |s| s.poll()) @@ -311,11 +284,7 @@ impl Spawn { } impl Spawn { - /// Invokes the underlying `start_send` method with this task in place. - /// - /// If the underlying operation returns `NotReady` then the `unpark` value - /// passed in will receive a notification when the operation is ready to be - /// attempted again. + #[doc(hidden)] #[deprecated(note = "recommended to use `start_send_notify` instead")] #[allow(deprecated)] pub fn start_send(&mut self, value: S::SinkItem, unpark: &Arc) @@ -323,13 +292,9 @@ impl Spawn { self.enter(BorrowedUnpark::Old(unpark), |s| s.start_send(value)) } - /// Invokes the underlying `poll_complete` method with this task in place. - /// - /// If the underlying operation returns `NotReady` then the `unpark` value - /// passed in will receive a notification when the operation is ready to be - /// attempted again. #[deprecated(note = "recommended to use `poll_flush_notify` instead")] #[allow(deprecated)] + #[doc(hidden)] pub fn poll_flush(&mut self, unpark: &Arc) -> Poll<(), S::SinkError> { self.enter(BorrowedUnpark::Old(unpark), |s| s.poll_complete()) @@ -418,6 +383,8 @@ pub trait Unpark: Send + Sync { /// Note that this trait is likely to be deprecated and/or renamed to avoid /// clashing with the `future::Executor` trait. If you've got a use case for /// this or would like to comment on the name please let us know! +#[deprecated] +#[allow(deprecated)] pub trait Executor: Send + Sync + 'static { /// Requests that `Run` is executed soon on the given executor. fn execute(&self, r: Run); @@ -425,16 +392,19 @@ pub trait Executor: Send + Sync + 'static { /// Units of work submitted to an `Executor`, currently only created /// internally. +#[deprecated] pub struct Run { spawn: Spawn + Send>>, inner: Arc, } +#[allow(deprecated)] struct RunInner { mutex: UnparkMutex, exec: Arc, } +#[allow(deprecated)] impl Run { /// Actually run the task (invoking `poll` on its future) on the current /// thread. @@ -462,6 +432,7 @@ impl Run { } } +#[allow(deprecated)] impl fmt::Debug for Run { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Run") @@ -470,6 +441,7 @@ impl fmt::Debug for Run { } } +#[allow(deprecated)] impl Notify for RunInner { fn notify(&self, _id: usize) { match self.mutex.notify() { From 0e3e8f6ebfa8f7e76aa75b12ace37b1f61539a84 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Thu, 4 Jan 2018 15:54:53 -0800 Subject: [PATCH 29/70] Explain why `Task` does not impl `Eq + Hash`. --- src/task_impl/mod.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/task_impl/mod.rs b/src/task_impl/mod.rs index 0a3e917df6..b1d885be3d 100644 --- a/src/task_impl/mod.rs +++ b/src/task_impl/mod.rs @@ -54,6 +54,31 @@ fn with R, R>(f: F) -> R { /// the future as notifications arrive, until the future terminates. /// /// This is obtained by the `task::current` function. +/// +/// # FAQ +/// +/// ### Why does `Task` not implement `Eq` and `Hash`? +/// +/// A valid use case for `Task` to implement these two traits has not been +/// encountered. +/// +/// Usually, this question is asked by someone who wants to store a `Task` +/// instance in a `HashSet`. This seems like an obvious way to implement a +/// future aware, multi-handle structure; e.g. a multi-producer channel. +/// +/// In this case, the idea is that whenever a `start_send` is called on one of +/// the channel's send handles, if the channel is at capacity, the current task +/// is stored in a `TaskSet`. Then, when capacity is available, a task is +/// removed from the task set and notified. +/// +/// The problem with this strategy is that multiple `Sender` handles can be used +/// on the same task. In this case, when the second handle is used and the task +/// is stored in `TaskSet`, there already is an entry. Then, when the first +/// handle is dropped, this entry is cleared, resulting in a dead lock. +/// +/// See [here](https://github.com/alexcrichton/futures-rs/issues/670) for more +/// discussion. +/// #[derive(Clone)] pub struct Task { id: usize, From 6e2eed0e227cdb7bf7dd83e3296c16d2003ed847 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Wed, 29 Aug 2018 04:57:15 -0700 Subject: [PATCH 30/70] Update link --- src/task_impl/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/task_impl/mod.rs b/src/task_impl/mod.rs index b1d885be3d..7a49015576 100644 --- a/src/task_impl/mod.rs +++ b/src/task_impl/mod.rs @@ -76,8 +76,8 @@ fn with R, R>(f: F) -> R { /// is stored in `TaskSet`, there already is an entry. Then, when the first /// handle is dropped, this entry is cleared, resulting in a dead lock. /// -/// See [here](https://github.com/alexcrichton/futures-rs/issues/670) for more -/// discussion. +/// See [here](https://github.com/rust-lang-nursery/futures-rs/issues/670) for +/// more discussion. /// #[derive(Clone)] pub struct Task { From 21a753766d6c0ce62416ca883bc559e839671c4a Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Fri, 31 Aug 2018 09:31:31 -0700 Subject: [PATCH 31/70] Remove fake type --- src/task_impl/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/task_impl/mod.rs b/src/task_impl/mod.rs index 7a49015576..e6cfb23c27 100644 --- a/src/task_impl/mod.rs +++ b/src/task_impl/mod.rs @@ -68,12 +68,12 @@ fn with R, R>(f: F) -> R { /// /// In this case, the idea is that whenever a `start_send` is called on one of /// the channel's send handles, if the channel is at capacity, the current task -/// is stored in a `TaskSet`. Then, when capacity is available, a task is -/// removed from the task set and notified. +/// is stored in a set. Then, when capacity is available, a task is removed from +/// the set and notified. /// /// The problem with this strategy is that multiple `Sender` handles can be used /// on the same task. In this case, when the second handle is used and the task -/// is stored in `TaskSet`, there already is an entry. Then, when the first +/// is stored in a set, there already is an entry. Then, when the first /// handle is dropped, this entry is cleared, resulting in a dead lock. /// /// See [here](https://github.com/rust-lang-nursery/futures-rs/issues/670) for From 446c02686eb476cc65a504ab32f5564d296972da Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Fri, 31 Aug 2018 14:59:52 -0700 Subject: [PATCH 32/70] impl Unpin for NotifyHandle with feature flag This adds an implementation of `Unpin` for `NotifyHandle`. `Unpin` is only available on the nightly channel, so the implementation is guarded by a feature flag. The implementation will always be needed as the struct contains a raw pointer to a trait object. --- .travis.yml | 1 + Cargo.toml | 1 + src/lib.rs | 1 + src/task_impl/mod.rs | 8 ++++++++ 4 files changed, 11 insertions(+) diff --git a/.travis.yml b/.travis.yml index 448cb6cfb4..4f007e9881 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,7 @@ script: - cargo doc --no-deps - cargo doc --no-deps --manifest-path futures-cpupool/Cargo.toml - if [ "$BENCH" = "1" ]; then cargo bench; fi + - if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then cargo test --features nightly; fi env: global: - secure: "iwVcMVIF7ZSY82fK5UyyUvVvJxMSYrbZawh1+4Oi8pvOdYq1gptcDoOC8jxWwCwrNF1b+/85n+jlEUngEqqSmV5PjAbWPjoc+u4Zn7CRi1AlxoUlvHPiQm4vM4Mkkd6GsqoIZttCeedU9m/w0nQ18uUtK8uD6vr2FVdcMnUnkYQAxuGOowGLrwidukzfBXMCu/JrwKMIbt61knAFiI/KJknu0h1mRrhpeF/sQ3tJFzRRcQeFJkbfwDzltMpPo1hq5D3HI4ONjYi/qO2pwUhDk4umfp9cLW9MS8rQvptxJTQmWemHi+f2/U4ld6a0URL6kEuMkt/EbH0A74eFtlicfRs44dX9MlWoqbLypnC3ymqmHcpwcwNA3HmZyg800MTuU+BPK41HIPdO9tPpxjHEiqvNDknH7qs+YBnis0eH7DHJgEjXq651PjW7pm+rnHPwsj+OzKE1YBNxBQZZDkS3VnZJz+O4tVsOzc3IOz0e+lf7VVuI17C9haj117nKp3umC4MVBA0S8RfreFgqpyDeY2zwcqOr0YOlEGGRl0vyWP8Qcxx12kQ7+doLolt6Kxda4uO0hKRmIF6+qki1T+L7v8BOGOtCncz4f7IX48eQ7+Wu0OtglRn45qAa3CxjUuW6xX3KSNH66PCXV0Jtp8Ga2SSevX2wtbbFu9f+9R+PQY4=" diff --git a/Cargo.toml b/Cargo.toml index 81895e3c06..865c779ff7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ appveyor = { repository = "rust-lang-nursery/futures-rs" } [dependencies] [features] +nightly = [] use_std = [] with-deprecated = [] default = ["use_std", "with-deprecated"] diff --git a/src/lib.rs b/src/lib.rs index 59e4874b2c..9a72090e83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,6 +158,7 @@ #![no_std] #![deny(missing_docs, missing_debug_implementations)] #![doc(html_root_url = "https://docs.rs/futures/0.1")] +#![cfg_attr(feature = "nightly", feature(pin))] #[macro_use] #[cfg(feature = "use_std")] diff --git a/src/task_impl/mod.rs b/src/task_impl/mod.rs index e6cfb23c27..63c5b0ccdf 100644 --- a/src/task_impl/mod.rs +++ b/src/task_impl/mod.rs @@ -712,3 +712,11 @@ impl From<&'static T> for NotifyHandle { unsafe { NotifyHandle::new(src as *const _ as *mut StaticRef) } } } + +#[cfg(feature = "nightly")] +mod nightly { + use super::NotifyHandle; + use core::marker::Unpin; + + impl Unpin for NotifyHandle {} +} From 8a159121fe8d00ec0921bbd531670926be4a6080 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Fri, 31 Aug 2018 15:11:42 -0700 Subject: [PATCH 33/70] fix mpsc::Sender::poll_complete impl This fixes the "bounded senders are actually unbounded" problem. The problem is discussed here: rust-lang-nursery/futures-rs#984 --- src/sync/mpsc/mod.rs | 9 ++++++++- tests/mpsc.rs | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/sync/mpsc/mod.rs b/src/sync/mpsc/mod.rs index 7187579666..c0f9a8857d 100644 --- a/src/sync/mpsc/mod.rs +++ b/src/sync/mpsc/mod.rs @@ -649,7 +649,14 @@ impl Sink for Sender { } fn poll_complete(&mut self) -> Poll<(), SendError> { - Ok(Async::Ready(())) + self.poll_ready() + // At this point, the value cannot be returned and `SendError` + // cannot be created with a `T` without breaking backwards + // comptibility. This means we cannot return an error. + // + // That said, there is also no guarantee that a `poll_complete` + // returning `Ok` implies the receiver sees the message. + .or_else(|_| Ok(().into())) } fn close(&mut self) -> Poll<(), SendError> { diff --git a/tests/mpsc.rs b/tests/mpsc.rs index 8df98d490f..faeb614608 100644 --- a/tests/mpsc.rs +++ b/tests/mpsc.rs @@ -548,3 +548,19 @@ fn try_send_fail() { assert_eq!(rx.next(), Some(Ok("goodbye"))); assert!(rx.next().is_none()); } + +#[test] +fn bounded_is_really_bounded() { + use futures::Async::*; + let (mut tx, mut rx) = mpsc::channel(0); + lazy(|| { + assert!(tx.start_send(1).unwrap().is_ready()); + // Not ready until we receive + assert!(!tx.poll_complete().unwrap().is_ready()); + // Receive the value + assert_eq!(rx.poll().unwrap(), Ready(Some(1))); + // Now the sender is ready + assert!(tx.poll_complete().unwrap().is_ready()); + Ok::<_, ()>(()) + }).wait().unwrap(); +} From 99416a63b521c5e0a0f7b09ed65b9c7a62674196 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Fri, 31 Aug 2018 15:39:46 -0700 Subject: [PATCH 34/70] fix race with dropping mpsc::Receiver Fix a bug where messages sent into a channel at the same time as the `Receiver` is dropped are never removed. Fixes #909 --- src/sync/mpsc/mod.rs | 53 +++++++++++------ tests/mpsc-close.rs | 131 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 16 deletions(-) diff --git a/src/sync/mpsc/mod.rs b/src/sync/mpsc/mod.rs index 7187579666..d9f36924f2 100644 --- a/src/sync/mpsc/mod.rs +++ b/src/sync/mpsc/mod.rs @@ -813,6 +813,12 @@ impl Receiver { loop { match unsafe { self.inner.message_queue.pop() } { PopResult::Data(msg) => { + // If there are any parked task handles in the parked queue, + // pop one and unpark it. + self.unpark_one(); + // Decrement number of messages + self.dec_num_messages(); + return Async::Ready(msg); } PopResult::Empty => { @@ -863,7 +869,7 @@ impl Receiver { let state = decode_state(curr); // If the channel is closed, then there is no need to park. - if !state.is_open && state.num_messages == 0 { + if state.is_closed() { return TryPark::Closed; } @@ -904,8 +910,8 @@ impl Stream for Receiver { fn poll(&mut self) -> Poll, ()> { loop { // Try to read a message off of the message queue. - let msg = match self.next_message() { - Async::Ready(msg) => msg, + match self.next_message() { + Async::Ready(msg) => return Ok(Async::Ready(msg)), Async::NotReady => { // There are no messages to read, in this case, attempt to // park. The act of parking will verify that the channel is @@ -929,17 +935,7 @@ impl Stream for Receiver { } } } - }; - - // If there are any parked task handles in the parked queue, pop - // one and unpark it. - self.unpark_one(); - - // Decrement number of messages - self.dec_num_messages(); - - // Return the message - return Ok(Async::Ready(msg)); + } } } } @@ -948,8 +944,27 @@ impl Drop for Receiver { fn drop(&mut self) { // Drain the channel of all pending messages self.close(); - while self.next_message().is_ready() { - // ... + + loop { + match self.next_message() { + Async::Ready(_) => {} + Async::NotReady => { + let curr = self.inner.state.load(SeqCst); + let state = decode_state(curr); + + // If the channel is closed, then there is no need to park. + if state.is_closed() { + return; + } + + // TODO: Spinning isn't ideal, it might be worth + // investigating using a condvar or some other strategy + // here. That said, if this case is hit, then another thread + // is about to push the value into the queue and this isn't + // the only spinlock in the impl right now. + thread::yield_now(); + } + } } } } @@ -1125,6 +1140,12 @@ impl Inner { unsafe impl Send for Inner {} unsafe impl Sync for Inner {} +impl State { + fn is_closed(&self) -> bool { + !self.is_open && self.num_messages == 0 + } +} + /* * * ===== Helpers ===== diff --git a/tests/mpsc-close.rs b/tests/mpsc-close.rs index 253e015705..9097d70249 100644 --- a/tests/mpsc-close.rs +++ b/tests/mpsc-close.rs @@ -1,9 +1,12 @@ extern crate futures; +use std::sync::{Arc, Weak}; use std::thread; +use std::time::{Duration, Instant}; use futures::prelude::*; use futures::sync::mpsc::*; +use futures::task; #[test] fn smoke() { @@ -19,3 +22,131 @@ fn smoke() { t.join().unwrap() } + +// Stress test that `try_send()`s occurring concurrently with receiver +// close/drops don't appear as successful sends. +#[test] +fn stress_try_send_as_receiver_closes() { + const AMT: usize = 10000; + // To provide variable timing characteristics (in the hopes of + // reproducing the collision that leads to a race), we busy-re-poll + // the test MPSC receiver a variable number of times before actually + // stopping. We vary this countdown between 1 and the following + // value. + const MAX_COUNTDOWN: usize = 20; + // When we detect that a successfully sent item is still in the + // queue after a disconnect, we spin for up to 100ms to confirm that + // it is a persistent condition and not a concurrency illusion. + const SPIN_TIMEOUT: Duration = Duration::from_secs(10); + const SPIN_SLEEP: Duration = Duration::from_millis(10); + struct TestRx { + rx: Receiver>, + // The number of times to query `rx` before dropping it. + poll_count: usize + } + struct TestTask { + command_rx: Receiver, + test_rx: Option>>, + countdown: usize, + } + impl TestTask { + /// Create a new TestTask + fn new() -> (TestTask, Sender) { + let (command_tx, command_rx) = channel::(0); + ( + TestTask { + command_rx, + test_rx: None, + countdown: 0, // 0 means no countdown is in progress. + }, + command_tx, + ) + } + } + impl Future for TestTask { + type Item = (); + type Error = (); + fn poll(&mut self) -> Poll<(), ()> { + // Poll the test channel, if one is present. + if let Some(ref mut rx) = self.test_rx { + if let Ok(Async::Ready(v)) = rx.poll() { + let _ = v.expect("test finished unexpectedly!"); + } + self.countdown -= 1; + // Busy-poll until the countdown is finished. + task::current().notify(); + } + // Accept any newly submitted MPSC channels for testing. + match self.command_rx.poll()? { + Async::Ready(Some(TestRx { rx, poll_count })) => { + self.test_rx = Some(rx); + self.countdown = poll_count; + task::current().notify(); + }, + Async::Ready(None) => return Ok(Async::Ready(())), + _ => {}, + } + if self.countdown == 0 { + // Countdown complete -- drop the Receiver. + self.test_rx = None; + } + Ok(Async::NotReady) + } + } + let (f, mut cmd_tx) = TestTask::new(); + let bg = thread::spawn(move || f.wait()); + for i in 0..AMT { + let (mut test_tx, rx) = channel(0); + let poll_count = i % MAX_COUNTDOWN; + cmd_tx.try_send(TestRx { rx, poll_count }).unwrap(); + let mut prev_weak: Option> = None; + let mut attempted_sends = 0; + let mut successful_sends = 0; + loop { + // Create a test item. + let item = Arc::new(()); + let weak = Arc::downgrade(&item); + match test_tx.try_send(item) { + Ok(_) => { + prev_weak = Some(weak); + successful_sends += 1; + } + Err(ref e) if e.is_full() => {} + Err(ref e) if e.is_disconnected() => { + // Test for evidence of the race condition. + if let Some(prev_weak) = prev_weak { + if prev_weak.upgrade().is_some() { + // The previously sent item is still allocated. + // However, there appears to be some aspect of the + // concurrency that can legitimately cause the Arc + // to be momentarily valid. Spin for up to 100ms + // waiting for the previously sent item to be + // dropped. + let t0 = Instant::now(); + let mut spins = 0; + loop { + if prev_weak.upgrade().is_none() { + break; + } + assert!(t0.elapsed() < SPIN_TIMEOUT, + "item not dropped on iteration {} after \ + {} sends ({} successful). spin=({})", + i, attempted_sends, successful_sends, spins + ); + spins += 1; + thread::sleep(SPIN_SLEEP); + } + } + } + break; + } + Err(ref e) => panic!("unexpected error: {}", e), + } + attempted_sends += 1; + } + } + drop(cmd_tx); + bg.join() + .expect("background thread join") + .expect("background thread result"); +} From 0a0b2d56a47c49d6c0bbf79520f60621a1063eb8 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Fri, 31 Aug 2018 17:05:05 -0700 Subject: [PATCH 35/70] Get tests passing on 1.15 --- tests/mpsc-close.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/mpsc-close.rs b/tests/mpsc-close.rs index 9097d70249..d88592cade 100644 --- a/tests/mpsc-close.rs +++ b/tests/mpsc-close.rs @@ -55,7 +55,7 @@ fn stress_try_send_as_receiver_closes() { let (command_tx, command_rx) = channel::(0); ( TestTask { - command_rx, + command_rx: command_rx, test_rx: None, countdown: 0, // 0 means no countdown is in progress. }, @@ -98,7 +98,7 @@ fn stress_try_send_as_receiver_closes() { for i in 0..AMT { let (mut test_tx, rx) = channel(0); let poll_count = i % MAX_COUNTDOWN; - cmd_tx.try_send(TestRx { rx, poll_count }).unwrap(); + cmd_tx.try_send(TestRx { rx: rx, poll_count: poll_count }).unwrap(); let mut prev_weak: Option> = None; let mut attempted_sends = 0; let mut successful_sends = 0; From f659cb6309f72f89bd57369bf2a772280cd2c5d7 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Fri, 31 Aug 2018 17:20:06 -0700 Subject: [PATCH 36/70] Fix test that relied on broken behavior --- tests/sink.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/sink.rs b/tests/sink.rs index cb2fdcf26d..c8a34d9e03 100644 --- a/tests/sink.rs +++ b/tests/sink.rs @@ -392,6 +392,12 @@ fn fanout_backpressure() { let (item, right_recv) = right_recv.into_future().wait().unwrap(); assert_eq!(item, Some(1)); assert!(flag.get()); + let (item, left_recv) = left_recv.into_future().wait().unwrap(); + assert_eq!(item, Some(2)); + assert!(flag.get()); + assert!(task.poll_future_notify(&flag, 0).unwrap().is_not_ready()); + let (item, right_recv) = right_recv.into_future().wait().unwrap(); + assert_eq!(item, Some(2)); match task.poll_future_notify(&flag, 0).unwrap() { Async::Ready(_) => { }, From 7b38d8f1332b2816e99300da1ade373f738d124b Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Fri, 31 Aug 2018 17:22:03 -0700 Subject: [PATCH 37/70] Try to fix for 1.15 again --- tests/mpsc-close.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/mpsc-close.rs b/tests/mpsc-close.rs index d88592cade..061616ae06 100644 --- a/tests/mpsc-close.rs +++ b/tests/mpsc-close.rs @@ -37,8 +37,8 @@ fn stress_try_send_as_receiver_closes() { // When we detect that a successfully sent item is still in the // queue after a disconnect, we spin for up to 100ms to confirm that // it is a persistent condition and not a concurrency illusion. - const SPIN_TIMEOUT: Duration = Duration::from_secs(10); - const SPIN_SLEEP: Duration = Duration::from_millis(10); + const SPIN_TIMEOUT_S: u64 = 10; + const SPIN_SLEEP_MS: u64 = 10; struct TestRx { rx: Receiver>, // The number of times to query `rx` before dropping it. @@ -128,13 +128,13 @@ fn stress_try_send_as_receiver_closes() { if prev_weak.upgrade().is_none() { break; } - assert!(t0.elapsed() < SPIN_TIMEOUT, + assert!(t0.elapsed() < Duration::from_secs(SPIN_TIMEOUT_S), "item not dropped on iteration {} after \ {} sends ({} successful). spin=({})", i, attempted_sends, successful_sends, spins ); spins += 1; - thread::sleep(SPIN_SLEEP); + thread::sleep(Duration::from_millis(SPIN_SLEEP_MS)); } } } From 0f8a0b1787a88127134bfd1c87a9de339d4e4289 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Fri, 31 Aug 2018 17:24:34 -0700 Subject: [PATCH 38/70] impl Unpin for TaskUnpark with feature flag `TaskUnpark` is deprecated, but is still used in some cases. --- src/task_impl/std/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/task_impl/std/mod.rs b/src/task_impl/std/mod.rs index f5469c686c..2da6ee1532 100644 --- a/src/task_impl/std/mod.rs +++ b/src/task_impl/std/mod.rs @@ -700,3 +700,11 @@ impl From> for NotifyHandle } } } + +#[cfg(feature = "nightly")] +mod nightly { + use super::TaskUnpark; + use core::marker::Unpin; + + impl Unpin for TaskUnpark {} +} From b6e37f1455ab5339f5e91592aa82bf04e6a21d9a Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Fri, 31 Aug 2018 18:25:31 -0700 Subject: [PATCH 39/70] impl Unpin for UnparkEvents with feature flag `UnparkEvents` is deprecated, but is still used in some cases. --- src/task_impl/std/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/task_impl/std/mod.rs b/src/task_impl/std/mod.rs index 2da6ee1532..db51969e7d 100644 --- a/src/task_impl/std/mod.rs +++ b/src/task_impl/std/mod.rs @@ -703,8 +703,9 @@ impl From> for NotifyHandle #[cfg(feature = "nightly")] mod nightly { - use super::TaskUnpark; + use super::{TaskUnpark, UnparkEvents}; use core::marker::Unpin; impl Unpin for TaskUnpark {} + impl Unpin for UnparkEvents {} } From 4707a1aa1bc840caa83843f6b316959a151dd218 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Sat, 1 Sep 2018 10:51:31 -0700 Subject: [PATCH 40/70] Fix bench The bench is written assuming internal implementation details of the channel which have changed after this fix. I removed the assertion which isn't neecessary. --- benches/sync_mpsc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/sync_mpsc.rs b/benches/sync_mpsc.rs index 34e4dd38c2..c0365c5fed 100644 --- a/benches/sync_mpsc.rs +++ b/benches/sync_mpsc.rs @@ -1,5 +1,6 @@ #![feature(test)] +#[macro_use] extern crate futures; extern crate test; @@ -106,7 +107,6 @@ impl Stream for TestSender { Err(_) => panic!(), Ok(AsyncSink::Ready) => { self.last += 1; - assert_eq!(Ok(Async::Ready(())), self.tx.poll_complete()); Ok(Async::Ready(Some(self.last))) } Ok(AsyncSink::NotReady(_)) => { From b6435ff4bcb7b40adf5af2d98f3ee4ad5a98a3b9 Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Sun, 2 Sep 2018 21:30:39 +0200 Subject: [PATCH 41/70] version 0.1.24 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 865c779ff7..2abefae7a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "futures" -version = "0.1.23" +version = "0.1.24" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" From dd6ebbd2cafdaeccd5a25d7874c05277e867bafc Mon Sep 17 00:00:00 2001 From: Harm Berntsen Date: Wed, 5 Sep 2018 14:46:24 +0200 Subject: [PATCH 42/70] Add Sender::is_closed method --- src/sync/mpsc/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/sync/mpsc/mod.rs b/src/sync/mpsc/mod.rs index 47e87ccd2b..31d2320ab6 100644 --- a/src/sync/mpsc/mod.rs +++ b/src/sync/mpsc/mod.rs @@ -600,6 +600,11 @@ impl Sender { Ok(self.poll_unparked(true)) } + /// Returns whether this channel is closed without needing a context. + pub fn is_closed(&self) -> bool { + !decode_state(self.inner.state.load(SeqCst)).is_open + } + fn poll_unparked(&mut self, do_park: bool) -> Async<()> { // First check the `maybe_parked` variable. This avoids acquiring the // lock in most cases @@ -665,6 +670,11 @@ impl Sink for Sender { } impl UnboundedSender { + /// Returns whether this channel is closed without needing a context. + pub fn is_closed(&self) -> bool { + self.0.is_closed() + } + /// Sends the provided message along this channel. /// /// This is an unbounded sender, so this function differs from `Sink::send` From 957f47b6a3848a8b318ca999d393168adc8e5629 Mon Sep 17 00:00:00 2001 From: nsan1129 Date: Fri, 13 Apr 2018 17:09:07 -0700 Subject: [PATCH 43/70] Add `oneshot::Receiver::try_recv`. Attempts to receive a message outside of context of a task, such as within a `Drop` impl. It does not schedule a task wakeup or have any other side effects. backporting this function to version 1 from master --- src/sync/oneshot.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/sync/oneshot.rs b/src/sync/oneshot.rs index 6db7cb5837..3a9d8efdca 100644 --- a/src/sync/oneshot.rs +++ b/src/sync/oneshot.rs @@ -228,6 +228,23 @@ impl Inner { } } + fn try_recv(&self) -> Result, Canceled> { + // If we're complete, either `::close_rx` or `::drop_tx` was called. + // We can assume a successful send if data is present. + if self.complete.load(SeqCst) { + if let Some(mut slot) = self.data.try_lock() { + if let Some(data) = slot.take() { + return Ok(Some(data.into())); + } + } + // Should there be a different error value or a panic in the case + // where `self.data.try_lock() == None`? + Err(Canceled) + } else { + Ok(None) + } + } + fn recv(&self) -> Poll { let mut done = false; @@ -346,6 +363,9 @@ impl Sender { /// within the context of a task. In other words, this should only ever be /// called from inside another future. /// + /// If `Ok(Ready)` is returned then the associated `Receiver` has been + /// dropped, which means any work required for sending should be canceled. + /// /// If you're calling this function from a context that does not have a /// task, then you can use the `is_canceled` API instead. pub fn poll_cancel(&mut self) -> Poll<(), ()> { @@ -403,6 +423,21 @@ impl Receiver { pub fn close(&mut self) { self.inner.close_rx() } + + /// Attempts to receive a message outside of the context of a task. + /// + /// Useful when a [`Context`](Context) is not available such as within a + /// `Drop` impl. + /// + /// Does not schedule a task wakeup or have any other side effects. + /// + /// A return value of `None` must be considered immediately stale (out of + /// date) unless [`::close`](Receiver::close) has been called first. + /// + /// Returns an error if the sender was dropped. + pub fn try_recv(&mut self) -> Result, Canceled> { + self.inner.try_recv() + } } impl Future for Receiver { From 70420e7da18a78ff97d22fa705d6de0f801bb24b Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Mon, 8 Oct 2018 11:41:30 -0700 Subject: [PATCH 44/70] Add generic poll_fn_notify to Spawn --- src/task_impl/mod.rs | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/task_impl/mod.rs b/src/task_impl/mod.rs index 63c5b0ccdf..bed49a35ed 100644 --- a/src/task_impl/mod.rs +++ b/src/task_impl/mod.rs @@ -275,6 +275,19 @@ impl Spawn { self.obj } + /// Calls the provided closure, scheduling notifications to be sent to the + /// `notify` argument. + pub fn poll_fn_notify(&mut self, + notify: &N, + id: usize, + f: F) -> R + where F: FnOnce(&mut T) -> R, + N: Clone + Into, + { + let mk = || notify.clone().into(); + self.enter(BorrowedUnpark::new(&mk, id), f) + } + /// Polls the internal future, scheduling notifications to be sent to the /// `notify` argument. /// @@ -310,8 +323,7 @@ impl Spawn { where N: Clone + Into, T: Future, { - let mk = || notify.clone().into(); - self.enter(BorrowedUnpark::new(&mk, id), |f| f.poll()) + self.poll_fn_notify(notify, id, |f| f.poll()) } /// Like `poll_future_notify`, except polls the underlying stream. @@ -322,8 +334,7 @@ impl Spawn { where N: Clone + Into, T: Stream, { - let mk = || notify.clone().into(); - self.enter(BorrowedUnpark::new(&mk, id), |s| s.poll()) + self.poll_fn_notify(notify, id, |s| s.poll()) } /// Invokes the underlying `start_send` method with this task in place. @@ -339,8 +350,7 @@ impl Spawn { where N: Clone + Into, T: Sink, { - let mk = || notify.clone().into(); - self.enter(BorrowedUnpark::new(&mk, id), |s| s.start_send(value)) + self.poll_fn_notify(notify, id, |s| s.start_send(value)) } /// Invokes the underlying `poll_complete` method with this task in place. @@ -355,8 +365,7 @@ impl Spawn { where N: Clone + Into, T: Sink, { - let mk = || notify.clone().into(); - self.enter(BorrowedUnpark::new(&mk, id), |s| s.poll_complete()) + self.poll_fn_notify(notify, id, |s| s.poll_complete()) } /// Invokes the underlying `close` method with this task in place. @@ -371,8 +380,7 @@ impl Spawn { where N: Clone + Into, T: Sink, { - let mk = || notify.clone().into(); - self.enter(BorrowedUnpark::new(&mk, id), |s| s.close()) + self.poll_fn_notify(notify, id, |s| s.close()) } fn enter(&mut self, unpark: BorrowedUnpark, f: F) -> R From 9065c90f5f02914e60d81c42b4dc1a6ccf75378e Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Mon, 15 Oct 2018 18:42:57 -0700 Subject: [PATCH 45/70] AtomicWaker cleanups: cas + swap instead of loop cas --- src/task_impl/atomic_task.rs | 49 ++++++++++++++---------------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/src/task_impl/atomic_task.rs b/src/task_impl/atomic_task.rs index 46881b9eca..d73954e617 100644 --- a/src/task_impl/atomic_task.rs +++ b/src/task_impl/atomic_task.rs @@ -183,40 +183,29 @@ impl AtomicTask { // // Start by assuming that the state is `REGISTERING` as this // is what we jut set it to. - let mut curr = REGISTERING; + let res = self.state.compare_exchange( + REGISTERING, WAITING, AcqRel, Acquire); - // If a task has to be notified, it will be set here. - let mut notify: Option = None; + match res { + Ok(_) => {} + Err(actual) => { + // This branch can only be reached if a + // concurrent thread called `notify`. In this + // case, `actual` **must** be `REGISTERING | + // `NOTIFYING`. + debug_assert_eq!(actual, REGISTERING | NOTIFYING); - loop { - let res = self.state.compare_exchange( - curr, WAITING, AcqRel, Acquire); + // Take the task to notify once the atomic operation has + // completed. + let notify = (*self.task.get()).take().unwrap(); - match res { - Ok(_) => { - // The atomic exchange was successful, now - // notify the task (if set) and return. - if let Some(task) = notify { - task.notify(); - } + // Just swap, because no one could change state + // while state == `Registering | `Waking` + self.state.swap(WAITING, AcqRel); - return; - } - Err(actual) => { - // This branch can only be reached if a - // concurrent thread called `notify`. In this - // case, `actual` **must** be `REGISTERING | - // `NOTIFYING`. - debug_assert_eq!(actual, REGISTERING | NOTIFYING); - - // Take the task to notify once the atomic operation has - // completed. - notify = (*self.task.get()).take(); - - // Update `curr` for the next iteration of the - // loop - curr = actual; - } + // The atomic swap was complete, now + // notify the task and return. + notify.notify(); } } } From 7ba512878b966fd191e440d08b56a4ccdee03bc8 Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Tue, 20 Nov 2018 16:00:50 -0500 Subject: [PATCH 46/70] impl Default for Futures*ordered --- src/stream/futures_ordered.rs | 6 ++++++ src/stream/futures_unordered.rs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/stream/futures_ordered.rs b/src/stream/futures_ordered.rs index 64e2d6f9e5..561bbb5189 100644 --- a/src/stream/futures_ordered.rs +++ b/src/stream/futures_ordered.rs @@ -114,6 +114,12 @@ pub fn futures_ordered(futures: I) -> FuturesOrdered<: return queue } +impl Default for FuturesOrdered where T: Future { + fn default() -> Self { + FuturesOrdered::new() + } +} + impl FuturesOrdered where T: Future { diff --git a/src/stream/futures_unordered.rs b/src/stream/futures_unordered.rs index 2940fd3495..a183d63406 100644 --- a/src/stream/futures_unordered.rs +++ b/src/stream/futures_unordered.rs @@ -113,6 +113,12 @@ enum Dequeue { Inconsistent, } +impl Default for FuturesUnordered where T: Future { + fn default() -> Self { + FuturesUnordered::new() + } +} + impl FuturesUnordered where T: Future, { From 264ace832fb9d580c3966738ce29ba604683cd6d Mon Sep 17 00:00:00 2001 From: Konrad Borowski Date: Tue, 22 Jan 2019 11:29:51 +0100 Subject: [PATCH 47/70] Remove usage of feature(pin) Pin is now stable on nightly. --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9a72090e83..59e4874b2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,7 +158,6 @@ #![no_std] #![deny(missing_docs, missing_debug_implementations)] #![doc(html_root_url = "https://docs.rs/futures/0.1")] -#![cfg_attr(feature = "nightly", feature(pin))] #[macro_use] #[cfg(feature = "use_std")] From f2c150743df8c8420bdf13b1df1204ebf5b47121 Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Fri, 8 Feb 2019 22:39:06 +0100 Subject: [PATCH 48/70] Remove unnecessary take() The `take()` calls were on `Option`, not `&mut Option`, so they were unnecessary. This commit removes them. --- src/stream/forward.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/stream/forward.rs b/src/stream/forward.rs index 2ecde10039..1d8b731828 100644 --- a/src/stream/forward.rs +++ b/src/stream/forward.rs @@ -65,7 +65,7 @@ impl Forward fn try_start_send(&mut self, item: T::Item) -> Poll<(), U::SinkError> { debug_assert!(self.buffered.is_none()); if let AsyncSink::NotReady(item) = self.sink_mut() - .take().expect("Attempted to poll Forward after completion") + .expect("Attempted to poll Forward after completion") .start_send(item)? { self.buffered = Some(item); @@ -92,16 +92,16 @@ impl Future for Forward loop { match self.stream_mut() - .take().expect("Attempted to poll Forward after completion") + .expect("Attempted to poll Forward after completion") .poll()? { Async::Ready(Some(item)) => try_ready!(self.try_start_send(item)), Async::Ready(None) => { - try_ready!(self.sink_mut().take().expect("Attempted to poll Forward after completion").close()); + try_ready!(self.sink_mut().expect("Attempted to poll Forward after completion").close()); return Ok(Async::Ready(self.take_result())) } Async::NotReady => { - try_ready!(self.sink_mut().take().expect("Attempted to poll Forward after completion").poll_complete()); + try_ready!(self.sink_mut().expect("Attempted to poll Forward after completion").poll_complete()); return Ok(Async::NotReady) } } From 4cc9edc23fb8b26169b200d6cc71d4b1de30bea8 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Fri, 22 Mar 2019 06:33:18 +0900 Subject: [PATCH 49/70] Allow deprecated ATOMIC_USIZE_INIT --- futures-cpupool/tests/smoke.rs | 7 ++++++- src/task_impl/core.rs | 6 +++++- src/task_impl/mod.rs | 5 ++++- tests/channel.rs | 1 + 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/futures-cpupool/tests/smoke.rs b/futures-cpupool/tests/smoke.rs index 1b267f2f02..4df671ebae 100644 --- a/futures-cpupool/tests/smoke.rs +++ b/futures-cpupool/tests/smoke.rs @@ -1,7 +1,9 @@ extern crate futures; extern crate futures_cpupool; -use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; +use std::sync::atomic::{AtomicUsize, Ordering}; +#[allow(deprecated)] +use std::sync::atomic::ATOMIC_USIZE_INIT; use std::thread; use std::time::Duration; @@ -36,6 +38,7 @@ fn select() { #[test] fn threads_go_away() { + #[allow(deprecated)] static CNT: AtomicUsize = ATOMIC_USIZE_INIT; struct A; @@ -66,7 +69,9 @@ fn threads_go_away() { #[test] fn lifecycle_test() { + #[allow(deprecated)] static NUM_STARTS: AtomicUsize = ATOMIC_USIZE_INIT; + #[allow(deprecated)] static NUM_STOPS: AtomicUsize = ATOMIC_USIZE_INIT; fn after_start() { diff --git a/src/task_impl/core.rs b/src/task_impl/core.rs index 9a77a2aef9..356cd8909d 100644 --- a/src/task_impl/core.rs +++ b/src/task_impl/core.rs @@ -2,7 +2,9 @@ use core::marker; use core::mem; -use core::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT}; +use core::sync::atomic::AtomicUsize; +#[allow(deprecated)] +use core::sync::atomic::ATOMIC_USIZE_INIT; use core::sync::atomic::Ordering::{SeqCst, Relaxed}; use super::{BorrowedTask, NotifyHandle}; @@ -84,7 +86,9 @@ impl Drop for TaskUnpark { } } +#[allow(deprecated)] static GET: AtomicUsize = ATOMIC_USIZE_INIT; +#[allow(deprecated)] static SET: AtomicUsize = ATOMIC_USIZE_INIT; /// Initialize the `futures` task system. diff --git a/src/task_impl/mod.rs b/src/task_impl/mod.rs index bed49a35ed..6f1cf36c0c 100644 --- a/src/task_impl/mod.rs +++ b/src/task_impl/mod.rs @@ -24,13 +24,16 @@ pub struct BorrowedTask<'a> { } fn fresh_task_id() -> usize { - use core::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; + use core::sync::atomic::{AtomicUsize, Ordering}; + #[allow(deprecated)] + use core::sync::atomic::ATOMIC_USIZE_INIT; // TODO: this assert is a real bummer, need to figure out how to reuse // old IDs that are no longer in use. // // Note, though, that it is intended that these ids go away entirely // eventually, see the comment on `is_current` below. + #[allow(deprecated)] static NEXT_ID: AtomicUsize = ATOMIC_USIZE_INIT; let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); assert!(id < usize::max_value() / 2, diff --git a/tests/channel.rs b/tests/channel.rs index 58c611b5ad..e2f11202d0 100644 --- a/tests/channel.rs +++ b/tests/channel.rs @@ -52,6 +52,7 @@ fn drop_rx() { #[test] fn drop_order() { + #[allow(deprecated)] static DROPS: AtomicUsize = ATOMIC_USIZE_INIT; let (tx, rx) = mpsc::channel(1); From b77dc5608fd221c67e48fef43817c94371d4d618 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Fri, 22 Mar 2019 06:35:21 +0900 Subject: [PATCH 50/70] Allow deprecated Error::cause --- src/future/shared.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/future/shared.rs b/src/future/shared.rs index 25417ef071..f40c0be170 100644 --- a/src/future/shared.rs +++ b/src/future/shared.rs @@ -306,6 +306,7 @@ impl error::Error for SharedError self.error.description() } + #[allow(deprecated)] fn cause(&self) -> Option<&error::Error> { self.error.cause() } From ae1b6672d4076ed71df29af38534a4fa5a5da265 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Fri, 22 Mar 2019 05:32:49 +0900 Subject: [PATCH 51/70] Correct the behavior of Chunks when items are ZST --- src/stream/chunks.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/stream/chunks.rs b/src/stream/chunks.rs index 3a361eb6f2..dbfaeb89ec 100644 --- a/src/stream/chunks.rs +++ b/src/stream/chunks.rs @@ -16,7 +16,8 @@ pub struct Chunks { items: Vec, err: Option, - stream: Fuse + stream: Fuse, + cap: usize, // https://github.com/rust-lang-nursery/futures-rs/issues/1475 } pub fn new(s: S, capacity: usize) -> Chunks @@ -28,6 +29,7 @@ pub fn new(s: S, capacity: usize) -> Chunks items: Vec::with_capacity(capacity), err: None, stream: super::fuse::new(s), + cap: capacity, } } @@ -54,7 +56,7 @@ impl ::sink::Sink for Chunks impl Chunks where S: Stream { fn take(&mut self) -> Vec { - let cap = self.items.capacity(); + let cap = self.cap; mem::replace(&mut self.items, Vec::with_capacity(cap)) } @@ -93,7 +95,6 @@ impl Stream for Chunks return Err(err) } - let cap = self.items.capacity(); loop { match self.stream.poll() { Ok(Async::NotReady) => return Ok(Async::NotReady), @@ -103,7 +104,7 @@ impl Stream for Chunks // the full one. Ok(Async::Ready(Some(item))) => { self.items.push(item); - if self.items.len() >= cap { + if self.items.len() >= self.cap { return Ok(Some(self.take()).into()) } } From 043b16f96732c07df560e6198728ec470d7e96e6 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Mon, 1 Apr 2019 17:51:36 -0700 Subject: [PATCH 52/70] Release 0.1.26 --- Cargo.toml | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2abefae7a6..37bff58b54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "futures" -version = "0.1.24" +version = "0.1.26" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" diff --git a/README.md b/README.md index aeb3fd4e22..3052efcb18 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ First, add this to your `Cargo.toml`: ```toml [dependencies] -futures = "0.1.17" +futures = "0.1.26" ``` Next, add this to your crate: @@ -39,7 +39,7 @@ a `#[no_std]` environment, use: ```toml [dependencies] -futures = { version = "0.1.17", default-features = false } +futures = { version = "0.1.26", default-features = false } ``` # License From bd05590594c4a213ee70536ae2fde1be6d714f47 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Thu, 9 May 2019 15:27:00 +0100 Subject: [PATCH 53/70] Add is_in_task This returns whether the caller is in a Task. If a task_local! is accessed from outside a Task, it panics. Right now, there is no way for a caller to know whether it's safe to look in a task_local!. --- src/task.rs | 2 +- src/task_impl/core.rs | 9 +++++++++ src/task_impl/std/mod.rs | 5 +++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/task.rs b/src/task.rs index 941f227dd3..f83f2c4719 100644 --- a/src/task.rs +++ b/src/task.rs @@ -33,7 +33,7 @@ #[allow(deprecated)] pub use task_impl::{Spawn, spawn, Unpark, Executor, Run, park}; -pub use task_impl::{Task, AtomicTask, current, init}; +pub use task_impl::{Task, AtomicTask, current, init, is_in_task}; #[allow(deprecated)] #[cfg(feature = "use_std")] diff --git a/src/task_impl/core.rs b/src/task_impl/core.rs index 356cd8909d..d454116012 100644 --- a/src/task_impl/core.rs +++ b/src/task_impl/core.rs @@ -140,6 +140,15 @@ pub unsafe fn init(get: fn() -> *mut u8, set: fn(*mut u8)) -> bool { } } +/// Return whether the caller is running in a task (and so can use task_local!). +pub fn is_in_task() -> bool { + if let Some(ptr) = get_ptr() { + !ptr.is_null() + } else { + false + } +} + #[inline] pub fn get_ptr() -> Option<*mut u8> { match GET.load(Relaxed) { diff --git a/src/task_impl/std/mod.rs b/src/task_impl/std/mod.rs index db51969e7d..eca750f7d8 100644 --- a/src/task_impl/std/mod.rs +++ b/src/task_impl/std/mod.rs @@ -27,6 +27,11 @@ pub use task_impl::core::init; thread_local!(static CURRENT_TASK: Cell<*mut u8> = Cell::new(ptr::null_mut())); +/// Return whether the caller is running in a task (and so can use task_local!). +pub fn is_in_task() -> bool { + CURRENT_TASK.with(|task| !task.get().is_null()) +} + static INIT: Once = ONCE_INIT; pub fn get_ptr() -> Option<*mut u8> { From 5654784d5b677dadc4b86affc575d12aa737e4a7 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Fri, 10 May 2019 12:13:16 -0700 Subject: [PATCH 54/70] Release 0.1.27 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 37bff58b54..9c46b75285 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "futures" -version = "0.1.26" +version = "0.1.27" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" From d2d44de1096d5cc57aa674cb3d9187c362a7eb2b Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Tue, 14 May 2019 09:58:29 -0700 Subject: [PATCH 55/70] Add futures01 crate --- Cargo.toml | 2 +- futures01/Cargo.toml | 26 +++++++ futures01/src/lib.rs | 164 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 futures01/Cargo.toml create mode 100644 futures01/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 9c46b75285..a3a32abe29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,4 @@ with-deprecated = [] default = ["use_std", "with-deprecated"] [workspace] -members = ["futures-cpupool"] +members = ["futures01", "futures-cpupool"] diff --git a/futures01/Cargo.toml b/futures01/Cargo.toml new file mode 100644 index 0000000000..a034d4836e --- /dev/null +++ b/futures01/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "futures01" +version = "0.1.27" +authors = ["Alex Crichton "] +license = "MIT/Apache-2.0" +repository = "https://github.com/rust-lang-nursery/futures-rs" +homepage = "https://github.com/rust-lang-nursery/futures-rs" +documentation = "https://docs.rs/futures" +description = """ +An implementation of futures and streams featuring zero allocations, +composability, and iterator-like interfaces. + +Note that this crate tracks permanently to the 0.1 branch of +the `futures` crate. +""" + +[dependencies.futures] +path = ".." +version = "0.1.27" +default-features = false + +[features] +default = ["use_std", "with-deprecated"] +nightly = ["futures/nightly"] +use_std = ["futures/use_std"] +with-deprecated = ["futures/with-deprecated"] diff --git a/futures01/src/lib.rs b/futures01/src/lib.rs new file mode 100644 index 0000000000..e90bb2037d --- /dev/null +++ b/futures01/src/lib.rs @@ -0,0 +1,164 @@ +//! Zero-cost Futures in Rust +//! +//! This library is an implementation of futures in Rust which aims to provide +//! a robust implementation of handling asynchronous computations, ergonomic +//! composition and usage, and zero-cost abstractions over what would otherwise +//! be written by hand. +//! +//! Futures are a concept for an object which is a proxy for another value that +//! may not be ready yet. For example issuing an HTTP request may return a +//! future for the HTTP response, as it probably hasn't arrived yet. With an +//! object representing a value that will eventually be available, futures allow +//! for powerful composition of tasks through basic combinators that can perform +//! operations like chaining computations, changing the types of futures, or +//! waiting for two futures to complete at the same time. +//! +//! You can find extensive tutorials and documentations at [https://tokio.rs] +//! for both this crate (asynchronous programming in general) as well as the +//! Tokio stack to perform async I/O with. +//! +//! [https://tokio.rs]: https://tokio.rs +//! +//! ## Installation +//! +//! Add this to your `Cargo.toml`: +//! +//! ```toml +//! [dependencies] +//! futures = "0.1" +//! ``` +//! +//! ## Examples +//! +//! Let's take a look at a few examples of how futures might be used: +//! +//! ``` +//! extern crate futures; +//! +//! use std::io; +//! use std::time::Duration; +//! use futures::prelude::*; +//! use futures::future::Map; +//! +//! // A future is actually a trait implementation, so we can generically take a +//! // future of any integer and return back a future that will resolve to that +//! // value plus 10 more. +//! // +//! // Note here that like iterators, we're returning the `Map` combinator in +//! // the futures crate, not a boxed abstraction. This is a zero-cost +//! // construction of a future. +//! fn add_ten(future: F) -> Map i32> +//! where F: Future, +//! { +//! fn add(a: i32) -> i32 { a + 10 } +//! future.map(add) +//! } +//! +//! // Not only can we modify one future, but we can even compose them together! +//! // Here we have a function which takes two futures as input, and returns a +//! // future that will calculate the sum of their two values. +//! // +//! // Above we saw a direct return value of the `Map` combinator, but +//! // performance isn't always critical and sometimes it's more ergonomic to +//! // return a trait object like we do here. Note though that there's only one +//! // allocation here, not any for the intermediate futures. +//! fn add<'a, A, B>(a: A, b: B) -> Box + 'a> +//! where A: Future + 'a, +//! B: Future + 'a, +//! { +//! Box::new(a.join(b).map(|(a, b)| a + b)) +//! } +//! +//! // Futures also allow chaining computations together, starting another after +//! // the previous finishes. Here we wait for the first computation to finish, +//! // and then decide what to do depending on the result. +//! fn download_timeout(url: &str, +//! timeout_dur: Duration) +//! -> Box, Error=io::Error>> { +//! use std::io; +//! use std::net::{SocketAddr, TcpStream}; +//! +//! type IoFuture = Box>; +//! +//! // First thing to do is we need to resolve our URL to an address. This +//! // will likely perform a DNS lookup which may take some time. +//! let addr = resolve(url); +//! +//! // After we acquire the address, we next want to open up a TCP +//! // connection. +//! let tcp = addr.and_then(|addr| connect(&addr)); +//! +//! // After the TCP connection is established and ready to go, we're off to +//! // the races! +//! let data = tcp.and_then(|conn| download(conn)); +//! +//! // That all might take awhile, though, so let's not wait too long for it +//! // to all come back. The `select` combinator here returns a future which +//! // resolves to the first value that's ready plus the next future. +//! // +//! // Note we can also use the `then` combinator which is similar to +//! // `and_then` above except that it receives the result of the +//! // computation, not just the successful value. +//! // +//! // Again note that all the above calls to `and_then` and the below calls +//! // to `map` and such require no allocations. We only ever allocate once +//! // we hit the `Box::new()` call at the end here, which means we've built +//! // up a relatively involved computation with only one box, and even that +//! // was optional! +//! +//! let data = data.map(Ok); +//! let timeout = timeout(timeout_dur).map(Err); +//! +//! let ret = data.select(timeout).then(|result| { +//! match result { +//! // One future succeeded, and it was the one which was +//! // downloading data from the connection. +//! Ok((Ok(data), _other_future)) => Ok(data), +//! +//! // The timeout fired, and otherwise no error was found, so +//! // we translate this to an error. +//! Ok((Err(_timeout), _other_future)) => { +//! Err(io::Error::new(io::ErrorKind::Other, "timeout")) +//! } +//! +//! // A normal I/O error happened, so we pass that on through. +//! Err((e, _other_future)) => Err(e), +//! } +//! }); +//! return Box::new(ret); +//! +//! fn resolve(url: &str) -> IoFuture { +//! // ... +//! # panic!("unimplemented"); +//! } +//! +//! fn connect(hostname: &SocketAddr) -> IoFuture { +//! // ... +//! # panic!("unimplemented"); +//! } +//! +//! fn download(stream: TcpStream) -> IoFuture> { +//! // ... +//! # panic!("unimplemented"); +//! } +//! +//! fn timeout(stream: Duration) -> IoFuture<()> { +//! // ... +//! # panic!("unimplemented"); +//! } +//! } +//! # fn main() {} +//! ``` +//! +//! Some more information can also be found in the [README] for now, but +//! otherwise feel free to jump in to the docs below! +//! +//! [README]: https://github.com/rust-lang-nursery/futures-rs#futures-rs + +#![no_std] +#![doc(html_root_url = "https://docs.rs/futures/0.1")] + +extern crate futures; + +#[doc(inline)] +pub use futures::*; From 1b65f9307032dafae295b8598f79872678c483d3 Mon Sep 17 00:00:00 2001 From: Arve Seljebu Date: Mon, 20 May 2019 12:22:16 +0200 Subject: [PATCH 56/70] concat -> concat2 typo for Concat2 struct --- src/stream/concat.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stream/concat.rs b/src/stream/concat.rs index aae5623efa..a0da71bdd5 100644 --- a/src/stream/concat.rs +++ b/src/stream/concat.rs @@ -9,7 +9,7 @@ use stream::Stream; /// A stream combinator to concatenate the results of a stream into the first /// yielded item. /// -/// This structure is produced by the `Stream::concat` method. +/// This structure is produced by the `Stream::concat2` method. #[must_use = "streams do nothing unless polled"] pub struct Concat2 where S: Stream, @@ -169,4 +169,4 @@ enum Inner { First, Extending(E), Done, -} \ No newline at end of file +} From 0da26ae56ed71350bf05e734d1487ba5c9562280 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 12 Feb 2018 13:47:32 -0800 Subject: [PATCH 57/70] Implement Stream for Either Closes #614 --- src/future/either.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/future/either.rs b/src/future/either.rs index f8c47f10eb..253f26784c 100644 --- a/src/future/either.rs +++ b/src/future/either.rs @@ -1,4 +1,4 @@ -use {Future, Poll}; +use {Future, Poll, Stream}; /// Combines two different futures yielding the same item and error /// types into a single type. @@ -37,3 +37,18 @@ impl Future for Either } } } + +impl Stream for Either + where A: Stream, + B: Stream +{ + type Item = A::Item; + type Error = A::Error; + + fn poll(&mut self) -> Poll, A::Error> { + match *self { + Either::A(ref mut a) => a.poll(), + Either::B(ref mut b) => b.poll(), + } + } +} From 632838c87c7e338bce2cddf11e60ffdf9e2a9da6 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Wed, 26 Jun 2019 10:19:10 -0700 Subject: [PATCH 58/70] 0.1.28 release --- Cargo.toml | 2 +- futures01/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a3a32abe29..95be2f3f1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "futures" -version = "0.1.27" +version = "0.1.28" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" diff --git a/futures01/Cargo.toml b/futures01/Cargo.toml index a034d4836e..579efd6234 100644 --- a/futures01/Cargo.toml +++ b/futures01/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "futures01" -version = "0.1.27" +version = "0.1.28" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/rust-lang-nursery/futures-rs" @@ -16,7 +16,7 @@ the `futures` crate. [dependencies.futures] path = ".." -version = "0.1.27" +version = "0.1.28" default-features = false [features] From ad7830d8b79ea397cba73297a59d3eedf3a7491c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Rouill=C3=A9?= Date: Wed, 17 Jul 2019 18:45:24 +0200 Subject: [PATCH 59/70] add must_use attributes on Future and Stream --- src/future/mod.rs | 1 + src/stream/mod.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/future/mod.rs b/src/future/mod.rs index 063322e29d..9867765902 100644 --- a/src/future/mod.rs +++ b/src/future/mod.rs @@ -174,6 +174,7 @@ use {Poll, stream}; /// More information about combinators can be found [on tokio.rs]. /// /// [on tokio.rs]: https://tokio.rs/docs/going-deeper-futures/futures-mechanics/ +#[must_use = "futures do nothing unless polled"] pub trait Future { /// The type of value that this future will resolved with if it is /// successful. diff --git a/src/stream/mod.rs b/src/stream/mod.rs index b674aefdcd..2d90362470 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -180,6 +180,7 @@ if_std! { /// please feel welcome to comment on [the issue][being considered]! /// /// [being considered]: https://github.com/rust-lang-nursery/futures-rs/issues/206 +#[must_use = "streams do nothing unless polled"] pub trait Stream { /// The type of item this stream will yield on success. type Item; From 56f8eb99eb66e19267df42cf4d3fd459b87f4c77 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Thu, 5 Sep 2019 09:47:33 -0700 Subject: [PATCH 60/70] 0.1.29 release --- Cargo.toml | 2 +- futures01/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 95be2f3f1e..99863783f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "futures" -version = "0.1.28" +version = "0.1.29" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" diff --git a/futures01/Cargo.toml b/futures01/Cargo.toml index 579efd6234..16d294d6c7 100644 --- a/futures01/Cargo.toml +++ b/futures01/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "futures01" -version = "0.1.28" +version = "0.1.29" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/rust-lang-nursery/futures-rs" @@ -16,7 +16,7 @@ the `futures` crate. [dependencies.futures] path = ".." -version = "0.1.28" +version = "0.1.29" default-features = false [features] From a00d35efcb5bfa5581f695618ca7b77068998fa0 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Wed, 11 Sep 2019 23:07:07 +0300 Subject: [PATCH 61/70] Don't bypass stream fuse in 0.1 Forward The poll implementation for Forward used method stream_mut to get at the stream. This bypasses the Fuse that was meant to prevent stream from being polled more than once in case close() on the sink initially returns NotReady. --- src/stream/forward.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stream/forward.rs b/src/stream/forward.rs index 1d8b731828..6722af8c20 100644 --- a/src/stream/forward.rs +++ b/src/stream/forward.rs @@ -91,7 +91,7 @@ impl Future for Forward } loop { - match self.stream_mut() + match self.stream.as_mut() .expect("Attempted to poll Forward after completion") .poll()? { From b85f56d85c33925d2580f58d586132edafc7e1f0 Mon Sep 17 00:00:00 2001 From: Thomas Orozco Date: Fri, 10 Apr 2020 10:47:01 +0100 Subject: [PATCH 62/70] Backport to 0.1: Avoid starvation from FuturesUnordered::poll_next This backports https://github.com/rust-lang/futures-rs/pull/2049/ to the 0.1 branch. Without this change, polling > 200 futures trough a FuturesUnordered on a Tokio 0.2 executor results in a busy loop in Tokio's cooperative scheduling module. See for a repro of where this breaks: https://github.com/tokio-rs/tokio/issues/2390 Tested by running the reproducer I submitted there. Without this change, it hangs forever (spinning on CPU). With the change, it doesn't. --- src/stream/futures_unordered.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/stream/futures_unordered.rs b/src/stream/futures_unordered.rs index a183d63406..d529d93fe9 100644 --- a/src/stream/futures_unordered.rs +++ b/src/stream/futures_unordered.rs @@ -15,6 +15,23 @@ use {task, Stream, Future, Poll, Async}; use executor::{Notify, UnsafeNotify, NotifyHandle}; use task_impl::{self, AtomicTask}; +/// Constant used for a `FuturesUnordered` to determine how many times it is +/// allowed to poll underlying futures without yielding. +/// +/// A single call to `poll_next` may potentially do a lot of work before +/// yielding. This happens in particular if the underlying futures are awoken +/// frequently but continue to return `Pending`. This is problematic if other +/// tasks are waiting on the executor, since they do not get to run. This value +/// caps the number of calls to `poll` on underlying futures a single call to +/// `poll_next` is allowed to make. +/// +/// The value itself is chosen somewhat arbitrarily. It needs to be high enough +/// that amortize wakeup and scheduling costs, but low enough that we do not +/// starve other tasks for long. +/// +/// See also https://github.com/rust-lang/futures-rs/issues/2047. +const YIELD_EVERY: usize = 32; + /// An unbounded set of futures. /// /// This "combinator" also serves a special function in this library, providing @@ -274,6 +291,10 @@ impl Stream for FuturesUnordered type Error = T::Error; fn poll(&mut self) -> Poll, T::Error> { + // Keep track of how many child futures we have polled, + // in case we want to forcibly yield. + let mut polled = 0; + // Ensure `parent` is correctly set. self.inner.parent.register(); @@ -369,12 +390,21 @@ impl Stream for FuturesUnordered future.poll() }) }; + polled += 1; let ret = match res { Ok(Async::NotReady) => { let node = bomb.node.take().unwrap(); *node.future.get() = Some(future); bomb.queue.link(node); + + if polled == YIELD_EVERY { + // We have polled a large number of futures in a row without yielding. + // To ensure we do not starve other tasks waiting on the executor, + // we yield here, but immediately wake ourselves up to continue. + task_impl::current().notify(); + return Ok(Async::NotReady); + } continue } Ok(Async::Ready(e)) => Ok(Async::Ready(Some(e))), From cbaaac24d550b48c244d16b630b1ddde7dd45ec5 Mon Sep 17 00:00:00 2001 From: Thomas Orozco Date: Wed, 13 May 2020 09:40:29 +0100 Subject: [PATCH 63/70] Backport to 0.1: Shared must relinquish control to the executor if repolled This backports #2136 to Futures 0.1. There isn't much to add on top of what's mentioned in #2136: the same bug exists in Futures 0.1, and it'll fail in the same way when polled in recent versions of Tokio (#2418). Backporting to 0.1 allows codebases that still have some Futures 0.1 code around to still use newer versions of Tokio. --- src/future/shared.rs | 80 ++++++++++++++++++-------------------------- tests/shared.rs | 30 +++++++++++++++++ 2 files changed, 63 insertions(+), 47 deletions(-) diff --git a/src/future/shared.rs b/src/future/shared.rs index f40c0be170..e6ec23dbe9 100644 --- a/src/future/shared.rs +++ b/src/future/shared.rs @@ -59,9 +59,8 @@ struct Notifier { const IDLE: usize = 0; const POLLING: usize = 1; -const REPOLL: usize = 2; -const COMPLETE: usize = 3; -const POISONED: usize = 4; +const COMPLETE: usize = 2; +const POISONED: usize = 3; pub fn new(future: F) -> Shared { Shared { @@ -133,7 +132,7 @@ impl Future for Shared IDLE => { // Lock acquired, fall through } - POLLING | REPOLL => { + POLLING => { // Another task is currently polling, at this point we just want // to ensure that our task handle is currently registered @@ -146,56 +145,45 @@ impl Future for Shared _ => unreachable!(), } - loop { - struct Reset<'a>(&'a AtomicUsize); + struct Reset<'a>(&'a AtomicUsize); - impl<'a> Drop for Reset<'a> { - fn drop(&mut self) { - use std::thread; + impl<'a> Drop for Reset<'a> { + fn drop(&mut self) { + use std::thread; - if thread::panicking() { - self.0.store(POISONED, SeqCst); - } + if thread::panicking() { + self.0.store(POISONED, SeqCst); } } + } - let _reset = Reset(&self.inner.notifier.state); - - // Poll the future - let res = unsafe { - (*self.inner.future.get()).as_mut().unwrap() - .poll_future_notify(&self.inner.notifier, 0) - }; - match res { - Ok(Async::NotReady) => { - // Not ready, try to release the handle - match self.inner.notifier.state.compare_and_swap(POLLING, IDLE, SeqCst) { - POLLING => { - // Success - return Ok(Async::NotReady); - } - REPOLL => { - // Gotta poll again! - let prev = self.inner.notifier.state.swap(POLLING, SeqCst); - assert_eq!(prev, REPOLL); - } - _ => unreachable!(), + let _reset = Reset(&self.inner.notifier.state); + + // Poll the future + let res = unsafe { + (*self.inner.future.get()).as_mut().unwrap() + .poll_future_notify(&self.inner.notifier, 0) + }; + match res { + Ok(Async::NotReady) => { + // Not ready, try to release the handle + match self.inner.notifier.state.compare_and_swap(POLLING, IDLE, SeqCst) { + POLLING => { + // Success + return Ok(Async::NotReady); } - + _ => unreachable!(), } - Ok(Async::Ready(i)) => { - unsafe { - (*self.inner.result.get()) = Some(Ok(SharedItem { item: Arc::new(i) })); - } - break; + } + Ok(Async::Ready(i)) => { + unsafe { + (*self.inner.result.get()) = Some(Ok(SharedItem { item: Arc::new(i) })); } - Err(e) => { - unsafe { - (*self.inner.result.get()) = Some(Err(SharedError { error: Arc::new(e) })); - } - - break; + } + Err(e) => { + unsafe { + (*self.inner.result.get()) = Some(Err(SharedError { error: Arc::new(e) })); } } } @@ -225,8 +213,6 @@ impl Drop for Shared where F: Future { impl Notify for Notifier { fn notify(&self, _id: usize) { - self.state.compare_and_swap(POLLING, REPOLL, SeqCst); - let waiters = mem::replace(&mut *self.waiters.lock().unwrap(), HashMap::new()); for (_, waiter) in waiters { diff --git a/tests/shared.rs b/tests/shared.rs index 99d2b381ea..e465d6acc3 100644 --- a/tests/shared.rs +++ b/tests/shared.rs @@ -202,3 +202,33 @@ fn recursive_poll_with_unpark() { drop(tx0); core.run(f3).unwrap(); } + +#[test] +fn shared_future_that_wakes_itself_until_pending_is_returned() { + use futures::Async; + use std::cell::Cell; + + let core = ::support::local_executor::Core::new(); + + let proceed = Cell::new(false); + let fut = futures::future::poll_fn(|| { + Ok::<_, ()>(if proceed.get() { + Async::Ready(()) + } else { + futures::task::current().notify(); + Async::NotReady + }) + }) + .shared() + .map(|_| ()) + .map_err(|_| ()); + + // The join future can only complete if the second future gets a chance to run after the first + // has returned pending + let second = futures::future::lazy(|| { + proceed.set(true); + Ok::<_, ()>(()) + }); + + core.run(fut.join(second)).unwrap(); +} From 8f9c4a6824044124d71415c6cc6511a540e26743 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Wed, 30 Sep 2020 19:02:36 +0900 Subject: [PATCH 64/70] Allow deprecated warnings (#2221) --- futures-cpupool/src/lib.rs | 1 + futures-cpupool/tests/smoke.rs | 2 ++ src/future/shared.rs | 1 + src/lib.rs | 1 + src/task_impl/std/mod.rs | 5 ++++- tests/all.rs | 2 ++ tests/bilock.rs | 2 ++ tests/channel.rs | 2 ++ tests/futures_ordered.rs | 2 ++ tests/futures_unordered.rs | 2 ++ tests/mpsc.rs | 1 + tests/recurse.rs | 2 ++ tests/shared.rs | 2 ++ tests/sink.rs | 4 +++- tests/stream.rs | 2 ++ tests/support/local_executor.rs | 2 ++ tests/unsync.rs | 1 + 17 files changed, 32 insertions(+), 2 deletions(-) diff --git a/futures-cpupool/src/lib.rs b/futures-cpupool/src/lib.rs index 90b6168bb7..53e48699eb 100644 --- a/futures-cpupool/src/lib.rs +++ b/futures-cpupool/src/lib.rs @@ -36,6 +36,7 @@ #![deny(missing_docs)] #![deny(missing_debug_implementations)] +#![allow(bare_trait_objects, unknown_lints)] extern crate futures; extern crate num_cpus; diff --git a/futures-cpupool/tests/smoke.rs b/futures-cpupool/tests/smoke.rs index 4df671ebae..fbf27c7744 100644 --- a/futures-cpupool/tests/smoke.rs +++ b/futures-cpupool/tests/smoke.rs @@ -1,3 +1,5 @@ +#![allow(bare_trait_objects, unknown_lints)] + extern crate futures; extern crate futures_cpupool; diff --git a/src/future/shared.rs b/src/future/shared.rs index e6ec23dbe9..e3b6d2fca7 100644 --- a/src/future/shared.rs +++ b/src/future/shared.rs @@ -288,6 +288,7 @@ impl fmt::Display for SharedError impl error::Error for SharedError where E: error::Error, { + #[allow(deprecated)] fn description(&self) -> &str { self.error.description() } diff --git a/src/lib.rs b/src/lib.rs index 59e4874b2c..ccadb6777f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -157,6 +157,7 @@ #![no_std] #![deny(missing_docs, missing_debug_implementations)] +#![allow(bare_trait_objects, unknown_lints)] #![doc(html_root_url = "https://docs.rs/futures/0.1")] #[macro_use] diff --git a/src/task_impl/std/mod.rs b/src/task_impl/std/mod.rs index eca750f7d8..e82a23e5d0 100644 --- a/src/task_impl/std/mod.rs +++ b/src/task_impl/std/mod.rs @@ -5,7 +5,9 @@ use std::fmt; use std::marker::PhantomData; use std::mem; use std::ptr; -use std::sync::{Arc, Mutex, Condvar, Once, ONCE_INIT}; +use std::sync::{Arc, Mutex, Condvar, Once}; +#[allow(deprecated)] +use std::sync::ONCE_INIT; use std::sync::atomic::{AtomicUsize, Ordering}; use {Future, Stream, Sink, Poll, Async, StartSend, AsyncSink}; @@ -32,6 +34,7 @@ pub fn is_in_task() -> bool { CURRENT_TASK.with(|task| !task.get().is_null()) } +#[allow(deprecated)] static INIT: Once = ONCE_INIT; pub fn get_ptr() -> Option<*mut u8> { diff --git a/tests/all.rs b/tests/all.rs index bdd67315c5..40e402f553 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -1,3 +1,5 @@ +#![allow(bare_trait_objects, unknown_lints)] + extern crate futures; use std::sync::mpsc::{channel, TryRecvError}; diff --git a/tests/bilock.rs b/tests/bilock.rs index 78d873635a..1658bdae27 100644 --- a/tests/bilock.rs +++ b/tests/bilock.rs @@ -1,3 +1,5 @@ +#![allow(bare_trait_objects, unknown_lints)] + extern crate futures; use std::thread; diff --git a/tests/channel.rs b/tests/channel.rs index e2f11202d0..7940de4509 100644 --- a/tests/channel.rs +++ b/tests/channel.rs @@ -1,3 +1,5 @@ +#![allow(bare_trait_objects, unknown_lints)] + extern crate futures; use std::sync::atomic::*; diff --git a/tests/futures_ordered.rs b/tests/futures_ordered.rs index 229a8e58c0..6054192e3b 100644 --- a/tests/futures_ordered.rs +++ b/tests/futures_ordered.rs @@ -1,3 +1,5 @@ +#![allow(bare_trait_objects, unknown_lints)] + extern crate futures; use std::any::Any; diff --git a/tests/futures_unordered.rs b/tests/futures_unordered.rs index 9b8c08d01b..e29659f011 100644 --- a/tests/futures_unordered.rs +++ b/tests/futures_unordered.rs @@ -1,3 +1,5 @@ +#![allow(bare_trait_objects, unknown_lints)] + extern crate futures; use std::any::Any; diff --git a/tests/mpsc.rs b/tests/mpsc.rs index faeb614608..9cb83e5952 100644 --- a/tests/mpsc.rs +++ b/tests/mpsc.rs @@ -1,4 +1,5 @@ #![cfg(feature = "use_std")] +#![allow(bare_trait_objects, unknown_lints)] #[macro_use] extern crate futures; diff --git a/tests/recurse.rs b/tests/recurse.rs index 4eb024ac95..a521ed13b7 100644 --- a/tests/recurse.rs +++ b/tests/recurse.rs @@ -1,3 +1,5 @@ +#![allow(bare_trait_objects, unknown_lints)] + extern crate futures; use std::sync::mpsc::channel; diff --git a/tests/shared.rs b/tests/shared.rs index e465d6acc3..97989fe2cb 100644 --- a/tests/shared.rs +++ b/tests/shared.rs @@ -1,3 +1,5 @@ +#![allow(bare_trait_objects, unknown_lints)] + extern crate futures; mod support; diff --git a/tests/sink.rs b/tests/sink.rs index c8a34d9e03..460dbdf20c 100644 --- a/tests/sink.rs +++ b/tests/sink.rs @@ -1,3 +1,5 @@ +#![allow(bare_trait_objects, unknown_lints)] + extern crate futures; use std::mem; @@ -372,7 +374,7 @@ fn fanout_backpressure() { let sink = StartSendFut::new(sink, 0).wait().unwrap(); let sink = StartSendFut::new(sink, 1).wait().unwrap(); - + let flag = Flag::new(); let mut task = executor::spawn(sink.send(2)); assert!(!flag.get()); diff --git a/tests/stream.rs b/tests/stream.rs index eb7560351d..2400a2abb1 100644 --- a/tests/stream.rs +++ b/tests/stream.rs @@ -1,3 +1,5 @@ +#![allow(bare_trait_objects, unknown_lints)] + #[macro_use] extern crate futures; diff --git a/tests/support/local_executor.rs b/tests/support/local_executor.rs index 615efc1f70..cf89e8152f 100644 --- a/tests/support/local_executor.rs +++ b/tests/support/local_executor.rs @@ -4,6 +4,8 @@ //! futures-aware inter-thread communications, and is not intended to be used to //! manage I/O. For futures that do I/O you'll likely want to use `tokio-core`. +#![allow(bare_trait_objects, unknown_lints)] + use std::cell::{Cell, RefCell}; use std::sync::{Arc, Mutex, mpsc}; diff --git a/tests/unsync.rs b/tests/unsync.rs index 3d11085980..490db0af1c 100644 --- a/tests/unsync.rs +++ b/tests/unsync.rs @@ -1,4 +1,5 @@ #![cfg(feature = "use_std")] +#![allow(bare_trait_objects, unknown_lints)] extern crate futures; From 6db66422556cb587ebd7923087ee8bb154b1b6b5 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Wed, 30 Sep 2020 16:59:43 +0900 Subject: [PATCH 65/70] Release 0.1.30 --- Cargo.toml | 2 +- futures01/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 99863783f2..332ab52dea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "futures" -version = "0.1.29" +version = "0.1.30" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" diff --git a/futures01/Cargo.toml b/futures01/Cargo.toml index 16d294d6c7..2f53fbc47a 100644 --- a/futures01/Cargo.toml +++ b/futures01/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "futures01" -version = "0.1.29" +version = "0.1.30" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/rust-lang-nursery/futures-rs" @@ -16,7 +16,7 @@ the `futures` crate. [dependencies.futures] path = ".." -version = "0.1.29" +version = "0.1.30" default-features = false [features] From cfe1d82c153daf97520ca0433c0b7e088360a552 Mon Sep 17 00:00:00 2001 From: Thomas Orozco Date: Wed, 24 Feb 2021 12:25:47 +0000 Subject: [PATCH 66/70] Backport to 0.1: FuturesUnordered: Do not poll the same future twice per iteration (#2358) Same as #2333. The same issue exists in 0.1, so backporting it there helps for code that is still using Futures 0.1 in some places. --- src/stream/futures_unordered.rs | 35 ++++++++++++++--------------- tests/futures_unordered.rs | 40 ++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/stream/futures_unordered.rs b/src/stream/futures_unordered.rs index d529d93fe9..3f25c86f39 100644 --- a/src/stream/futures_unordered.rs +++ b/src/stream/futures_unordered.rs @@ -15,23 +15,6 @@ use {task, Stream, Future, Poll, Async}; use executor::{Notify, UnsafeNotify, NotifyHandle}; use task_impl::{self, AtomicTask}; -/// Constant used for a `FuturesUnordered` to determine how many times it is -/// allowed to poll underlying futures without yielding. -/// -/// A single call to `poll_next` may potentially do a lot of work before -/// yielding. This happens in particular if the underlying futures are awoken -/// frequently but continue to return `Pending`. This is problematic if other -/// tasks are waiting on the executor, since they do not get to run. This value -/// caps the number of calls to `poll` on underlying futures a single call to -/// `poll_next` is allowed to make. -/// -/// The value itself is chosen somewhat arbitrarily. It needs to be high enough -/// that amortize wakeup and scheduling costs, but low enough that we do not -/// starve other tasks for long. -/// -/// See also https://github.com/rust-lang/futures-rs/issues/2047. -const YIELD_EVERY: usize = 32; - /// An unbounded set of futures. /// /// This "combinator" also serves a special function in this library, providing @@ -291,6 +274,22 @@ impl Stream for FuturesUnordered type Error = T::Error; fn poll(&mut self) -> Poll, T::Error> { + // Variable to determine how many times it is allowed to poll underlying + // futures without yielding. + // + // A single call to `poll_next` may potentially do a lot of work before + // yielding. This happens in particular if the underlying futures are awoken + // frequently but continue to return `Pending`. This is problematic if other + // tasks are waiting on the executor, since they do not get to run. This value + // caps the number of calls to `poll` on underlying futures a single call to + // `poll_next` is allowed to make. + // + // The value is the length of FuturesUnordered. This ensures that each + // future is polled only once at most per iteration. + // + // See also https://github.com/rust-lang/futures-rs/issues/2047. + let yield_every = self.len(); + // Keep track of how many child futures we have polled, // in case we want to forcibly yield. let mut polled = 0; @@ -398,7 +397,7 @@ impl Stream for FuturesUnordered *node.future.get() = Some(future); bomb.queue.link(node); - if polled == YIELD_EVERY { + if polled == yield_every { // We have polled a large number of futures in a row without yielding. // To ensure we do not starve other tasks waiting on the executor, // we yield here, but immediately wake ourselves up to continue. diff --git a/tests/futures_unordered.rs b/tests/futures_unordered.rs index e29659f011..325a6f3e48 100644 --- a/tests/futures_unordered.rs +++ b/tests/futures_unordered.rs @@ -5,7 +5,8 @@ extern crate futures; use std::any::Any; use futures::sync::oneshot; -use futures::stream::futures_unordered; +use std::iter::FromIterator; +use futures::stream::{futures_unordered, FuturesUnordered}; use futures::prelude::*; mod support; @@ -127,3 +128,40 @@ fn iter_mut_len() { assert_eq!(iter_mut.len(), 0); assert!(iter_mut.next().is_none()); } + +#[test] +fn polled_only_once_at_most_per_iteration() { + #[derive(Debug, Clone, Copy, Default)] + struct F { + polled: bool, + } + + impl Future for F { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Result, Self::Error> { + if self.polled { + panic!("polled twice") + } else { + self.polled = true; + Ok(Async::NotReady) + } + } + } + + + let tasks = FuturesUnordered::from_iter(vec![F::default(); 10]); + let mut tasks = futures::executor::spawn(tasks); + assert!(tasks.poll_stream_notify(&support::notify_noop(), 0).unwrap().is_not_ready()); + assert_eq!(10, tasks.get_mut().iter_mut().filter(|f| f.polled).count()); + + let tasks = FuturesUnordered::from_iter(vec![F::default(); 33]); + let mut tasks = futures::executor::spawn(tasks); + assert!(tasks.poll_stream_notify(&support::notify_noop(), 0).unwrap().is_not_ready()); + assert_eq!(33, tasks.get_mut().iter_mut().filter(|f| f.polled).count()); + + let tasks = FuturesUnordered::::new(); + let mut tasks = futures::executor::spawn(tasks); + assert!(tasks.poll_stream_notify(&support::notify_noop(), 0).unwrap().is_ready()); +} From 8b26bd830e5dca67fbddd806860b475895080b93 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Wed, 24 Feb 2021 21:58:54 +0900 Subject: [PATCH 67/70] Migrate CI to GitHub Actions (#2360) --- .github/workflows/ci.yml | 61 +++++++++++++++++++++++++++++++++++++++ .travis.yml | 32 -------------------- Cargo.toml | 4 --- README.md | 3 +- appveyor.yml | 39 ------------------------- futures-cpupool/README.md | 3 +- 6 files changed, 63 insertions(+), 79 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml delete mode 100644 appveyor.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..96e35d79f5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: CI + +on: + pull_request: + push: + branches: + - master + - '[0-9]+.[0-9]+' + +env: + RUST_BACKTRACE: 1 + +defaults: + run: + shell: bash + +jobs: + test: + name: cargo +${{ matrix.rust }} test (${{ matrix.os }}) + strategy: + matrix: + include: + - rust: stable + os: ubuntu-latest + - rust: beta + os: ubuntu-latest + - rust: nightly + os: ubuntu-latest + - rust: nightly + os: macos-latest + - rust: nightly + os: windows-latest + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - name: Install Rust + # --no-self-update is necessary because the windows environment cannot self-update rustup.exe. + run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} + - run: cargo install cargo-hack + - run: cargo hack build --feature-powerset --workspace + - run: cargo test --workspace + - run: cargo doc --workspace --no-deps + - run: cargo bench + if: startsWith(matrix.rust, 'nightly') + - run: cargo test --features nightly + if: startsWith(matrix.rust, 'nightly') + + msrv: + name: cargo +${{ matrix.rust }} build + strategy: + matrix: + rust: + # This is the minimum Rust version supported by futures 0.1. + - 1.15.0 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Rust + run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} + - run: cargo build + - run: cargo build --no-default-features diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4f007e9881..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,32 +0,0 @@ -language: rust - -matrix: - include: - - os: osx - - rust: stable - - rust: beta - - rust: nightly - env: BENCH=1 - - os: linux - rust: 1.15.0 - script: cargo test -sudo: false -script: - - cargo build - - cargo build --no-default-features - - cargo test - - cargo test --no-default-features --features use_std - - cargo test --manifest-path futures-cpupool/Cargo.toml - - cargo test --manifest-path futures-cpupool/Cargo.toml --no-default-features - - - cargo doc --no-deps - - cargo doc --no-deps --manifest-path futures-cpupool/Cargo.toml - - if [ "$BENCH" = "1" ]; then cargo bench; fi - - if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then cargo test --features nightly; fi -env: - global: - - secure: "iwVcMVIF7ZSY82fK5UyyUvVvJxMSYrbZawh1+4Oi8pvOdYq1gptcDoOC8jxWwCwrNF1b+/85n+jlEUngEqqSmV5PjAbWPjoc+u4Zn7CRi1AlxoUlvHPiQm4vM4Mkkd6GsqoIZttCeedU9m/w0nQ18uUtK8uD6vr2FVdcMnUnkYQAxuGOowGLrwidukzfBXMCu/JrwKMIbt61knAFiI/KJknu0h1mRrhpeF/sQ3tJFzRRcQeFJkbfwDzltMpPo1hq5D3HI4ONjYi/qO2pwUhDk4umfp9cLW9MS8rQvptxJTQmWemHi+f2/U4ld6a0URL6kEuMkt/EbH0A74eFtlicfRs44dX9MlWoqbLypnC3ymqmHcpwcwNA3HmZyg800MTuU+BPK41HIPdO9tPpxjHEiqvNDknH7qs+YBnis0eH7DHJgEjXq651PjW7pm+rnHPwsj+OzKE1YBNxBQZZDkS3VnZJz+O4tVsOzc3IOz0e+lf7VVuI17C9haj117nKp3umC4MVBA0S8RfreFgqpyDeY2zwcqOr0YOlEGGRl0vyWP8Qcxx12kQ7+doLolt6Kxda4uO0hKRmIF6+qki1T+L7v8BOGOtCncz4f7IX48eQ7+Wu0OtglRn45qAa3CxjUuW6xX3KSNH66PCXV0Jtp8Ga2SSevX2wtbbFu9f+9R+PQY4=" - -notifications: - email: - on_success: never diff --git a/Cargo.toml b/Cargo.toml index 332ab52dea..035af4ed9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,10 +14,6 @@ composability, and iterator-like interfaces. """ categories = ["asynchronous"] -[badges] -travis-ci = { repository = "rust-lang-nursery/futures-rs" } -appveyor = { repository = "rust-lang-nursery/futures-rs" } - [dependencies] [features] diff --git a/README.md b/README.md index 3052efcb18..1c33d5ddbb 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ This library is an implementation of **zero-cost futures** in Rust. -[![Build Status](https://travis-ci.org/rust-lang-nursery/futures-rs.svg?branch=master)](https://travis-ci.org/rust-lang-nursery/futures-rs) -[![Build status](https://ci.appveyor.com/api/projects/status/yl5w3ittk4kggfsh?svg=true)](https://ci.appveyor.com/project/rust-lang-nursery/futures-rs) +[![Build Status](https://img.shields.io/github/workflow/status/rust-lang/futures-rs/CI/master)](https://github.com/rust-lang/futures-rs/actions) [![Crates.io](https://img.shields.io/crates/v/futures.svg?maxAge=2592000)](https://crates.io/crates/futures) [Documentation](https://docs.rs/futures) diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index b516f6084c..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,39 +0,0 @@ -environment: - - # At the time this was added AppVeyor was having troubles with checking - # revocation of SSL certificates of sites like static.rust-lang.org and what - # we think is crates.io. The libcurl HTTP client by default checks for - # revocation on Windows and according to a mailing list [1] this can be - # disabled. - # - # The `CARGO_HTTP_CHECK_REVOKE` env var here tells cargo to disable SSL - # revocation checking on Windows in libcurl. Note, though, that rustup, which - # we're using to download Rust here, also uses libcurl as the default backend. - # Unlike Cargo, however, rustup doesn't have a mechanism to disable revocation - # checking. To get rustup working we set `RUSTUP_USE_HYPER` which forces it to - # use the Hyper instead of libcurl backend. Both Hyper and libcurl use - # schannel on Windows but it appears that Hyper configures it slightly - # differently such that revocation checking isn't turned on by default. - # - # [1]: https://curl.haxx.se/mail/lib-2016-03/0202.html - RUSTUP_USE_HYPER: 1 - CARGO_HTTP_CHECK_REVOKE: false - - matrix: - - TARGET: x86_64-pc-windows-msvc -install: - - set PATH=C:\Program Files\Git\mingw64\bin;%PATH% - - curl -sSf -o rustup-init.exe https://win.rustup.rs/ - - rustup-init.exe -y --default-host %TARGET% - - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin - - rustc -V - - cargo -V - -build: false - -test_script: - - cargo build - - cargo build --no-default-features - - cargo test - - cargo test --no-default-features --features use_std - - cargo test --manifest-path futures-cpupool/Cargo.toml diff --git a/futures-cpupool/README.md b/futures-cpupool/README.md index 9037b2e18e..6950bc4622 100644 --- a/futures-cpupool/README.md +++ b/futures-cpupool/README.md @@ -3,8 +3,7 @@ A library for creating futures representing work happening concurrently on a dedicated thread pool. -[![Build Status](https://travis-ci.org/rust-lang-nursery/futures-rs.svg?branch=master)](https://travis-ci.org/rust-lang-nursery/futures-rs) -[![Build status](https://ci.appveyor.com/api/projects/status/yl5w3ittk4kggfsh?svg=true)](https://ci.appveyor.com/project/rust-lang-nursery/futures-rs) +[![Build Status](https://img.shields.io/github/workflow/status/rust-lang/futures-rs/CI/master)](https://github.com/rust-lang/futures-rs/actions) [Documentation](https://docs.rs/futures-cpupool) From 49ad690a604304c6dfd750f40f4b0809faeb01cf Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Wed, 24 Feb 2021 22:07:55 +0900 Subject: [PATCH 68/70] Release 0.1.31 (#2359) --- Cargo.toml | 2 +- futures01/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 035af4ed9c..01dfd53f02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "futures" -version = "0.1.30" +version = "0.1.31" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" diff --git a/futures01/Cargo.toml b/futures01/Cargo.toml index 2f53fbc47a..05e3e26f6e 100644 --- a/futures01/Cargo.toml +++ b/futures01/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "futures01" -version = "0.1.30" +version = "0.1.31" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/rust-lang-nursery/futures-rs" @@ -16,7 +16,7 @@ the `futures` crate. [dependencies.futures] path = ".." -version = "0.1.30" +version = "0.1.31" default-features = false [features] From 47b876fb67cf91833d04ed07720b82fc599fac93 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sat, 24 Jul 2021 04:20:08 +0900 Subject: [PATCH 69/70] Add publish script --- ci/publish.sh | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100755 ci/publish.sh diff --git a/ci/publish.sh b/ci/publish.sh new file mode 100755 index 0000000000..4d6eae069f --- /dev/null +++ b/ci/publish.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -euo pipefail +IFS=$'\n\t' + +# A list of paths to the crate to be published. +# It will be published in the order listed. +MEMBERS=( + "." + "futures01" +) + +cd "$(cd "$(dirname "$0")" && pwd)"/.. + +set -x + +for i in "${!MEMBERS[@]}"; do + ( + cd "${MEMBERS[${i}]}" + cargo publish + ) + if [[ $((i + 1)) != "${#MEMBERS[@]}" ]]; then + sleep 45 + fi +done From fc680d1cf1074ff1fa29d7b41ac789d37bcdd518 Mon Sep 17 00:00:00 2001 From: Fabio Valentini Date: Mon, 24 Jul 2023 22:51:44 +0200 Subject: [PATCH 70/70] Fix logic bug in stream::Stream::filter_map doctest (#2765) The operator for the modulo and the test were swapped, and using "modulo zero" is undefined and results in an unconditional panic, which is now caught with Rust 1.71 (unconditional_panic lint). --- src/stream/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stream/mod.rs b/src/stream/mod.rs index 2d90362470..9ad4eea45c 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -399,7 +399,7 @@ pub trait Stream { /// /// let (_tx, rx) = mpsc::channel::(1); /// let evens_plus_one = rx.filter_map(|x| { - /// if x % 0 == 2 { + /// if x % 2 == 0 { /// Some(x + 1) /// } else { /// None