Date: Wed, 2 Jul 2025 15:28:21 +0100
On Wed, 2 Jul 2025 at 15:04, lordzsar1--- via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> 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.
I assume that "negative argument" means a time that has already passed
(sleep_until takes a time_point, not a duration, so "negative" isn't really
clear).
If that's what you mean, I don't think that's true. The time has passed, so
it should return ASAP.
> 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`!?
>
It would return.
> }
>
> 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.
>
An implementation is always allowed to wake up "later" because it's
impossible to implement anything different. The process might be suspended,
or the thread might not be scheduled, or it might just take many cycles to
return from the function. But I think it's safe to assume that
implementations do not do that intentionally. If the time has passed, there
is no need to sleep at all.
> (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:
>
I disagree. You should not have to do that.
>
> [...]
> 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.
>
Adding a "should" doesn't tell you anything. Users still can't rely on it,
and implementers aren't required to document whether they do or not. You
would still be relying on implementations to be sensible and do the right
thing, which is no different to the situation we already have today.
>
> 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
>
I don't agree that it's a valid transformation to move such clock::now()
calls, but we could standardize that by saying that for all the standard
clocks, now() is an observable checkpoint.
> 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.
>
I don't see where this last bullet comes from.
std-proposals_at_[hidden]> wrote:
> 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.
I assume that "negative argument" means a time that has already passed
(sleep_until takes a time_point, not a duration, so "negative" isn't really
clear).
If that's what you mean, I don't think that's true. The time has passed, so
it should return ASAP.
> 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`!?
>
It would return.
> }
>
> 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.
>
An implementation is always allowed to wake up "later" because it's
impossible to implement anything different. The process might be suspended,
or the thread might not be scheduled, or it might just take many cycles to
return from the function. But I think it's safe to assume that
implementations do not do that intentionally. If the time has passed, there
is no need to sleep at all.
> (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:
>
I disagree. You should not have to do that.
>
> [...]
> 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.
>
Adding a "should" doesn't tell you anything. Users still can't rely on it,
and implementers aren't required to document whether they do or not. You
would still be relying on implementations to be sensible and do the right
thing, which is no different to the situation we already have today.
>
> 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
>
I don't agree that it's a valid transformation to move such clock::now()
calls, but we could standardize that by saying that for all the standard
clocks, now() is an observable checkpoint.
> 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.
>
I don't see where this last bullet comes from.
Received on 2025-07-02 14:28:38