Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Executors and partly reenable futures crate #1037

Merged
merged 25 commits into from
Jun 22, 2018
Merged

Conversation

MajorBreakfast
Copy link
Contributor

@MajorBreakfast MajorBreakfast commented Jun 12, 2018

This needs careful review. I'm not entirely sure what I did ^^' All I can say is that it builds now and some of the tests run.

Notes:

  • futures-core subcrate does not yet reexport from core
  • executor.spawn_obj(Box::new(future).into()) looks ugly ^^' I think we should introduce an IntoTaskObj trait. Unless there's going to be a simpler API layered on top of course.
  • The doc tests seem to require #![feature(arbitrary_self_types)]. Can this be done? Didn't seem to work for me. Maybe I just did it the wrong way.
  • Various places require Executor to be Sized. Should it become the default, so that we don't need to type it everywhere?
  • The Rust compiler is genius. It reports new errors blazing fast and the error messages are extremely helpful
  • The Task structs should probably be renamed to something like TaskContainer { task_obj, ... }.

@MajorBreakfast
Copy link
Contributor Author

MajorBreakfast commented Jun 12, 2018

Because this PR is so big and requires careful vetting, #1034 should be reviewed and merged first. I will then rebase on top of it after it has landed.

@MajorBreakfast
Copy link
Contributor Author

MajorBreakfast commented Jun 12, 2018

In this recent commit I renamed Task to TaskContainer. Previously we had two things called task:

  • TaskObj: The root future wrapped in a custom trait object
  • Task: The environment { task, exec, wake_handle } that contains it and other stuff

Calling both things "task" would be too confusing.

Alternative to what I implemented:

  • Rename TaskObj to something else. It's only a stopgap solution until dyn can be passed by value after all anyway (see RFC section). One possible good name would be RootFutureObj
  • Rename TaskContainer back to Task

I quite like the alternative. What do you think?

@MajorBreakfast
Copy link
Contributor Author

MajorBreakfast commented Jun 12, 2018

Okay here are some updated thoughts on this:

What about we rename TaskObj to TaskFutureObj:

  • "Task" signifies that it's for a task and should have Output = ()
  • "Future" signifies that it can be polled. TaskFutureObj should therefore also implement Future to make this perfectly clear: Currently it just implements a method call poll_task() (which I already suggested to rename to poll). But we should go the extra mile and make it a real future type. Because it is Unpin executors will be able to call poll_unpin. So, the same signature as today's poll_task() is also available.
  • "Obj" signifies that it's a custom trait object
  • The trait will be called UnsafeTaskFuture

Edit: I no longer believe we should do this.

@cramertj
@aturon

@MajorBreakfast MajorBreakfast mentioned this pull request Jun 12, 2018
13 tasks
@cramertj
Copy link
Member

executor.span_obj(Box::new(future).into()) looks ugly ^^' I think we should introduce an IntoTaskObj trait. Unless there's going to be a simpler API layered on top of course.

The plan was originally to add a method to Context that would take a generic future, box it, and spawn it. However, that impl needs std and Context is defined in core, so I think we should add it via an extension trait here instead.

@cramertj
Copy link
Member

Various places require Executor to be Sized. Should it become the default, so that we don't need to type it everywhere?

No-- the executor inside of task::Context is &mut dyn Executor.

@MajorBreakfast
Copy link
Contributor Author

MajorBreakfast commented Jun 13, 2018

I now think we should stick with calling the top level future "task".

(And rename Task to TaskContainer to avoid confusion)

@MajorBreakfast
Copy link
Contributor Author

I rebased and updated it

