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

Consider documenting that (parts of?) stdlib must not be used before/after main #110708

Closed
Tracked by #110897
dtolnay opened this issue Apr 23, 2023 · 9 comments · Fixed by #115247
Closed
Tracked by #110897

Consider documenting that (parts of?) stdlib must not be used before/after main #110708

dtolnay opened this issue Apr 23, 2023 · 9 comments · Fixed by #115247
Labels
A-docs Area: documentation for any part of the project, including the compiler, standard library, and tools S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). T-libs Relevant to the library team, which will review and decide on the PR/issue.

Comments

@dtolnay
Copy link
Member

dtolnay commented Apr 23, 2023

All existing standard library documentation implicitly assumes that the APIs are being used between the start of a Rust main and end of main.

For example std::thread::current does not document any indication that the function would panic. It does not need to document that, because the function cannot panic, as long as the call occurs within the duration of main.

However it's possible to observe a panic like this:

extern "C" fn get_thread() {
    let _ = std::panic::catch_unwind(std::thread::current);
}

fn main() {
    unsafe { libc::atexit(get_thread) };
}
thread '<unnamed>' panicked at 'use of std::thread::current() is not possible after the thread's local data has been destroyed', library/std/src/thread/mod.rs:733:5
stack backtrace:
   5: core::option::Option<T>::expect
             at /rustc/90743e7298aca107ddaa0c202a4d3604e29bfeb6/library/core/src/option.rs:741:21
   6: std::thread::current
             at /rustc/90743e7298aca107ddaa0c202a4d3604e29bfeb6/library/std/src/thread/mod.rs:733:5
   7: core::ops::function::FnOnce::call_once
             at /rustc/90743e7298aca107ddaa0c202a4d3604e29bfeb6/library/core/src/ops/function.rs:251:5
  11: std::panic::catch_unwind
             at /rustc/90743e7298aca107ddaa0c202a4d3604e29bfeb6/library/std/src/panic.rs:137:14
  12: playground::get_thread
             at ./[src/main.rs:2](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021#):13
  14: exit
  15: __libc_start_main
  16: _start

(Related PR and discussion: #107216)

In general using the standard library from an atexit callback, or before main through a static constructor, is UB: according to #107216 (comment) "we can't really guarantee anything specific happens [...]; at least not in a cross-platform way."

Is this worth calling out centrally as a caveat to all other documentation of the standard library? At the top level of the whole std crate (it would perhaps be more prominent than it deserves), at the module level, or in the Reference? Certainly for std::thread, std::io, std::fs, the expectation users need to have is that nothing in there will work outside of main.

Are there APIs it makes sense to carve out as being permissible outside of main? Stuff like Cell, ManuallyDrop, MaybeUninit, NonNull, etc. We'd maybe need to do research into how constructors and atexit are being used in the wild. For example the inventory crate relies on AtomicPtr, UnsafeCell, and Option to be usable before main: https://github.com/dtolnay/inventory/blob/508cb5918640d05414b0c49843d1c26088df6713/src/lib.rs#L191. It seems obvious that those things should work but there isn't documentation which guarantees it. I assume that makes the inventory crate technically unsound as written.

@dtolnay dtolnay added A-docs Area: documentation for any part of the project, including the compiler, standard library, and tools T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Apr 23, 2023
@ChrisDenton
Copy link
Member

IMHO, core types should work in whatever context but libstd is more dicey. It more explicitly has a runtime or three (e.g. Rust's, libc's and the OS's). So at a minimum anything in the modules env, fs, io, net, os, process, thread, etc should be regarded with suspicion unless documented otherwise.

In summary, my thoughts are:

  • core is fine
  • alloc depends on the allocator used
  • std assumes code is running in main (except for the core and possibly alloc stuff)

@the8472
Copy link
Member

the8472 commented Apr 23, 2023

IMHO, core types should work in whatever context but libstd is more dicey

Core types can still panic and panics do IO.

@ChrisDenton
Copy link
Member

Hm... how much of an issue is that? Panics don't have to do I/O (e.g. if stderr is closed) so if platforms don't support that before or after main then it can be silently skipped, no?

@the8472
Copy link
Member

the8472 commented Apr 23, 2023

But do we do that properly today? And it's not just IO but also panic hooks which in turn can rely on various other parts of std.

@m-ou-se m-ou-se added the I-libs-nominated Nominated for discussion during a libs team meeting. label Apr 24, 2023
@ChrisDenton
Copy link
Member

I think it would be fine to document that e.g. after main they may want to set an empty (or aborting) panic hook if panicking is possible.

I really don't want to be in a situation where the user is in a worse place than if they'd just used no_std.

@the8472
Copy link
Member

the8472 commented Apr 24, 2023

Could we turn panics into immediate aborts after main? That would also avoid the backtrace printing and symbolication.

@Amanieu
Copy link
Member

Amanieu commented May 31, 2023

I looked through the standard library, and thread:current is the only function that has limitation on usage before/after main (in this case, this is only a problem during TLS destruction). This function is also called from thread::park, mpmc and thread::scope, which can therefore panic when called during TLS destruction. mpmc and thread::scoped might be able to be reworked to avoid this, but this is fundamentally a limitation of park since it requires a reference to the current Thread to work.

The stack guard used by the stack overflow handler also accesses the same TLS slot as thread::current, but since the stack bounds don't have a Drop this could be moved to a separate thread_local to properly support stack overflow handling during TLS destruction.

@m-ou-se m-ou-se added S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). and removed I-libs-nominated Nominated for discussion during a libs team meeting. labels Jun 28, 2023
@the8472
Copy link
Member

the8472 commented Jul 12, 2023

If we're going to make a statement at all then imo we should start out with something more cautious than making solid guarantees since this would be a pretty wide-ranging promise.

  • before/after main behavior is not tested. It is best-effort and if someone relies on it they should have their own tests
  • core and alloc are expected to work because they don't touch OS APIs or global state
  • caveat: user code in any hookable API like panic handlers, global allocators, OOM handler or any future hooks. Especially panics affect a lot of code.
    • we might replace hooks after main?
  • most existing parts of std, backtrace and other things interacting with the OS are expected to work but there may be platform-specific edge-cases and the behavior may change in the future due to changing implementation details or new features that depend more runtime state (e.g. a std threadpool or async runtime)
  • then finally a list of known limitations

@the8472
Copy link
Member

the8472 commented Aug 26, 2023

Proposed documentation based on my previous comment: #115247

@dtolnay dtolnay linked a pull request Aug 26, 2023 that will close this issue
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Sep 16, 2023
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Sep 16, 2023
Rollup merge of rust-lang#115247 - the8472:life-before-main, r=dtolnay

Document std limitations before/after main

Solves rust-lang#110708
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-docs Area: documentation for any part of the project, including the compiler, standard library, and tools S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants