Skip to content

Commit d6756db

Browse files
committed
timers: fix the order of timers under a long loop
1 parent ecd385e commit d6756db

File tree

4 files changed

+96
-30
lines changed

4 files changed

+96
-30
lines changed

lib/internal/priority_queue.js

+26-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = class PriorityQueue {
1515
#heap = new Array(64);
1616
#setPosition;
1717
#size = 0;
18+
#secondary = null;
1819

1920
constructor(comparator, setPosition) {
2021
if (comparator !== undefined)
@@ -23,7 +24,19 @@ module.exports = class PriorityQueue {
2324
this.#setPosition = setPosition;
2425
}
2526

26-
insert(value) {
27+
updateSecondary() {
28+
if (this.#size <= 1) {
29+
this.#secondary = null;
30+
return;
31+
}
32+
33+
const top = this.peek();
34+
this.removeAt(1);
35+
this.#secondary = this.peek();
36+
this.insert(top, false);
37+
}
38+
39+
insert(value, updateSecondary = true) {
2740
const heap = this.#heap;
2841
const pos = ++this.#size;
2942
heap[pos] = value;
@@ -32,12 +45,17 @@ module.exports = class PriorityQueue {
3245
heap.length *= 2;
3346

3447
this.percolateUp(pos);
48+
if (updateSecondary) this.updateSecondary();
3549
}
3650

3751
peek() {
3852
return this.#heap[1];
3953
}
4054

55+
secondary() {
56+
return this.#secondary;
57+
}
58+
4159
percolateDown(pos) {
4260
const compare = this.#compare;
4361
const setPosition = this.#setPosition;
@@ -82,7 +100,7 @@ module.exports = class PriorityQueue {
82100
setPosition(item, pos);
83101
}
84102

85-
removeAt(pos) {
103+
removeAt(pos, updateSecondary = true) {
86104
const heap = this.#heap;
87105
const size = --this.#size;
88106
heap[pos] = heap[size + 1];
@@ -94,15 +112,20 @@ module.exports = class PriorityQueue {
94112
else
95113
this.percolateDown(pos);
96114
}
115+
116+
if (updateSecondary) {
117+
this.updateSecondary();
118+
}
97119
}
98120

99-
shift() {
121+
shift(updateSecondary = true) {
100122
const heap = this.#heap;
101123
const value = heap[1];
102124
if (value === undefined)
103125
return;
104126

105127
this.removeAt(1);
128+
if (updateSecondary) this.updateSecondary();
106129

107130
return value;
108131
}

lib/internal/timers.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -520,16 +520,26 @@ function getTimerCallbacks(runNextTicks) {
520520

521521
let ranAtLeastOneTimer = false;
522522
let timer;
523+
const secondaryList = timerListQueue.secondary();
523524
while ((timer = L.peek(list)) != null) {
524525
const diff = now - timer._idleStart;
525526

526527
// Check if this loop iteration is too early for the next timer.
527528
// This happens if there are more timers scheduled for later in the list.
528-
if (diff < msecs) {
529+
//
530+
// Else, if the secondary list is earlier than current timer, then we
531+
// return to the main loop to process the secondary list first.
532+
if (diff < msecs || secondaryList?.expiry < timer._idleStart + msecs) {
529533
list.expiry = MathMax(timer._idleStart + msecs, now + 1);
530534
list.id = timerListId++;
531535
timerListQueue.percolateDown(1);
532-
debug('%d list wait because diff is %d', msecs, diff);
536+
timerListQueue.updateSecondary();
537+
538+
if (diff < msecs) {
539+
debug('%d list wait because diff is %d', msecs, diff);
540+
} else {
541+
debug('%d list wait because it\s no more earliest', msecs);
542+
}
533543
return;
534544
}
535545

test/parallel/test-priority-queue.js

+25-25
Original file line numberDiff line numberDiff line change
@@ -80,35 +80,35 @@ const PriorityQueue = require('internal/priority_queue');
8080
return a.value - b.value;
8181
}, (node, pos) => (node.position = pos));
8282

83-
queue.insert({ value: 1, position: null });
84-
queue.insert({ value: 2, position: null });
85-
queue.insert({ value: 3, position: null });
86-
queue.insert({ value: 4, position: null });
87-
queue.insert({ value: 5, position: null });
83+
queue.insert({ value: 1, position: null }, false);
84+
queue.insert({ value: 2, position: null }, false);
85+
queue.insert({ value: 3, position: null }, false);
86+
queue.insert({ value: 4, position: null }, false);
87+
queue.insert({ value: 5, position: null }, false);
8888

89-
queue.insert({ value: 2, position: null });
89+
queue.insert({ value: 2, position: null }, false);
9090
const secondLargest = { value: 10, position: null };
91-
queue.insert(secondLargest);
91+
queue.insert(secondLargest, false);
9292
const largest = { value: 15, position: null };
93-
queue.insert(largest);
93+
queue.insert(largest, false);
9494

95-
queue.removeAt(5);
95+
queue.removeAt(5, false);
9696
assert.strictEqual(largest.position, 5);
9797

9898
// Check that removing 2nd to last item works fine
99-
queue.removeAt(6);
99+
queue.removeAt(6, false);
100100
assert.strictEqual(secondLargest.position, 6);
101101

102102
// Check that removing the last item doesn't throw
103-
queue.removeAt(6);
103+
queue.removeAt(6, false);
104104

105-
assert.strictEqual(queue.shift().value, 1);
106-
assert.strictEqual(queue.shift().value, 2);
107-
assert.strictEqual(queue.shift().value, 2);
108-
assert.strictEqual(queue.shift().value, 4);
109-
assert.strictEqual(queue.shift().value, 15);
105+
assert.strictEqual(queue.shift(false).value, 1);
106+
assert.strictEqual(queue.shift(false).value, 2);
107+
assert.strictEqual(queue.shift(false).value, 2);
108+
assert.strictEqual(queue.shift(false).value, 4);
109+
assert.strictEqual(queue.shift(false).value, 15);
110110

111-
assert.strictEqual(queue.shift(), undefined);
111+
assert.strictEqual(queue.shift(false), undefined);
112112
}
113113

114114

@@ -122,20 +122,20 @@ const PriorityQueue = require('internal/priority_queue');
122122
const i7 = { value: 7, position: null };
123123
const i8 = { value: 8, position: null };
124124

125-
queue.insert({ value: 1, position: null });
126-
queue.insert({ value: 6, position: null });
127-
queue.insert({ value: 2, position: null });
128-
queue.insert(i7);
129-
queue.insert(i8);
130-
queue.insert(i3);
125+
queue.insert({ value: 1, position: null }, false);
126+
queue.insert({ value: 6, position: null }, false);
127+
queue.insert({ value: 2, position: null }, false);
128+
queue.insert(i7, false);
129+
queue.insert(i8, false);
130+
queue.insert(i3, false);
131131

132132
assert.strictEqual(i7.position, 4);
133-
queue.removeAt(4);
133+
queue.removeAt(4, false);
134134

135135
// 3 should percolate up to swap with 6 (up)
136136
assert.strictEqual(i3.position, 2);
137137

138-
queue.removeAt(2);
138+
queue.removeAt(2, false);
139139

140140
// 8 should swap places with 6 (down)
141141
assert.strictEqual(i8.position, 4);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
5+
const common = require('../common');
6+
7+
const orders = [];
8+
9+
process.on('exit', () => {
10+
assert.deepStrictEqual(orders, [ 1, 2, 3 ]);
11+
});
12+
13+
setTimeout(() => {
14+
orders.push(1);
15+
}, 10);
16+
17+
setTimeout(() => {
18+
orders.push(2);
19+
}, 15);
20+
21+
let now = Date.now();
22+
while (Date.now() - now < 100) {
23+
//
24+
}
25+
26+
setTimeout(() => {
27+
orders.push(3);
28+
}, 10);
29+
30+
now = Date.now();
31+
while (Date.now() - now < 100) {
32+
//
33+
}

0 commit comments

Comments
 (0)