Skip to content

Include suspend time on minipal ticks for Unix-like systems#119443

Closed
marzent wants to merge 2 commits intodotnet:mainfrom
marzent:issue-119437
Closed

Include suspend time on minipal ticks for Unix-like systems#119443
marzent wants to merge 2 commits intodotnet:mainfrom
marzent:issue-119437

Conversation

@marzent
Copy link

@marzent marzent commented Sep 7, 2025

On macOS, this uses CLOCK_MONOTONIC_RAW (and CLOCK_MONOTONIC_RAW_APPROX for low-res ticks as a mini-optimization here) and on Linux uses CLOCK_BOOTTIME when available now.

Emscripten only fully implements CLOCK_MONOTONIC on all platforms, so leave it at that.

Of note is that this loses the optimization done in dotnet/coreclr#2293, since there is no CLOCK_BOOTTIME_COARSE; however, as of Linux 5.0, all of CLOCK_BOOTTIME, CLOCK_MONOTONIC, and CLOCK_MONOTONIC_COARSE are handled via a fast path in vDSO on at least x86/arm64 in the low nanosecond range.

On my arm64 machine with kernel 6.15.6, I couldn't even measure a meaningful difference between any of these three at least.

Fixes #77945 and #119437

Uses CLOCK_MONOTONIC_RAW for hires and CLOCK_MONOTONIC_RAW_APPROX for lowres ticks.

Fixes dotnet#119437
Copilot AI review requested due to automatic review settings September 7, 2025 17:53
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Sep 7, 2025
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Sep 7, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR updates the time measurement implementation on Unix-like systems to include suspend time in the clock measurements. The change switches from uptime-based clocks to suspend-inclusive clocks to provide more accurate time tracking across system sleep/wake cycles.

Key changes:

  • Switch from CLOCK_UPTIME_RAW to CLOCK_MONOTONIC_RAW on macOS systems
  • Prefer CLOCK_BOOTTIME over CLOCK_MONOTONIC on Linux when available
  • Remove the CLOCK_MONOTONIC_COARSE optimization in favor of consistent suspend-inclusive timing

Uses CLOCK_BOOTTIME for hires/lowres ticks.

