Skip to content

Implement Future for SpawnedTask. #15653

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

Merged
merged 1 commit into from
Apr 10, 2025
Merged

Conversation

ashdnazg
Copy link
Contributor

@ashdnazg ashdnazg commented Apr 9, 2025

Rationale for this change

Allows polling a SpawnedTask, instead of just joining it.
Required for #15654

What changes are included in this PR?

An impl Future was added to SpawnedTask.

In addition, the underlying implementation is changed from JoinSet to a JoinHandle to simplify
the code, as JoinSet doesn't provide any additional benefits.

Documentation was also updated to make clearer what happens when spawn_blocking is called.

Are these changes tested?

Added two tests that verify the behaviour on spawn.

Are there any user-facing changes?

No, although SpawnedTask is public so it does add to the API.

@github-actions github-actions bot added the common Related to common crate label Apr 9, 2025
Copy link
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

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

Thanks @ashdnazg for this contribution.

Implementing future for spawned task seems fine to me but I am not sure about changing to use a join handle (even though implemeting Drop to call abort might be fine)

Can you please add tests showing that any tasks launched by spawned tasks are actually cancelled (both before and after they have started)?

@ashdnazg
Copy link
Contributor Author

ashdnazg commented Apr 9, 2025

@alamb I added two tests for the two scenarios.

Comment on lines -20 to +24
use crate::JoinSet;
use tokio::task::JoinError;
use tokio::task::{JoinError, JoinHandle};
Copy link
Contributor

@gabotechs gabotechs Apr 9, 2025

Choose a reason for hiding this comment

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

I think not using crate::JoinSet is going to render the work being done by @geoffreyclaude in #14547 pretty much useless. Is this right @geoffreyclaude?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we can just add something similar to the spawned task

Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch @gabotechs! Yes, pretty much. For both to be compatible, the same wrapping I implemented over the tokio::task::JoinSet probably needs to be done over tokio::task::spawn and tokio::task::spawn_blocking.

Copy link
Contributor

Choose a reason for hiding this comment

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

@ashdnazg I'll try to add unit tests for the instrumentation feature tomorrow which you could use to validate your change doesn't introduce any regression.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@geoffreyclaude I updated the PR with the tracing functions wrapping the spawned tasks.

Copy link
Contributor

Choose a reason for hiding this comment

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

@ashdnazg Looks good to me! I've opened #15673 to add the required regression tests on my feature.

@ashdnazg
Copy link
Contributor Author

ashdnazg commented Apr 9, 2025

Tests should be more reliable now 🤦.

Copy link
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

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

This looks really nice to me -- thank you @ashdnazg 🙏

Let's wait a while before merging to give @geoffreyclaude and others a chance to review it again if they want.

I went through this one carefully and I think it is 👨‍🍳 👌 very nice.

let mut inner = JoinSet::new();
inner.spawn(task);
#[allow(clippy::disallowed_methods)]
let inner = tokio::task::spawn(trace_future(task));
Copy link
Contributor

Choose a reason for hiding this comment

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

I think some rationale about why it is ok to use the disallowed methods would help. Something like:

Suggested change
let inner = tokio::task::spawn(trace_future(task));
// Ok to use spawn here as SpawnedTask handles aborting/cancelling the task on Drop
let inner = tokio::task::spawn(trace_future(task));

@@ -47,22 +54,20 @@ impl<R: 'static> SpawnedTask<R> {
T: Send + 'static,
R: Send,
{
let mut inner = JoinSet::new();
inner.spawn_blocking(task);
#[allow(clippy::disallowed_methods)]
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
#[allow(clippy::disallowed_methods)]
// Ok to use spawn here as SpawnedTask handles aborting/cancelling the task on Drop
#[allow(clippy::disallowed_methods)]


#[tokio::test]
async fn runtime_shutdown() {
let rt = Runtime::new().unwrap();
#[allow(clippy::async_yields_async)]
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is ok -- the only reason clippy picks this up now is that SpawnedTask is actually a future where previously one had to call join().await -- so TLDR this change makes sense to me (though the test is perhaps somewhat suspect)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you want, I can change the rt.spawn to rt.spawn_blocking and then we get rid of both the unnecessary surrounding async and the clippy thing.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we can add a second test -- I think we should leave the existing test as is as part of ensuring this PR doesn't introduce any regressiosn

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A second test wouldn't test anything new IMO so keeping as is.

@geoffreyclaude
Copy link
Contributor

This looks really nice to me -- thank you @ashdnazg 🙏

Let's wait a while before merging to give @geoffreyclaude and others a chance to review it again if they want.

I went through this one carefully and I think it is 👨‍🍳 👌 very nice.

Thanks! Looks good to me, especially if the new tests pass. @ashdnazg could you please just rebase on top of main so the CI picks up my merged tests?

It allows polling a SpawnedTask, instead of just joining it.

The implementation is changed from `JoinSet` to a `JoinHandle` to simplify
the code, as `JoinSet` doesn't provide any additional benefits.
@ashdnazg
Copy link
Contributor Author

Rebased and added docs per @alamb 's suggestions.

@alamb
Copy link
Contributor

alamb commented Apr 10, 2025

all the tests are green so let's go go go!

@alamb alamb merged commit 18a80f0 into apache:main Apr 10, 2025
27 checks passed
@rluvaton
Copy link
Contributor

@ashdnazg thank you for your contribution, hope to see you contribute again!

nirnayroy pushed a commit to nirnayroy/datafusion that referenced this pull request May 2, 2025
It allows polling a SpawnedTask, instead of just joining it.

The implementation is changed from `JoinSet` to a `JoinHandle` to simplify
the code, as `JoinSet` doesn't provide any additional benefits.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
common Related to common crate
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants