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
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 {

View File

@ -0,0 +1,100 @@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<script src="include.js"></script>
<script>
asyncTest(done => {
let invocation = 0;
function makeTimeout() {
const count = ++invocation;
setTimeout(() => {
println(count);
if (count === 100)
done();
}, 1);
}
for (let i = 0; i < 100; i++) {
makeTimeout();
}
});
</script>