Skip to content

Comments

fix: use raw timer time for setTimeout scheduling to prevent stale clock clamping#6041

Merged
anonrig merged 1 commit intomainfrom
yagiz/fix-settimeout-inconsistencies-2
Feb 11, 2026
Merged

fix: use raw timer time for setTimeout scheduling to prevent stale clock clamping#6041
anonrig merged 1 commit intomainfrom
yagiz/fix-settimeout-inconsistencies-2

Conversation

@anonrig
Copy link
Member

@anonrig anonrig commented Feb 7, 2026

Fixes #6037

When a timeout fires, its timeoutTimes entry isn’t removed until the promise cleanup runs. During the callback, context.now() clamps to that stale past time, so a new setTimeout computed from context.now() can be scheduled in the past and fire immediately.

This change computes the target time from the raw timer channel clock to avoid stale clamping while scheduling. Note: this bypasses context.now()’s clamping (and precise_timers rounding), which may change timing side‑channel behavior.

@anonrig anonrig requested a review from kentonv February 7, 2026 23:05
@anonrig anonrig requested review from a team as code owners February 7, 2026 23:05
@anonrig anonrig requested review from danlapid and jasnell February 7, 2026 23:08
@jasnell
Copy link
Collaborator

jasnell commented Feb 7, 2026

Hmm there's a non-zero risk that this could break something that depends on the current timing, even if flawed. Are we certain this is not a breaking change that needs to be flagged?

@danlapid
Copy link
Collaborator

danlapid commented Feb 7, 2026

Hmm there's a non-zero risk that this could break something that depends on the current timing, even if flawed. Are we certain this is not a breaking change that needs to be flagged?

That would depend on how this does or does not interact with the internal spectre mitigations

Though either way, relying on intricate timing should be assumed to be unreliable.
Do you have a specific concern with the way this specific change would affect timing?

@jasnell
Copy link
Collaborator

jasnell commented Feb 8, 2026

I'm likely worrying for nothing and can't cite a specific example. I've just seen enough cases over the years where changing the timing of events == changing the order of events == subtle breaks. It's probably ok and the risk is likely near zero... it just isn't zero. I want to make sure those are kept in mind.

@danlapid
Copy link
Collaborator

danlapid commented Feb 8, 2026

Ok, let's just wait for @kentonv 's review to make sure we're not missing anything

Copy link
Member

@kentonv kentonv left a comment

Choose a reason for hiding this comment

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

The code in IoContext::now() says:

  KJ_IF_SOME(maybeNextTimeout, timeoutManager->getNextTimeout()) {
    // Don't return a time beyond when the next setTimeout() callback is intended to run. This
    // ensures that Date.now() inside the callback itself always returns exactly the time at which
    // the callback was scheduled (hiding non-determinism which could contain side channels), and
    // that the time returned by Date.now() never goes backwards.
    return kj::min(adjustedTime, maybeNextTimeout);

It seems like this isn't working as intended? Date.now() inside a timeout event doesn't seem to be returning exactly the time that the timeout was scheduled for, as the comment suggests it should.

If we feel that Date.now() should return an exact time during a timeout event, we should fix it so it does. If we feel that this is not what we want, we should remove the code. But I don't think the right answer is to add more code elsewhere which works around the logic.

It may be that we want the exact scheduling behavior in production (where other spectre timer mitigations exist) but not in workerd (which uses exact timers). I need to think about this more but have to run to a meeting now.

@jasnell
Copy link
Collaborator

jasnell commented Feb 9, 2026

... and that's why I like to have @kentonv review these timing related ones ;-) ... the code change itself is ok but I had a strong suspicion this wasn't doing the right thing overall. I think it might be worth capturing a bit more detail of how all the timing stuff should work / is supposed to work in a markdown doc to make this stuff easier to analyze so we're not always having to ping Kenton on these ;-)

@kentonv
Copy link
Member

kentonv commented Feb 9, 2026

Since Date.now() in workerd just reports real time, it makes sense that setTimeout() shouldn't do any shenanigans with scheduling. The "clamp to next timeout time" doesn't make sense.

In prod, the spectre mitigation is supposed to make it appear that timers do not advance at all while in tight CPU loops, and then makes up the difference by advancing more quickly when asleep.

In prod it ought to be the case that if you measure Date.now() before and after setTimeout(), the difference should be exactly the length of the timeout -- because time stops when the CPU is running.

I think we just need to push this clamping behavior into TimerChannel, since that's the abstraction behind which the spectre stuff is supposed to live.

@anonrig anonrig force-pushed the yagiz/fix-settimeout-inconsistencies-2 branch from f9c688a to cfe283c Compare February 10, 2026 02:15
@anonrig
Copy link
Member Author

anonrig commented Feb 10, 2026

This requires a change in downstream repository as well. I've made some changes upon your feedback @kentonv.

@anonrig anonrig requested a review from kentonv February 11, 2026 18:25
@anonrig anonrig merged commit 6ff050c into main Feb 11, 2026
22 of 23 checks passed
@anonrig anonrig deleted the yagiz/fix-settimeout-inconsistencies-2 branch February 11, 2026 20:53
@cloudflare cloudflare deleted a comment from anowardear062-svg Feb 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

setTimeout is still too inaccurate

4 participants