LibCore: Make timer firing order stable for equal deadlines

Timers scheduled with identical `fire_time` could fire out of order
because the heap is not stable. This change assigns a monotonically
increasing `sequence_id` when a timer is scheduled and extend the heap
comparator to order by (`fire_time`, `sequence_id`). This guarantees
FIFO among timers with the same deadline.

This matches the HTML "run steps after a timeout" ordering requirement:
older invocations with <= delay complete before newer ones.
https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#run-steps-after-a-timeout
This commit is contained in:
Aliaksandr Kalenik
2025-09-21 17:47:08 +02:00
committed by Sam Atkins
parent 1d41cf10f4
commit de3f32a5c9
Notes: github-actions[bot] 2025-09-22 11:35:38 +00:00
3 changed files with 128 additions and 0 deletions

View File

@@ -70,6 +70,9 @@ public:
bool is_scheduled() const { return m_index != INVALID_INDEX; }
void set_sequence_id(u64 id) { m_sequence_id = id; }
u64 sequence_id() const { return m_sequence_id; }
protected:
union {
AK::Duration m_duration;
@@ -78,6 +81,7 @@ protected:
private:
ssize_t m_index = INVALID_INDEX;
u64 m_sequence_id { 0 };
};
class TimeoutSet {
@@ -122,12 +126,14 @@ public:
void schedule_relative(EventLoopTimeout* timeout)
{
timeout->set_sequence_id(m_next_sequence_id++);
timeout->set_index({}, -1 - static_cast<ssize_t>(m_scheduled_timeouts.size()));
m_scheduled_timeouts.append(timeout);
}
void schedule_absolute(EventLoopTimeout* timeout)
{
timeout->set_sequence_id(m_next_sequence_id++);
m_heap.insert(timeout);
}
@@ -160,6 +166,8 @@ private:
IntrusiveBinaryHeap<
EventLoopTimeout*,
decltype([](EventLoopTimeout* a, EventLoopTimeout* b) {
if (a->fire_time() == b->fire_time())
return a->sequence_id() < b->sequence_id();
return a->fire_time() < b->fire_time();
}),
decltype([](EventLoopTimeout* timeout, size_t index) {
@@ -168,6 +176,7 @@ private:
8>
m_heap;
Vector<EventLoopTimeout*, 8> m_scheduled_timeouts;
u64 m_next_sequence_id { 0 };
};
class EventLoopTimer final : public EventLoopTimeout {