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

Document the behaviour of RUST_MIN_STACK=0 #135178

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Gankra
Copy link
Contributor

@Gankra Gankra commented Jan 7, 2025

While digging through the code I noticed it went out of its way to distinguish RUST_MIN_STACK=0 from the sentinel value of 0, with no justification given.

Then I did some searching and discovered at least one person knew that it made Rust use the platform default stack size to work under valgrind better. 0

This took me deeper into the guts of the platform-specific thread code where indeed i saw comments and/or explicit checks about 0 being a special sentinel to discard the 2MiB default in favour of "whatever the platform thread API wants to pick".

These docs are already heavily caveated with "this is platform-specific" so I see no harm in specifying this further-platform-specific behaviour.

While digging through the code I noticed it went out of its way to distinguish RUST_MIN_STACK=0 from the sentinel value of 0, with no justification given. 

Then I did some searching and discovered at least one person knew that it made Rust use the platform default stack size to work under valgrind better. [0]

This took me deeper into the guts of the platform-specific thread code where indeed i saw comments and/or explicit checks about 0 being a special sentinel to discard the 2MiB default in favour of "whatever the platform thread API wants to pick".

These docs are already heavily caveated with "this is platform-specific" so I see no harm in specifying this further-platform-specific behaviour.

[0]: https://nest.pijul.com/pijul/pijul/discussions/498
@rustbot
Copy link
Collaborator

rustbot commented Jan 7, 2025

r? @joboet

rustbot has assigned @joboet.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jan 7, 2025
@Gankra
Copy link
Contributor Author

Gankra commented Jan 7, 2025

Instances of platform-specific code referencing this behaviour as intended/supported:

#[cfg(target_os = "espidf")]
if stack > 0 {
// Only set the stack if a non-zero value is passed
// 0 is used as an indication that the default stack size configured in the ESP-IDF menuconfig system should be used
assert_eq!(
libc::pthread_attr_setstacksize(&mut attr, cmp::max(stack, min_stack_size(&attr))),
0
);
}

// CreateThread rounds up values for the stack size to the nearest page size (at least 4kb).
// If a value of zero is given then the default stack size is used instead.
// SAFETY: `thread_start` has the right ABI for a thread's entry point.
// `p` is simply passed through to the new thread without being touched.
let ret = unsafe {
let ret = c::CreateThread(
ptr::null_mut(),
stack,
Some(thread_start),
p as *mut _,
c::STACK_SIZE_PARAM_IS_A_RESERVATION,
ptr::null_mut(),
);
HandleOrNull::from_raw_handle(ret)
};

@@ -125,9 +125,12 @@
//! ## Stack size
//!
//! The default stack size is platform-dependent and subject to change.
//! Currently, it is 2 MiB on all Tier-1 platforms.
//! Currently, it is 2 MiB on all Tier-1 platforms to make programs behave more consistently
//! cross-platform (notably Windows would otherwise default to 1MiB stacks).
Copy link
Member

Choose a reason for hiding this comment

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

Hm, maybe this is a nitpick but the Windows comment isn't strictly accurate. The default size is linker-defined, which does (on MSVC at least) use 1MiB if not otherwise set but it can be different. So saying 0 means 1MiB on Windows is not necessarily true even now and in the future Rust might opt to use a different value by default.

That said, I'm not sure it's worth going on about /stack here, Maybe it's acceptable to say "1MiB" in a lies to children kind of way.

Copy link
Contributor Author

@Gankra Gankra Jan 7, 2025

Choose a reason for hiding this comment

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

Yeah that's basically the emotional arc I went through. I was sorely tempted to specify "usually" or something.

Copy link
Member

Choose a reason for hiding this comment

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

Fair. I'm a little bit uncomfortable with std docs that aren't technically correct but it is truthish and it does make sense to mention the 1MiB somewhere as it's something that does trip up cross-platform code (admittedly mostly for the main thread rather than spawning new ones but still).

@Gankra
Copy link
Contributor Author

Gankra commented Jan 7, 2025

Checking some stuff more closely I think what I'm documenting is wrong on linux/macos, where we don't seem to check for 0 and instead do max(value_we_want, basically_16kb), always overloading the platform default. This is made very confusing by pthread_attr_setstacksize specifying it sets the "minimum size" while pthread_create makes it clear that is the size, and in fact can be used to make stacks smaller than the default (made even more confusing by the fact that RLIMIT_STACK specifically claims to set the "maximum size" when really it seems to set the default?)

Note that in the following code min_stack_size is not referring to any of the pthread values described above but is basically libc::PTHREAD_STACK_MIN + POSSIBLE_SMALL_MARGINS_FOR_THREAD_STATE where libc::PTHREAD_STACK_MIN is 16kb.

#[cfg(not(target_os = "espidf"))]
{
let stack_size = cmp::max(stack, min_stack_size(&attr));
match libc::pthread_attr_setstacksize(&mut attr, stack_size) {
0 => {}
n => {
assert_eq!(n, libc::EINVAL);
// EINVAL means |stack_size| is either too small or not a
// multiple of the system page size. Because it's definitely
// >= PTHREAD_STACK_MIN, it must be an alignment issue.
// Round up to the nearest page and try again.
let page_size = os::page_size();
let stack_size =
(stack_size + page_size - 1) & (-(page_size as isize - 1) as usize - 1);
assert_eq!(libc::pthread_attr_setstacksize(&mut attr, stack_size), 0);
}
};
}

@Gankra
Copy link
Contributor Author

Gankra commented Jan 7, 2025

I guess it's keeping with the spirit of "it's platform-specific" to have 0 get handled so inconsistently but it's Weird that it's like... Almost A Convention but Not.

@joboet
Copy link
Member

joboet commented Jan 15, 2025

r? libs-api

@rustbot rustbot added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Jan 15, 2025
@rustbot rustbot assigned Amanieu and unassigned joboet Jan 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. 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.

None yet

5 participants