Skip to content

Conversation

joboet
Copy link
Member

@joboet joboet commented Sep 13, 2025

Fixes #37440 (for good).

This fixes two issues with Condvar::wait_timeout on macOS:

Apple's implementation of pthread_cond_timedwait internally converts the absolute timeout to a relative one, measured in nanoseconds, but fails to consider overflow when doing so. This results in wait_timeout returning much earlier than anticipated when passed a duration that is slightly longer than u64::MAX nanoseconds (around 584 years). The existing clamping introduced by #42604 to address #37440 unfortunately used a maximum duration of 1000 years and thus still runs into the bug when run on older macOS versions (or with PTHREAD_MUTEX_USE_ULOCK set to a value other than "1"). See #37440 (comment) for context.

Reducing the maximum duration alone however would not be enough to make the implementation completely correct. As macOS does not support pthread_condattr_setclock, the deadline passed to pthread_cond_timedwait is measured against the wall-time clock. std currently calculates the deadline by retrieving the current time and adding the duration to that, only for macOS to convert the deadline back to a relative duration by retrieving the current time itself (this conversion is performed before the aforementioned problematic one). Thus, if the wall-time clock is adjusted between the std lookup and the system lookup, the relative duration could have changed, possibly even to a value larger than $2^{64}\ \textrm{ns}$. Luckily however, macOS supports the non-standard, tongue-twisting pthread_cond_timedwait_relative_np function which avoids the wall-clock-time roundtrip by taking a relative timeout. Even apart from that, this function is perfectly suited for std's purposes: it is public (albeit badly-documented) API, available since macOS 10.4 (that's way below our minimum of 10.12) and completely resilient against wall-time changes as all timeouts are measured against the monotonic clock inside the kernel.

Thus, this PR switches Condvar::wait_timeout to pthread_cond_timedwait_relative_np, making sure to clamp the duration to a maximum of $2^{64} - 1 \ \textrm{ns}$. I've added a miri shim as well, so the only thing missing is a definition of pthread_cond_timedwait_relative_np inside libc.

@rustbot
Copy link
Collaborator

rustbot commented Sep 13, 2025

The Miri subtree was changed

cc @rust-lang/miri

@rustbot rustbot added O-unix Operating system: Unix-like 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 Sep 13, 2025
@rustbot
Copy link
Collaborator

rustbot commented Sep 13, 2025

r? @ibraheemdev

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

let (clock, anchor) = if relative {
// `pthread_cond_timedwait_relative_np` always measures time against the
// monotonic clock, regardless of the condvar clock.
(TimeoutClock::Monotonic, TimeoutAnchor::Relative)
Copy link
Member

Choose a reason for hiding this comment

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

So the argument named relative really is very specific for this macos operation, and also swaps out the clock. Please give it a name that more accurately reflects what it does (e.g. macos_relative_np), and document it in the doc comment for this function.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done, thank you!

libc::ETIMEDOUT
);
let elapsed_time = current_time.elapsed().as_millis();
assert!(50 <= elapsed_time && elapsed_time <= 150);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
assert!(50 <= elapsed_time && elapsed_time <= 150);
// This is actually deterministic (since isolation remains enabled), but can change slightly with Rust updates.
assert!(90 <= elapsed_time && elapsed_time <= 110);

"pthread_cond_timedwait_relative_np" => {
let [cond, mutex, reltime] =
this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
this.pthread_cond_timedwait(cond, mutex, reltime, dest, true)?;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
this.pthread_cond_timedwait(cond, mutex, reltime, dest, true)?;
this.pthread_cond_timedwait(cond, mutex, reltime, dest, /* macos_relative_np */ true)?;

@@ -815,7 +815,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
"pthread_cond_timedwait" => {
let [cond, mutex, abstime] =
this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
this.pthread_cond_timedwait(cond, mutex, abstime, dest)?;
this.pthread_cond_timedwait(cond, mutex, abstime, dest, false)?;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
this.pthread_cond_timedwait(cond, mutex, abstime, dest, false)?;
this.pthread_cond_timedwait(cond, mutex, abstime, dest, /* macos_relative_np */ false)?;

// https://github.com/rust-lang/rust/issues/37440#issuecomment-3285958326).
//
// To work around this issue, always clamp the timeout to u64::MAX nanoseconds,
// even if the "ulock" variant is used (which does guard against timeouts).
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// even if the "ulock" variant is used (which does guard against timeouts).
// even if the "ulock" variant is used (which does guard against overflow).

@joboet joboet force-pushed the macos-condvar-timeout branch from 205907b to 95efab6 Compare September 13, 2025 15:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
O-unix Operating system: Unix-like 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
Development

Successfully merging this pull request may close these issues.

Unexpected behavior using std::thread::park_timeout on OSX
4 participants