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

Instant accounts for system sleep on Windows but not on linux #79462

Open
mahkoh opened this issue Nov 27, 2020 · 10 comments
Open

Instant accounts for system sleep on Windows but not on linux #79462

mahkoh opened this issue Nov 27, 2020 · 10 comments
Labels
A-time Area: Time C-bug Category: This is a bug. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Comments

@mahkoh
Copy link
Contributor

mahkoh commented Nov 27, 2020

On linux, Instant uses clock_gettime with CLOCK_MONOTONIC which does not count time spent in a sleep state such as hiberation or standby.

On Windows, Instant uses QueryPerformanceCounter which does count time spent in a sleep state:

QueryPerformanceCounter reads the performance counter and returns the total number of ticks that have occurred since the Windows operating system was started, including the time when the machine was in a sleep state such as standby, hibernate, or connected standby.

Neither behavior is currently documented.

Consider the following code (adapted from similar code in Tokio):

use std::time::{Duration, SystemTime, Instant};

fn main() {
    let mut next = Instant::now();
    loop {
        let now = Instant::now();
        if next > now {
            std::thread::sleep(next - now);
        }
        next += Duration::from_secs(1);
        println!("{:?} tick", SystemTime::UNIX_EPOCH.elapsed().unwrap().as_millis());
    }
}

Behavior on Linux: Ticks during sleep are lost.

Behavior on Windows: Ticks during sleep occur in rapid succession after resume.

@mahkoh mahkoh added the C-bug Category: This is a bug. label Nov 27, 2020
@jonas-schievink jonas-schievink added the T-libs Relevant to the library team, which will review and decide on the PR/issue. label Nov 27, 2020
@mahkoh mahkoh changed the title Instant accounts for system sleep on Windows but not elsewhere Instant accounts for system sleep on Windows but not on linux Nov 27, 2020
@mahkoh
Copy link
Contributor Author

mahkoh commented Nov 27, 2020

The behavior of CLOCK_MONOTONIC appears to vary between implementations:

  • Linux: CLOCK_MONOTONIC counts the uptime. CLOCK_BOOTTIME counts the time since boot. [1]
  • FreeBSD: CLOCK_MONOTONIC counts the uptime. There appears to be no way to get the time since boot. [2]
  • OpenBSD: CLOCK_MONOTONIC counts the time since boot. CLOCK_UPTIME counts the uptime. [3]
  • MacOS: CLOCK_MONOTONIC counts the time since boot. CLOCK_UPTIME_RAW counts the uptime. [4]

[1] During the 4.17 development cycle CLOCK_MONOTONIC was changed to behave like CLOCK_BOOTTIME. But this broke userspace and was reverted.
[2] https://lists.freebsd.org/pipermail/freebsd-hackers/2018-June/052899.html
[3] https://github.com/openbsd/src/blob/e71a3804998757589450d9364e961ab21db849c9/sys/kern/kern_time.c#L121-L124
[4] http://www.manpagez.com/man/3/clock_gettime/

@the8472
Copy link
Member

the8472 commented Nov 28, 2020

From the docs

An instant may jump forwards or experience time dilation (slow down or speed up), but it will never go backwards.

I think this leeway exists to allow whichever timer is the fastest to be used, not the most consistent or human-friendly one.

@mahkoh
Copy link
Contributor Author

mahkoh commented Nov 28, 2020

That comment is about NTP.

@the8472
Copy link
Member

the8472 commented Nov 28, 2020

The documentation does not restrict itself to that. NTP is only one possible cause for clocks jumping, but not the only one. Users can manually adjust clocks too. Sleep is just another possible source. VM migrations are a thing too.

@mahkoh
Copy link
Contributor Author

mahkoh commented Nov 28, 2020

Please spare me this academic citing of the documentation. This issue is about a serious difference in behavior on different platforms. Your pedantic reading would make an implementation that has Instant::new return a constant value valid. This is neither what the authors of the API intended nor how downstream is using it.

@the8472
Copy link
Member

the8472 commented Nov 28, 2020

The point is that Instant boils down to rdtsc on many systems (give or take some cleanups/backsliding protection). rdtsc quality has changed with cpu generations. E.g. in some models it did not tick when the cpu was in some sleep states (which would be similar to what you're experiencing) and this changed in other models (see the nonstop_tsc cpu capability, among others).

Your pedantic reading would make an implementation that has Instant::new return a constant value valid.

Indeed. But implementations strive to do better than that on a best-effort basis. That doesn't mean they promise to do better than that. It definitely does not promise wall-time.

This is neither what the authors of the API intended

The intent of Instant is to have a low-overhead, fine-grained elapsed time measurement. Whether elapsed time includes sleeps or not isn't specified. Calling to the system clock that provides wall-time is high-overhead or low-granularity on some platforms.

This issue is about a serious difference in behavior on different platforms.

Well, at least the documentation could be improved.

@mahkoh
Copy link
Contributor Author

mahkoh commented Nov 28, 2020

Both CLOCK_MONOTONIC and CLOCK_BOOTTIME have the same associated costs on x86_64 linux. Therefore there is nothing inherent on x86_64 that makes one mode more expensive than the other one. Nobody was talking about wall-time.

@the8472
Copy link
Member

the8472 commented Nov 28, 2020

CLOCK_BOOTTIME was introduced in kernel 2.6.39. Rust's minimum supported kernel version was recently bumped to 2.6.32. It might be possible to work around this by probing with different flags, but that would only provide this behavior on a best-effort basis and not guarantee it which means downstream should not be relying on it.
And as you mention yourself there's no CLOCK_BOOTTIME for FreeBSD.

So the smallest common denominator is and remains that system suspend may or may not be accounted for.

@asnare
Copy link

asnare commented Dec 20, 2021

I just ran into this on macOS, where the implementation is based on mach_absolute_time() and (also) doesn't advance during sleep.

Is there any interest in updating the documentation to specifically mention that measured durations may or may not include time spent during system suspend/sleep? It's clearly a significant difference in behaviour amongst the supported platforms…

@DXist
Copy link

DXist commented Sep 25, 2023

I've published boot-time crate that provides Instant struct based on CLOCK_BOOTTIME.

bors added a commit to rust-lang-ci/rust that referenced this issue Sep 30, 2023
…rk-Simulacrum

Document that Instant may or may not include system-suspend time

Since people are still occasionally surprised by this let's make it more explicit. This doesn't add any new guarantees, only documents the status quo.

Related issues: rust-lang#87906 rust-lang#79462
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-time Area: Time C-bug Category: This is a bug. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

6 participants