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

Mark JoinHandle with #[must_use] #48830

Closed
wants to merge 5 commits into from
Closed

Mark JoinHandle with #[must_use] #48830

wants to merge 5 commits into from

Conversation

ghost
Copy link

@ghost ghost commented Mar 7, 2018

This PR adds a #[must_use] attribute to JoinHandle.

The change may introduce a considerable number of warnings in the Rust code, but that's a good thing! Note that warnings are still easy to get around by writing let _ = std::thread::spawn(...), but at least they make the user think twice about forgetting to join the thread.

Generally speaking, spawning a thread with no intention of ever joining should be discouraged since it may lead to very subtle bugs. See #48820 for an explanation.

Closes #48820

cc @matklad

@Centril Centril added C-enhancement Category: An issue proposing an enhancement or a PR with one. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Mar 8, 2018
@ghost
Copy link
Author

ghost commented Mar 8, 2018

An interesting comment I found in src/liballoc/arc.rs, which may be yet another reason for this change:

// Note that we **do not** run these tests here. The windows builders get super
// unhappy if a thread outlives the main thread and then exits at the same time
// (something deadlocks) so we just avoid this entirely by not running these
// tests.
/// ```no_run
/// use std::sync::Arc;
/// use std::thread;
///
/// let five = Arc::new(5);
///
/// for _ in 0..10 {
///     let five = Arc::clone(&five);
///
///     thread::spawn(move || {
///         println!("{:?}", five);
///     });
/// }
/// ```

@@ -1267,6 +1267,7 @@ impl<T> JoinInner<T> {
/// [`Clone`]: ../../std/clone/trait.Clone.html
/// [`thread::spawn`]: fn.spawn.html
/// [`thread::Builder::spawn`]: struct.Builder.html#method.spawn
#[must_use]
Copy link
Member

Choose a reason for hiding this comment

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

It might be a good idea to add a short message, describing the reason for must_use, like

#[must_use = "it is better to explicitly join the spawned thread, otherwise it might be terminated abruptly, without running destructors"]

Copy link
Author

Choose a reason for hiding this comment

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

Done.

@@ -36,9 +36,7 @@
//! non-zero exit code.
//!
//! When the main thread of a Rust program terminates, the entire program shuts
//! down, even if other threads are still running. However, this module provides
//! convenient facilities for automatically waiting for the termination of a
//! child thread (i.e., join).
Copy link
Author

Choose a reason for hiding this comment

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

Written in 2014. I wish it were still true.

@pietroalbini pietroalbini added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Mar 8, 2018
@pietroalbini
Copy link
Member

Highfive failed to assign a reviewer, @Kimundi?

@matklad
Copy link
Member

matklad commented Mar 8, 2018

I think we might want to cc @rust-lang/libs here: there will be a lot of warnings because of this, so it's better to discuss this at the team level just in case I think?

@Kimundi
Copy link
Member

Kimundi commented Mar 8, 2018

Yeah, this seems like it might have a lot of impact.

@alexcrichton
Copy link
Member

Thanks for the PR @stjepang!

I'd personally think, however, that we would not want to do this. I think it's a laudable effort to encourage authors to think about shutdown and how to join threads, but there's a few reasons that I think we won't want to take this strategy:

  • This seems pretty likely to cause a lot of warnings on stable. In the sense that I'm sure lots of crates, tests, examples, etc, all use thread::spawn without worrying about the result.
  • I'm personally not a fan of lints where they're targeted at "more than half the time you don't want this, but if you do do this extra thing to ignore it". In other words I think there are legitimate use cases for daemonizing the thread and not worrying about its result, but now all of them will be required to do something like let _ = ... or drop(...) to avoid getting this warning. That ergonomic hit is one I'm personally not a huge fan of, but I know others feel quite differently!

I wonder though if there's perhaps another route we can take for warning about this? Not much comes to mind though :(

@ghost
Copy link
Author

ghost commented Mar 8, 2018

@alexcrichton

I'd say the key issue here is that thread::spawn is the easiest way of spawning threads, but not the best one for casual use. Manually calling .join().unwrap() is a chore and easy to forget, which makes thread::spawn a potential footgun.

Maybe we don't need to "fix" thread::spawn - maybe we're just lacking an alternative? I actually like the old JoinGuard, which was removed during the leakpocalypse. Maybe we should bring it back, require F: 'static, and call the method guarded rather than scoped? An example of how it might be used:

let guard = thread::guarded(move || {
    // ...
});

// Join the thread.
// Panics if the thread ended with a panic.
drop(guard);

Also, I find the fact that the documentation says the following amusing:

//! When the main thread of a Rust program terminates, the entire program shuts
//! down, even if other threads are still running. However, this module provides
//! convenient facilities for automatically waiting for the termination of a
//! child thread (i.e., join).

We're being warned than daemonized threads will not be cleanly shut down, but fret not - we're covered by convenient facilities for automatic joining. Except that's a lie - there is nothing automatic about calling .join(). Clearly, something is missing here (the comment was written in the old days of JoinGuard). :)

@alexcrichton
Copy link
Member

Having an alternative seems plausible to me!

@bors
Copy link
Contributor

bors commented Mar 11, 2018

☔ The latest upstream changes (presumably #48691) made this pull request unmergeable. Please resolve the merge conflicts.

@shepmaster
Copy link
Member

Where does this PR stand? Should it be closed in favor of a new API addition like the proposed thread::guarded? Does the libs team want to weigh in on the current PR and/or the proposed path before @stjepang starts additional work?

@shepmaster shepmaster added S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). I-nominated and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 23, 2018
@shepmaster
Copy link
Member

Does the libs team want to weigh in

Well, now you gotta talk about it at your meeting, so there 😝

@pietroalbini
Copy link
Member

Ping from triage @rust-lang/libs! What's the decision on this?

@alexcrichton
Copy link
Member

We'll have triage this wednesday and I'll make sure we discuss it there

@sfackler
Copy link
Member

We discussed this in triage and like the approach of a separate function which has a #[must_use] guard that joins on drop rather than detaching.

@SimonSapin
Copy link
Contributor

Isn’t #[must_use] mostly useful for types that don’t do anything on drop?

@sfackler
Copy link
Member

Mostly but not entirely! In this case, code like

thread::spawn_attached(|| do_expensive_work());

Is totally broken - we're presumably doing this work in a separate thread because we want to do other stuff while it's running, but the handle is going to join when it's dropped immediately.

@pietroalbini pietroalbini added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Apr 23, 2018
@pietroalbini pietroalbini added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed C-enhancement Category: An issue proposing an enhancement or a PR with one. S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). I-nominated S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 23, 2018
@pietroalbini
Copy link
Member

If I understood this correctly the PR needs to change? Marking as waiting on author @stjepang.

@ghost
Copy link
Author

ghost commented Apr 30, 2018

Let's close this PR since it's definitely not the direction we're going to take.

The alternative is to add a new function for spawning threads that returns a join-on-drop guard.

@Veetaha
Copy link
Contributor

Veetaha commented May 1, 2020

std::thread::guarded/scoped() (with 'static closure lifetime limitations of course) is very much needed, would there be any interest in merging a PR with this one, @pietroalbini @alexcrichton?

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

std::thread::JoinHandle should be marked with #[must_use]
10 participants