-
Notifications
You must be signed in to change notification settings - Fork 88
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
turn the futures-unordered footgun into a status quo story #172
turn the futures-unordered footgun into a status quo story #172
Conversation
src/vision/status_quo/barbara_battles_with_futures_unordered.md
Outdated
Show resolved
Hide resolved
src/vision/status_quo/barbara_battles_with_futures_unordered.md
Outdated
Show resolved
Hide resolved
In general, this is a really nasty footgun in the design of async fn do_work(database: &Database) {
let work = do_select(database, FIND_WORK_QUERY).await?;
stream::iter(work)
.map(|item| async move {
let work_item = do_select(database, work_from_item(item)).await;
process_work_item(database, work_item).await;
})
.buffered(5)
.for_each(|()| std::future::ready(()))
.await;
} The above could be further rewritten to use To not count them in the upper limit of five, a further way to write it would be: async fn do_work(database: &Database) {
let work = do_select(database, FIND_WORK_QUERY).await?;
let selects = stream::iter(work)
.map(|item| do_select(database, work_from_item(item)))
.buffered(5)
.fuse();
tokio::pin!(selects);
let mut results = FuturesUnordered::new();
loop {
tokio::select! {
Some(work_item) = selects.next() => {
results.push(process_work_item(database, work_item));
},
Some(()) = results.next() => { /* do nothing */ },
else => break,
}
}
} I'm not sure if there is a simpler way to write the above. |
Co-authored-by: Alice Ryhl <alice@ryhl.io>
Ah, that's interesting. I had not considered the I was imagining something like: join!(
selects.map(|work_item| async move { results.push(process_work_item(database, work_item)) }).complete(),
results.complete(),
); where impl<T: Stream> StreamExt for T {
async fn complete(self) {
while let Some(_) = self.next().await { }
}
} |
I mean, my
You can do that with |
| ----------- ...and is required to live as long as `'static` here | ||
``` | ||
|
||
"Ah, right," she says, "spawned tasks can't use borrowed data. I wish I had [rayon] or the scoped threads from [crossbeam]." (What Barbara doesn't realize is that spawning wouldn't actually have fixed her problem anyway: the `for_each` combinator would have awaited the resulting `JoinHandle` and hence it would have blocked... but she could have tweaked her program to fix that if she had gotten that far.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's true that the spawn
used here would not have fixed her problem, but spawning would have fixed her problem if she had put the spawn around do_select
rather than around process_work_item
. (Ignoring any issues regarding 'static
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I should note that in the FAQ,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix we use most commonly is described below - we spawn inside do_select
to ensure that the work done there can complete.
Right, I know they're equivalent, I was just playing around with what it might look like to not "open code" it. But you're right about the borrow checker interactions, good point. |
Co-authored-by: Alice Ryhl <alice@ryhl.io>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise, LGTM. Thanks for writing this for me!
| ----------- ...and is required to live as long as `'static` here | ||
``` | ||
|
||
"Ah, right," she says, "spawned tasks can't use borrowed data. I wish I had [rayon] or the scoped threads from [crossbeam]." (What Barbara doesn't realize is that spawning wouldn't actually have fixed her problem anyway: the `for_each` combinator would have awaited the resulting `JoinHandle` and hence it would have blocked... but she could have tweaked her program to fix that if she had gotten that far.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix we use most commonly is described below - we spawn inside do_select
to ensure that the work done there can complete.
Co-authored-by: Simon Farnsworth <simon@farnz.org.uk> Co-authored-by: Ryan Levick <rylev@users.noreply.github.com>
I incorporated @farnz's feedback, which I think really improved the story, thanks again for that. It's much more precise now. |
Closes #131