-
Notifications
You must be signed in to change notification settings - Fork 13.9k
std: don't leak the thread closure if destroying the thread attributes fails #148026
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,7 @@ | |
| target_os = "aix", | ||
| )))] | ||
| use crate::ffi::CStr; | ||
| use crate::mem::{self, ManuallyDrop}; | ||
| use crate::mem::{self, DropGuard, ManuallyDrop}; | ||
| use crate::num::NonZero; | ||
| #[cfg(all(target_os = "linux", target_env = "gnu"))] | ||
| use crate::sys::weak::dlsym; | ||
|
|
@@ -52,10 +52,13 @@ impl Thread { | |
| name: Option<&str>, | ||
| f: Box<dyn FnOnce()>, | ||
| ) -> io::Result<Thread> { | ||
| let data = Box::into_raw(Box::new(ThreadData { name: name.map(Box::from), f })); | ||
| let mut native: libc::pthread_t = mem::zeroed(); | ||
| let data = Box::new(ThreadData { name: name.map(Box::from), f }); | ||
|
|
||
| let mut attr: mem::MaybeUninit<libc::pthread_attr_t> = mem::MaybeUninit::uninit(); | ||
| assert_eq!(libc::pthread_attr_init(attr.as_mut_ptr()), 0); | ||
| let mut attr = DropGuard::new(&mut attr, |attr| { | ||
| assert_eq!(libc::pthread_attr_destroy(attr.as_mut_ptr()), 0) | ||
| }); | ||
|
Comment on lines
+59
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps you could add a comment here to explain the relevant considerations of using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing I notice is that before, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that is very much intended. The leakage of the current version is a bug. And do you really think that this needs documentation? The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The what is clear enough, but the why could use some expanding. If it was completely straightforward, then this bug wouldn't have needed fixing, so I do think explaining why the DropGuard is needed would be good. |
||
|
|
||
| #[cfg(any(target_os = "espidf", target_os = "nuttx"))] | ||
| if stack > 0 { | ||
|
|
@@ -90,8 +93,6 @@ impl Thread { | |
| // on the stack size, in which case we can only gracefully return | ||
| // an error here. | ||
| if libc::pthread_attr_setstacksize(attr.as_mut_ptr(), stack_size) != 0 { | ||
| assert_eq!(libc::pthread_attr_destroy(attr.as_mut_ptr()), 0); | ||
| drop(Box::from_raw(data)); | ||
| return Err(io::const_error!( | ||
| io::ErrorKind::InvalidInput, | ||
| "invalid stack size" | ||
|
|
@@ -101,19 +102,16 @@ impl Thread { | |
| }; | ||
| } | ||
|
|
||
| let data = Box::into_raw(data); | ||
| let mut native: libc::pthread_t = mem::zeroed(); | ||
| let ret = libc::pthread_create(&mut native, attr.as_ptr(), thread_start, data as *mut _); | ||
| // Note: if the thread creation fails and this assert fails, then p will | ||
| // be leaked. However, an alternative design could cause double-free | ||
| // which is clearly worse. | ||
| assert_eq!(libc::pthread_attr_destroy(attr.as_mut_ptr()), 0); | ||
|
|
||
| return if ret != 0 { | ||
| // The thread failed to start and as a result p was not consumed. Therefore, it is | ||
| // safe to reconstruct the box so that it gets deallocated. | ||
| return if ret == 0 { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to be the last expression of this function, so maybe does not need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is an item (the |
||
| Ok(Thread { id: native }) | ||
| } else { | ||
| // The thread failed to start and as a result `data` was not consumed. | ||
| // Therefore, it is safe to reconstruct the box so that it gets deallocated. | ||
| drop(Box::from_raw(data)); | ||
| Err(io::Error::from_raw_os_error(ret)) | ||
| } else { | ||
| Ok(Thread { id: native }) | ||
| }; | ||
|
|
||
| extern "C" fn thread_start(data: *mut libc::c_void) -> *mut libc::c_void { | ||
|
|
||
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.
Seems like this declaration could be moved down in its entirety. I'm not sure why you only did that for the
Box::into_rawpart.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.
I mistakenly assumed that the
Box::fromimplementation could panic, in which case this should do as little other work as possible. That's not the case, but I'll still leave it here in case something changes in the future.