struct Task {
fut: Box<Future<Item = (), Error = Never>>,
map: LocalMap,
struct TaskContainer {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment to this that describes what it's for? Something like "wrapper for TaskObj that implements Future with a unit output" would help-- it's less obvious what its purpose is now that it no longer pairs the task with a LocalMap.

N.B. I think that TaskObj should implement Future-- we should add that impl in std. Opened rust-lang/rust#51667

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already made TaskObj implement Future

I‘ll add the comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting rid of TaskContainer entirely. We can use TaskObj directly. Can't do that in ThreadPool, but here it's simple

pub fn run_until<F>(&mut self, mut f: F, exec: &mut Executor) -> Result<F::Item, F::Error>
where F: Future
pub fn run_until<F, Exec>(&mut self, mut f: F, exec: &mut Exec) -> F::Output
where F: Future + Unpin, Exec: Executor + Sized
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Unpin bound isn't needed. Can you add this macro to futures-core and use it instead?

@@ -202,14 +197,14 @@ lazy_static! {
///
/// Use a [`LocalPool`](LocalPool) if you need finer-grained control over
/// spawned tasks.
pub fn block_on<F: Future>(f: F) -> Result<F::Item, F::Error> {
pub fn block_on<F: Future + Unpin>(f: F) -> F::Output {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rm Unpin

type Item = ();
type Error = Never;
fn poll(&mut self, cx: &mut Context) -> Poll<(), Never> {
impl<F: Future<Output = ()> + Unpin + Send + 'static> Future for Spawn<F> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Unpin bound isn't needed.

@@ -131,6 +132,7 @@ struct MySender<F, T> {
tx: Option<Sender<T>>,
keep_running_flag: Arc<AtomicBool>,
}
impl<F, T> Unpin for MySender<F, T> {} // ToDo: May I do this?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would only add it where F: Unpin, and I would remove the Unpin bound on the Future impl. You will want to use the unsafe_pinned_field macro to create a safe function for accessing PinMut<F>

@@ -96,23 +96,22 @@ impl ThreadPool {
///
/// Note that the function will return when the provided future completes,
/// even if some of the tasks it spawned are still running.
pub fn run<F: Future>(&mut self, f: F) -> Result<F::Item, F::Error> {
pub fn run<F: Future + Unpin>(&mut self, f: F) -> F::Output {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rm Unpin

@@ -9,7 +9,7 @@ use super::future::Either;
use core::mem::PinMut;

mod close;
mod fanout;
// mod fanout;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is fanout commented out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the tests fail. There is no more iter_ok() for instance. Fixing now...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No this is a rabbit hole... it requires converting future_util/src/stream/forwards.rs and probably various other things

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you leave fanout, but disable the tests? I'd like to make sure I know what code still needs fixing to work on 0.3 (regardless of tests) and what still needs fixing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Tests are disabled now.

impl<T: Sync> Sync for FuturesUnordered<T> {}
unsafe impl<T: Send> Send for FuturesUnordered<T> {}
unsafe impl<T: Sync> Sync for FuturesUnordered<T> {}
impl<T: Unpin> Unpin for FuturesUnordered<T> {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FuturesUnordered should be Unpin regardless of T.

@@ -99,12 +103,12 @@ enum Dequeue<T> {
}

impl<T> FuturesUnordered<T>
where T: Future,
where T: Future + Unpin
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and elsewhere, FuturesUnordered shouldn't require that the future is Unpin, since it's allocating it. Instead it should track usage to ensure the future isn't moved.

Waker::new(hide_lt(ptr))
let ptr: Arc<Node<T>> = handle.0.clone();
let ptr: *mut ArcNode<T> = mem::transmute(ptr);
let ptr = mem::transmute(ptr as *mut UnsafeWake); // Hide lifetime
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you supply manual type arguments to mem::transmute? It's hard to tell what it's transmuting between here.

@MajorBreakfast
Copy link
Contributor Author

I've split FuturesUnordered into multiple files. Whenever a file is large I spend most of my time scrolling and trying to find stuff instead of getting things done ^^'

@MajorBreakfast
Copy link
Contributor Author

@cramertj To make reviewing easier, look at the "Future + !Unpin support for FuturesUnordered" commit directly. I split the file in the preceding commit. This PR seems a bit bigger than it really is (it's still big of course)

// At this point, it may be worth yielding the thread &
// spinning a few times... but for now, just yield using the
// task system.
cx.waker().wake();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you can use local_waker().wake() here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

use super::node::Node;

#[derive(Debug)]
/// Mutable iterator over all futures in the unordered set.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subtle: this is unsound now because it gives mutable access to the futures allowing them to be mem::replaced, although they may have been polled. This should either yield PinMut<F> or iter_mut should be changed to include an F: Unpin bound. Perhaps offer both as iter_mut and iter_pin_mut?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Done.

let ptr = mem::transmute::<Arc<Node<T>>, *mut ArcNode<T>>(ptr);
let ptr = ptr as *mut UnsafeWake;
// Hide lifetime
let ptr = mem::transmute::<*mut UnsafeWake, *mut UnsafeWake>(ptr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a no-op?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It hides the lifetime. Before my PR it was in a function called hide_lt. Otherwise we get:

error[E0310]: the parameter type `T` may not live long enough
  --> futures-util/src/stream/futures_unordered/node.rs:96:23
   |
91 | impl<'a, T> From<NodeToHandle<'a, T>> for LocalWaker {
   |          - help: consider adding an explicit lifetime bound `T: 'static`...
...
96 |             let ptr = ptr as *mut UnsafeWake;
   |                       ^^^
   |
note: ...so that the type `stream::futures_unordered::node::ArcNode<T>` will meet its required lifetime bounds

unsafe impl<T> Send for ArcNode<T> {}
unsafe impl<T> Sync for ArcNode<T> {}

unsafe impl<T> UnsafeWake for ArcNode<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This impl looks like it could be written as an impl of the Wake trait for Node<T>, which would make the From impls above trivial.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't because the UnsafeWake trait is used to hide the lifetime. I've added a bunch of comments in my next commit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, thanks!


#[doc(hidden)]
impl<'a, T> From<NodeToHandle<'a, T>> for LocalWaker {
fn from(handle: NodeToHandle<'a, T>) -> LocalWaker {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and the function below are the same modulo the call to either LocalWaker::new or Waker::new-- can you break out the shared bits into a function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


#[derive(Debug)]
/// Mutable iterator over all futures in the unordered set.
pub struct IterPinMut<'a, F: 'a> {
Copy link
Member

@cramertj cramertj Jun 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than duplicating the logic between IterPinMut and IterMut, I'd implement IterMut in terms of IterPinMut. The Unpin bound will make it safe to go from PinMut of the yielded value to &mut of the value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -155,6 +157,15 @@ impl<T> FuturesUnordered<T> {
}
}

/// Returns an iterator that allows modifying each future in the set.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add an Unpin bound to the iter_mut method itself? It could be confusing to have iter_mut be callable but the result not be usable as an iterator.

@@ -10,7 +10,7 @@ pub struct IterMut<'a, F: 'a> {
pub(super) _marker: PhantomData<&'a mut FuturesUnordered<F>>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add an Unpin bound to the IterMut type itself, too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

self.len -= 1;
Some(future)
}
PinMut::new(&mut self.0).next().map(|f| unsafe { PinMut::get_mut(f) })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the PinMut::new here? You should be able to remove that and have self.0.next().map(|f| unsafe { PinMut::get_mut(f) })

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah ^^'

@cramertj
Copy link
Member

@MajorBreakfast Looks great! Thanks for being so patient with all the changes.

@cramertj cramertj merged commit 7a67744 into rust-lang:0.3 Jun 22, 2018
@MajorBreakfast
Copy link
Contributor Author

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants