Skip to content

std: use _exit for process::exit - #165125

Open
joboet wants to merge 2 commits into
rust-lang:mainfrom
joboet:_exit
Open

joboet wants to merge 2 commits into
rust-lang:mainfrom
joboet:_exit

Conversation

@joboet

@joboet joboet commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

View all comments

Fixes #126600
Fixes #164243
Fixes #164273

This fixes three issues related to process::exit at once: C's exit is not MT-safe, and there is no entirely sound way to make it so, and it calls destructors for TLS variables (and C++ static variables) that may be currently in use, leading to double-frees and similar undefined behaviour. The POSIX-defined _exit function on the other hand immediately terminates the process without running any handlers and hence does not have any of these problems, so this PR switches process::exit to use it instead of exit.

While skipping atexit handlers is a bit unfortunate, it shouldn't come as that big a surprise to users of process::exit; that function is already documented not to perform any stack cleanup. It's a change in behaviour, though, so this will need FCP.

@joboet joboet added the needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. label Oct 29, 2026
@rustbot

rustbot commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

miri is developed in its own repository. If the Miri part of this change can be broken out, consider making this change to rust-lang/miri instead. However, if Miri needs adjusting for rustc changes, just ignore this message.

cc @rust-lang/miri

@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 Oct 29, 2026
@rustbot

rustbot commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

r? @ChrisDenton

rustbot has assigned @ChrisDenton.
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

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: @ChrisDenton, libs
  • @ChrisDenton, libs expanded to 37 candidates
  • Random selection from ChrisDenton, JohnTitor, Mark-Simulacrum, clarfonthey

@rust-log-analyzer

This comment has been minimized.

@rustbot

rustbot commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@RalfJung

RalfJung commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

C's exit is not MT-safe, and there is no entirely sound way to make it so

This was discussed in the past and the conclusion was that adding a lock on our side is good enough for now; musl and glibc have also added locks on their side.

it calls destructors for TLS variables (and C++ static variables) that may be currently in use,

It should generally work like "return from main", I just recently finally fixed that in Miri.^^ (Some of those tests I added will probably fail now.)
But yeah you might be right about the soundness problem -- when we return from main we know that we are not inside with with block of any TLS, so that's a different story soundness-wise.

@RalfJung

Copy link
Copy Markdown
Member

The one alternative I can think of to fix the soundness problem is to mark TLS variables where a with closure is going on, and skip their destructors.

@joboet

joboet commented Oct 29, 2026

Copy link
Copy Markdown
Member Author

On every platform but macOS we can also fix this by using the pthread-TLS-key-based destructor registration – pthread TLS destructors aren't run on exit, only C++ TLS ones are.

As an aside, I'm actually very surprised that C++ doesn't have the same problem. I don't really know the language, but I'd imagine there are a ton of move constructors (or is it assignment operators?) that are unsound for a similar reason.

@rust-log-analyzer

Copy link
Copy Markdown
Collaborator

The job test-x86_64-gnu-miri failed! Check out the build log: (web) (plain enhanced) (plain)

Click to see the possible cause of the failure (guessed by this bot)
tests/fail/tree_borrows/wildcard/subtree_internal_relatedness.rs ... ok
tests/fail/tree_borrows/wildcard/subtree_internal_relatedness_wildcard.rs ... ok

FAILED TEST: tests/fail/concurrency/yield-before-exit.rs (revision `with_exit`)
command: MIRI_ENV_VAR_TEST="0" MIRI_TEMP="/tmp/miri-uitest-ngUhO0" RUST_BACKTRACE="1" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/miri" "--error-format=json" "-Dwarnings" "-Dunused" "-Ainternal_features" "-Zui-testing" "--sysroot=/checkout/obj/build/x86_64-unknown-linux-gnu/miri-sysroot" "--out-dir" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/tmp/miri_ui/0/tests/fail/concurrency" "tests/fail/concurrency/yield-before-exit.rs" "-Zmiri-fixed-schedule" "--edition" "2021" "--cfg=with_exit" "-Cextra-filename=with_exit"

error: test got exit status: 0, but expected 1
 = note: compilation succeeded, but was expected to fail

error: no output was emitted
---

Location:
   /cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ui_test-0.30.7/src/lib.rs:365

Backtrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it.
Run with RUST_BACKTRACE=full to include source snippets.
error: test failed, to rerun pass `--test ui`

Caused by:
  process didn't exit successfully: `/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/build/miri/f6f04c4e7359cfd8/out/ui-f6f04c4e7359cfd8` (exit status: 1)
Bootstrap failed while executing `test --stage 2 miri cargo-miri`
Currently active steps:
test::Miri { target: x86_64-unknown-linux-gnu } at src/bootstrap/src/core/build_steps/test.rs:716
Command `/checkout/obj/build/x86_64-unknown-linux-gnu/stage0/bin/cargo test --target x86_64-unknown-linux-gnu -Zbinary-dep-depinfo -j 4 -Zroot-dir=/checkout -Zembed-metadata=no --locked --color=always --profile=release --manifest-path /checkout/src/tools/miri/Cargo.toml -- [workdir=/checkout]` failed with exit code 1
Created at: src/bootstrap/src/core/build_steps/tool.rs:208:21
Executed at: src/bootstrap/src/core/build_steps/test.rs:790:19