Fixes [dotnet#119437](dotnet#119437)
@marzent
Copy link
Author

marzent commented Sep 7, 2025

@dotnet-policy-service agree

@marzent marzent changed the title Include suspend time on minimal on Unix-like systems Include suspend time on minipal ticks for Unix-like systems Sep 7, 2025
@jkotas jkotas added area-System.Runtime and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Sep 7, 2025
@jkotas
Copy link
Member

jkotas commented Sep 7, 2025

How does this change interact with existing places that deal with timeouts and use the existing timer?

For example,

bool monitorWaitResult = _waitMonitor.Wait(timeoutMilliseconds - elapsedMilliseconds);
// It's possible for the wait to have timed out, but before the monitor could reacquire the lock, a
// signaler could have acquired it and signaled to satisfy the wait or interrupt the thread. Accept the
// signal and ignore the wait timeout.
int waitResult = ProcessSignaledWaitState();
if (waitResult != WaitHandle.WaitTimeout)
{
return waitResult;
}
if (monitorWaitResult)
{
elapsedMilliseconds = Environment.TickCount - startTimeMilliseconds;
calls
SystemNative_LowLevelMonitor_TimedWait that is still on CLOCK_MONOTONIC plan and it combines it with TickCount that is on CLOCK_BOOTTIME plan now.

I am changing the area to System.Threading.

cc @jkoritzinsky

@marzent
Copy link
Author

marzent commented Sep 8, 2025

How does this change interact with existing places that deal with timeouts and use the existing timer?

This is actually a really good question. Since the timeout passed into SystemNative_LowLevelMonitor_TimedWait is relative this doesn't really make a difference, except for if the pthread condition variable does count time in suspend or not...

On macOS this is wall time in my testing (can also be verified in Apples libc source of _pthread_cond_wait that uses gettimeofday), so this PR would be a better fit even, if that ever was a problem.

On Linux this would create a discrepancy there (again not too sure if it is actually meaningful, and could worst case be fixed by using the appropriate clock in pthread_condattr_setclock).

Notably the Windows codepath, which I believe ends up in WaitForSingleObjectEx on first glance, also shows some interesting behavior according to MSDN:

Windows XP, Windows Server 2003, Windows Vista, Windows 7, Windows Server 2008, and Windows Server 2008 R2: The dwMilliseconds value does include time spent in low-power states. For example, the timeout does keep counting down while the computer is asleep.
Windows 8 and newer, Windows Server 2012 and newer: The dwMilliseconds value does not include time spent in low-power states. For example, the timeout does not keep counting down while the computer is asleep.

So with this change Linux would probably behave as "badly" as modern Windows, when combining TickCount (with suspend time) + Monitor Wait (without suspend time) here I believe.

This also means that for macOS the current interaction without this PR is the TickCount (without suspend time) + Monitor Wait (with suspend time) scenario.

@tannergooding
Copy link
Member

It's worth noting that "best practice" when dealing with something like elapsed/delta times is to clamp the times that are "egregious" so that it behaves generally as expected in the face of things like a debugger breakpoint as well.

There's always going to be various quirks over how timing is tracked and whether it considers low power states, clock frequency changes, app suspension, general OS thread/process management, etc.

It would be "nice" if most of the OS generally followed the same behavior with regards to whether something like sleep/hibernation time (as defined by ACPI) was tracked; but it won't "fix" the broader issue here.

@marzent
Copy link
Author

marzent commented Sep 17, 2025

Is there anything else that needs to be done in this PR besides the two commits?

This is my first time contributing, and I’m happy to address any issues if anything needs changing.

I don’t believe it’s necessary to modify CLOCK_MONOTONIC in other parts of the code for this change. Also, it seems that special care is taken for the macOS codepaths to (reasonably) ensure that waits exclude suspend time, similar to how CLOCK_MONOTONIC works on Linux, and similar to modern Windows waits.

@jkotas
Copy link
Member

jkotas commented Sep 17, 2025

This change is fixing Environment.TickCount and friends on non-Windows to match Windows behavior and what's written in the documentation. However, doing that is going to introduce bugs in other parts of the core libraries. The bugs that this change is introducing may be worse than the bugs that it is fixing. It is not clear what the net impact is to me.

Note that there are Windows specific places in the code like

// Based on its documentation the QueryUnbiasedInterruptTime() function validates
// the argument is non-null. In this case we are always supplying an argument,
// so will skip return value validation.
bool success = Interop.Kernel32.QueryUnbiasedInterruptTime(out ulong time100ns);
Debug.Assert(success);
return (long)(time100ns / 10_000); // convert from 100ns to milliseconds
that compensate for the discrepancy between Environment.TickCount and other APIs. We may need to replicate this on non-Windows. We may even want to introduce new public APIs that expose times that align with timers used for timeouts.

@xtqqczze
Copy link
Contributor

@jkotas Does this PR have a reasonable path toward acceptance?

@NinoFloris
Copy link
Contributor

I would certainly appreciate this to be consistent across platforms, and ideally such that it uses the cheapest (perf-wise) mechanisms to retrieve TickCount64. macOS isn't very cheap today for example, and it shows up in profiles there. It's not that I intrinsically care about macOS perf that much. However as it is my main dev platform I have to mentally ignore these kind of hotspots that aren't as pronounced on linux.

@jkotas
Copy link
Member

jkotas commented Oct 28, 2025

@jkotas Does this PR have a reasonable path toward acceptance?

It needs to address this "However, doing that is going to introduce bugs in other parts of the core libraries. The bugs that this change is introducing may be worse than the bugs that it is fixing. It is not clear what the net impact is to me."

@marzent
Copy link
Author

marzent commented Oct 28, 2025

I am sorry for the late update on this, I haven't had much time to work on it lately...

If my understanding is correct, we just need to provide a Linux/macOS equivalent for QueryUnbiasedInterruptTime, and use it in all places where QueryUnbiasedInterruptTime is used currently in place of TickCount for the Windows codepath.

I will try to implement this, but it looks to be a larger change.

@agocke
Copy link
Member

agocke commented Nov 4, 2025

@marzent No problem -- in that case, I'm going to move this to draft

@agocke agocke marked this pull request as draft November 4, 2025 22:12
@dotnet-policy-service
Copy link
Contributor

Draft Pull Request was automatically closed for 30 days of inactivity. Please let us know if you'd like to reopen it.

@github-actions github-actions bot locked and limited conversation to collaborators Jan 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Threading community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stopwatch.GetTimestamp() doesn't take into account sleep time on Unix

7 participants