Date: Wed, 2 Jul 2025 14:01:55 +0000 (UTC)
Dear std-proposals mailing list,
recently I found that I _have_ to check the clock before calling std::this_thead::sleep_until, as the function does not have a specified behaviour, if called with a negative argument. E.g. in a loop that may take longer than the intended sleep interval:
using clock = std::chrono::steady_clock;
using clock_representation = clock::duration::rep;
template<std::regular_invocable F>
void sleepMaintainFrameRate(clock_representation frameRate, F && loop)
{
using unit = clock::duration;
const auto loopBegin{clock::now()};
const unit napTime{unit::period::den / unit::period::num / frameRate};
const auto loopIdealEnd{loopBegin + napTime};
loop(); // may end after loopIdealEnd, e.g. if the user specified a sufficiently high framerate
std::this_thread::sleep_until(loopIdealEnd); // What will this do, if `clock::now() > loopIdealEnd`!?
}
As the function sleeps for as long as specified or longer, as per [thread.req.timing].4, an implementation would be free to e.g. implement the negative case as sleep_until(1), the smallest sensible sleep.
(Or to not handle the case at all and put us at the mercy of the OS or the underlying hardware – too many different places to check.)
MSVC implements sleep_until such that it checks the clock and does nothing if the time is negative-or-equal. For the negative case, this is exactly what I want. However, to ensure this behaviour across library implementations, I must still reproduce the same check in user code:
[...]
loop();
if (loopIdealEnd > clock::now())
std::this_thread::sleep_until(loopIdealEnd);
}
and to omit the check inside the library implementation under the as-if rule, if I am not mistaken, the linker would have to
* understand specifically how a steady_clock works (that is, that the second result _must not_ be earlier than the first)
* ignore that _technically_ clock calls can never be omitted, as clock calls _themselves_ take time, aka two calls cannot have the exact same result
A second, less straight forward case is the one of 0 duration: Should the implementation yield, which is semantically equal to sleeping for no time, or should it not yield? The MSVC implementation _does not_ but it would be at least as reasonable to _do_ : If we _ought not to_ need more time to loop than we want to sleep_for – but right now we _do_ – then we are probably in a resource-constrained situation and it might be very neighborly of our thread to _do_ yield at this point and not add to the bottleneck.
I find myself unable to say: _always do_ yield or _never do_ yield in this case. Thus it would seem important that the Standard take a stance, even if arbitrary, (e.g. like the wording in [thread.req.timing].3: “Implementations should […]”, allowing dissent, if need be, but still establishing a default expectation), so the implementor may know which of the two cases they have to specify explicitly and which one not.
What makes the situation even harder on the Standard Library user, is that the compilers – apparently standard-conforming – are currently free to move any clock::now after loop in front of loop, making it straight up impossible (or at least unreasonably hard) to implement it correctly:
* https://www.reddit.com/r/cpp/comments/dh3hle/why_is_compiler_allowed_to_reorder_instructions/
* https://stackoverflow.com/a/26190816
Indeed, I was not able to find out, how such a correct implementation would look like. This demonstrated obliviousness of the used toolchains to clocks’ observable effects enhances my presumption that they cannot (in good conscience) optimise superfluous clock calls away, aka checking in user code _really must be_ a pessimisation, not merely “harmless” boilerplate.
... I hope this write-up sounds about sensible to you. The reactions to my question https://stackoverflow.com/q/79677353 prompted me to try and “escalate”, if you will:
* "This function may block for longer than sleep_duration due to scheduling or resource contention delays.", so it might technically just sleep for 0 instead.
* […] sleeping for a negative duration refers to a time that has already passed and consequently it should not sleep at all.
* I think the common expectation is that this would behave the same as sleeping with a duration of 0 e.g. wakeup on next thread scheduling. (Though I also sleep(0) should no longer deschedule a thread)...
* might also be UB, if sleep(-10) doesn't make any progress.
Yours sincerely
recently I found that I _have_ to check the clock before calling std::this_thead::sleep_until, as the function does not have a specified behaviour, if called with a negative argument. E.g. in a loop that may take longer than the intended sleep interval:
using clock = std::chrono::steady_clock;
using clock_representation = clock::duration::rep;
template<std::regular_invocable F>
void sleepMaintainFrameRate(clock_representation frameRate, F && loop)
{
using unit = clock::duration;
const auto loopBegin{clock::now()};
const unit napTime{unit::period::den / unit::period::num / frameRate};
const auto loopIdealEnd{loopBegin + napTime};
loop(); // may end after loopIdealEnd, e.g. if the user specified a sufficiently high framerate
std::this_thread::sleep_until(loopIdealEnd); // What will this do, if `clock::now() > loopIdealEnd`!?
}
As the function sleeps for as long as specified or longer, as per [thread.req.timing].4, an implementation would be free to e.g. implement the negative case as sleep_until(1), the smallest sensible sleep.
(Or to not handle the case at all and put us at the mercy of the OS or the underlying hardware – too many different places to check.)
MSVC implements sleep_until such that it checks the clock and does nothing if the time is negative-or-equal. For the negative case, this is exactly what I want. However, to ensure this behaviour across library implementations, I must still reproduce the same check in user code:
[...]
loop();
if (loopIdealEnd > clock::now())
std::this_thread::sleep_until(loopIdealEnd);
}
and to omit the check inside the library implementation under the as-if rule, if I am not mistaken, the linker would have to
* understand specifically how a steady_clock works (that is, that the second result _must not_ be earlier than the first)
* ignore that _technically_ clock calls can never be omitted, as clock calls _themselves_ take time, aka two calls cannot have the exact same result
A second, less straight forward case is the one of 0 duration: Should the implementation yield, which is semantically equal to sleeping for no time, or should it not yield? The MSVC implementation _does not_ but it would be at least as reasonable to _do_ : If we _ought not to_ need more time to loop than we want to sleep_for – but right now we _do_ – then we are probably in a resource-constrained situation and it might be very neighborly of our thread to _do_ yield at this point and not add to the bottleneck.
I find myself unable to say: _always do_ yield or _never do_ yield in this case. Thus it would seem important that the Standard take a stance, even if arbitrary, (e.g. like the wording in [thread.req.timing].3: “Implementations should […]”, allowing dissent, if need be, but still establishing a default expectation), so the implementor may know which of the two cases they have to specify explicitly and which one not.
What makes the situation even harder on the Standard Library user, is that the compilers – apparently standard-conforming – are currently free to move any clock::now after loop in front of loop, making it straight up impossible (or at least unreasonably hard) to implement it correctly:
* https://www.reddit.com/r/cpp/comments/dh3hle/why_is_compiler_allowed_to_reorder_instructions/
* https://stackoverflow.com/a/26190816
Indeed, I was not able to find out, how such a correct implementation would look like. This demonstrated obliviousness of the used toolchains to clocks’ observable effects enhances my presumption that they cannot (in good conscience) optimise superfluous clock calls away, aka checking in user code _really must be_ a pessimisation, not merely “harmless” boilerplate.
... I hope this write-up sounds about sensible to you. The reactions to my question https://stackoverflow.com/q/79677353 prompted me to try and “escalate”, if you will:
* "This function may block for longer than sleep_duration due to scheduling or resource contention delays.", so it might technically just sleep for 0 instead.
* […] sleeping for a negative duration refers to a time that has already passed and consequently it should not sleep at all.
* I think the common expectation is that this would behave the same as sleeping with a duration of 0 e.g. wakeup on next thread scheduling. (Though I also sleep(0) should no longer deschedule a thread)...
* might also be UB, if sleep(-10) doesn't make any progress.
Yours sincerely
Received on 2025-07-02 14:03:58