Command has failed. Rerun with -v to see more details.
Build completed unsuccessfully in 0:32:29
  local time: Thu Oct 29 13:32:25 UTC 2026
  network time: Thu, 29 Oct 2026 13:32:26 GMT
##[error]Process completed with exit code 1.
##[group]Run echo "disk usage:"

@maxdexh

maxdexh commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

Does this also change the behavior of main? If not (which I'm assuming), I feel like its behavior should be documented separately, because this is really confusing.

As an aside, I'm actually very surprised that C++ doesn't have the same problem. I don't really know the language, but I'd imagine there are a ton of move constructors (or is it assignment operators?) that are unsound for a similar reason.

I think the C++ approach is to say "if you call std::exit during a TLS destructor, it's your own fault". The function isn't even thread-safe ^^

}
}
}
target_os = "windows" => unsafe { crate::sys::pal::c::ExitProcess(code as u32) },

@RalfJung RalfJung Oct 29, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AFAIK this still calls destructors on Windows

View changes since the review

@RalfJung RalfJung Oct 29, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please make the test call libc::exit directly so that we keep testing the same codepath as before here (ensuring that atexit handlers get invoked on exit).

View changes since the review

@RalfJung RalfJung Oct 29, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Seems worth a comment in the test that exit does not invoke the destructors so we will not get output in that case.

View changes since the review

)?;
} else {
// Stop interpreter loop.
throw_machine_stop!(TerminationInfo::Exit {

@RalfJung RalfJung Oct 29, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should still do the "yield 100 times" thing here that exit does, by invoking replace_active_thread_stack. Looks like ExitType::ExitCalled needs a field saying whether atexit handlers should be invoked.

I can handle that if you prefer.

View changes since the review

@RalfJung

Copy link
Copy Markdown
Member

On every platform but macOS we can also fix this by using the pthread-TLS-key-based destructor registration – pthread TLS destructors aren't run on exit, only C++ TLS ones are.

That's the same fix as what your PR does, just different in implementation details. I was thinking about a fix that actually behaves differently.

@joboet joboet added the I-libs-nominated Nominated for discussion during a libs team meeting. label Nov 1, 2026
@rust-bors

rust-bors Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

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

@nia-e

nia-e commented Nov 3, 2026

Copy link
Copy Markdown
Member

Per today's libs meeting, we'd like to look into the option of using pthread TLS - there's probably a good reason not to use it, but it's worth double-checking. Failing that, we're in favour of merging this PR and possibly exposing an unsafe exit function that also calls atexit handlers.

Some other options were raised (notably, registering an atexit handler ourselves which during the exit call registers another atexit handler which crashes if called... though I strongly doubt these kinds of hacks will be portable ^^) but this is likely the best we can do for now. We shouldn't guarantee behaviour either way though to keep ourselves open to something better in the future.

@nia-e nia-e added I-libs-radar Libs issues that are tracked on the team's radar. and removed I-libs-nominated Nominated for discussion during a libs team meeting. labels Nov 3, 2026
@nia-e

This comment was marked as outdated.

@joshtriplett

Copy link
Copy Markdown
Member

I looked into this, and per #10312 at least, it looks like we switched away from pthread TLS for multiple reasons: performance (pthread TLS is slower, especially for repeated accesses) and limited slots, at the very least.

So, I don't think we should switch back to pthread TLS.

Proposal: let's merge this PR, and (per suggestion from @clarfonthey) consider adding an unstable exit function that does call exit.

@nia-e

nia-e commented Nov 3, 2026

Copy link
Copy Markdown
Member

fair enough. with that in mind:

@rfcbot fcp merge libs

@rust-rfcbot

rust-rfcbot commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

@nia-e has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

cc @rust-lang/libs-ping: FCP proposed for libs, please feel free to register concerns.
See this document for info about what commands tagged team members can give me.

@rust-rfcbot rust-rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. and removed needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. labels Nov 3, 2026
@ChrisDenton

ChrisDenton commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Since it was brought up in the meeting I'll also add that Windows doesn't have the Unix unsafety issue (#126600, #164243) because it it is thread safe but it can still deadlock (#164273) due to Windows having a lock and immediately terminating other threads.

And of course every platform has the issue that locals aren't dropped (which can be surprising if they have intentional side-effects).

@RalfJung

RalfJung commented Nov 3, 2026

Copy link
Copy Markdown
Member

This is still unsound on Windows though as the destructor gets &mut access to a thread-local that the thread may currently have & access to.

@ChrisDenton

Copy link
Copy Markdown
Member

Oh hm, yeah, that is still an issue.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. I-libs-radar Libs issues that are tracked on the team's radar. proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. 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.

Projects

None yet

9 participants