Skip to content

[bug] Expired timer batch fires in creation order, not deadline order (5 ms timer fires after a 10 ms one) #6287

Description

@proggeramlug

Severity: correctness (timer ordering diverges from Node)
Found by: e2e A/B while working on #6084's timer drain (PR #6285).

What

Perry fires an expired timer batch in creation order. Node fires by deadline. So a shorter timer created later than a longer one fires second:

setTimeout(() => console.log("late10"), 10);
setTimeout(() => console.log("reffed"), 5);   // created later, shorter deadline
  • node: reffed, late10
  • perry: late10, reffed

Reproduced with a mixed burst (10 same-deadline + 5 cleared + staggered 5/10/20/30 ms). Everything else in that test matches Node byte-for-byte — same-deadline timers do fire in creation order correctly, cancelled timers never fire, and hasRef() is right. The divergence is purely the ordering between different deadlines that are all expired at the same tick.

Why

js_timer_tick collects expired timers by walking TIMER_QUEUE in queue (creation) order and firing them in that order. Nothing sorts the expired batch by deadline. When the loop is busy (or several deadlines elapse between ticks), multiple timers are expired at the same tick and the relative order is whatever the queue order happened to be.

Confirmed pre-existing on main, not a regression: reverting timer.rs to base and rebuilding produces the identical late10, reffed output. PR #6285's single-pass partition is deliberately order-identical to the old Vec::remove(i) scan, so it neither causes nor fixes this.

Fix

This is the natural thing to fix alongside the BinaryHeap redesign already listed as a follow-up in #6084 item 3: a per-queue BinaryHeap keyed by (deadline, creation_seq) gives deadline order for free while preserving creation order as the tiebreak for same-deadline timers (which is the Node semantic that the current code does get right, and must not regress).

Part of the #6084 timer cluster.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions