Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 13 additions & 15 deletions library/std/src/sys/thread/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 });
Copy link
Member

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_raw part.

Copy link
Member Author

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::from implementation 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.


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
Copy link
Member

Choose a reason for hiding this comment

The 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 DropGuard here?

Copy link
Member

Choose a reason for hiding this comment

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

One thing I notice is that before, libc::pthread_attr_destroy(attr.as_mut_ptr()) is not called for #[cfg(any(target_os = "espidf", target_os = "nuttx"))], but with this change it will/could be. Is that intended?

Copy link
Member Author

Choose a reason for hiding this comment

The 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 DropGuard just makes sure that the attribute structure is destroyed, which is pretty self-explanatory to me.

Copy link
Member

Choose a reason for hiding this comment

The 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 {
Expand Down Expand Up @@ -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"
Expand All @@ -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 {
Copy link
Member

Choose a reason for hiding this comment

The 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 return?

Copy link
Member Author

Choose a reason for hiding this comment

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

As there is an item (the thread_start function) after this, the return is required by the parser.

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 {
Expand Down
Loading