Skip to content

Commit b06f19f

Browse files
authored
use faster timers (#1908)
* refactor: refreshTimeout * perf: fast timers * fixup * Revert "fixup" This reverts commit 2fc8eaa7195c931481d902a3f2583f38317td0be8. * fixup * fixup * fixup * fixup * fixup * fixup * fixup * fixup * fixup * Update lib/timers.js * Update lib/timers.js * Update lib/timers.js
1 parent b4c0e5a commit b06f19f

8 files changed

Lines changed: 244 additions & 4 deletions

File tree

lib/client.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
const assert = require('assert')
66
const net = require('net')
77
const util = require('./core/util')
8+
const timers = require('./timers')
89
const Request = require('./core/request')
910
const DispatcherBase = require('./dispatcher-base')
1011
const {
@@ -444,9 +445,9 @@ class Parser {
444445
setTimeout (value, type) {
445446
this.timeoutType = type
446447
if (value !== this.timeoutValue) {
447-
clearTimeout(this.timeout)
448+
timers.clearTimeout(this.timeout)
448449
if (value) {
449-
this.timeout = setTimeout(onParserTimeout, value, this)
450+
this.timeout = timers.setTimeout(onParserTimeout, value, this)
450451
// istanbul ignore else: only for jest
451452
if (this.timeout.unref) {
452453
this.timeout.unref()
@@ -562,7 +563,7 @@ class Parser {
562563
this.llhttp.llhttp_free(this.ptr)
563564
this.ptr = null
564565

565-
clearTimeout(this.timeout)
566+
timers.clearTimeout(this.timeout)
566567
this.timeout = null
567568
this.timeoutValue = null
568569
this.timeoutType = null

lib/timers.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
'use strict'
2+
3+
let fastNow = Date.now()
4+
let fastNowTimeout
5+
6+
const fastTimers = []
7+
8+
function onTimeout () {
9+
fastNow = Date.now()
10+
11+
let len = fastTimers.length
12+
let idx = 0
13+
while (idx < len) {
14+
const timer = fastTimers[idx]
15+
16+
if (timer.expires && fastNow >= timer.expires) {
17+
timer.expires = 0
18+
timer.callback(timer.opaque)
19+
}
20+
21+
if (timer.expires === 0) {
22+
timer.active = false
23+
if (idx !== len - 1) {
24+
fastTimers[idx] = fastTimers.pop()
25+
} else {
26+
fastTimers.pop()
27+
}
28+
len -= 1
29+
} else {
30+
idx += 1
31+
}
32+
}
33+
34+
if (fastTimers.length > 0) {
35+
refreshTimeout()
36+
}
37+
}
38+
39+
function refreshTimeout () {
40+
if (fastNowTimeout && fastNowTimeout.refresh) {
41+
fastNowTimeout.refresh()
42+
} else {
43+
clearTimeout(fastNowTimeout)
44+
fastNowTimeout = setTimeout(onTimeout, 1e3)
45+
if (fastNowTimeout.unref) {
46+
fastNowTimeout.unref()
47+
}
48+
}
49+
}
50+
51+
class Timeout {
52+
constructor (callback, delay, opaque) {
53+
this.callback = callback
54+
this.delay = delay
55+
this.opaque = opaque
56+
this.expires = 0
57+
this.active = false
58+
59+
this.refresh()
60+
}
61+
62+
refresh () {
63+
if (!this.active) {
64+
this.active = true
65+
fastTimers.push(this)
66+
if (!fastNowTimeout || fastTimers.length === 1) {
67+
refreshTimeout()
68+
fastNow = Date.now()
69+
}
70+
}
71+
72+
this.expires = fastNow + this.delay
73+
}
74+
75+
clear () {
76+
this.expires = 0
77+
}
78+
}
79+
80+
module.exports = {
81+
setTimeout (callback, delay, opaque) {
82+
return new Timeout(callback, delay, opaque)
83+
},
84+
clearTimeout (timeout) {
85+
if (timeout && timeout.clear) {
86+
timeout.clear()
87+
}
88+
}
89+
}

test/client-keep-alive.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const { test } = require('tap')
44
const { Client } = require('..')
5+
const timers = require('../lib/timers')
56
const { kConnect } = require('../lib/core/symbols')
67
const { createServer } = require('net')
78
const http = require('http')
@@ -47,6 +48,12 @@ test('keep-alive header 0', (t) => {
4748
const clock = FakeTimers.install()
4849
t.teardown(clock.uninstall.bind(clock))
4950

51+
const orgTimers = { ...timers }
52+
Object.assign(timers, { setTimeout, clearTimeout })
53+
t.teardown(() => {
54+
Object.assign(timers, orgTimers)
55+
})
56+
5057
const server = createServer((socket) => {
5158
socket.write('HTTP/1.1 200 OK\r\n')
5259
socket.write('Content-Length: 0\r\n')

test/client-reconnect.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const { test } = require('tap')
44
const { Client } = require('..')
55
const { createServer } = require('http')
66
const FakeTimers = require('@sinonjs/fake-timers')
7+
const timers = require('../lib/timers')
78

89
test('multiple reconnect', (t) => {
910
t.plan(5)
@@ -12,6 +13,12 @@ test('multiple reconnect', (t) => {
1213
const clock = FakeTimers.install()
1314
t.teardown(clock.uninstall.bind(clock))
1415

16+
const orgTimers = { ...timers }
17+
Object.assign(timers, { setTimeout, clearTimeout })
18+
t.teardown(() => {
19+
Object.assign(timers, orgTimers)
20+
})
21+
1522
const server = createServer((req, res) => {
1623
n === 0 ? res.destroy() : res.end('ok')
1724
})

test/client-timeout.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { Client, errors } = require('..')
55
const { createServer } = require('http')
66
const { Readable } = require('stream')
77
const FakeTimers = require('@sinonjs/fake-timers')
8+
const timers = require('../lib/timers')
89

910
test('refresh timeout on pause', (t) => {
1011
t.plan(1)
@@ -51,6 +52,12 @@ test('start headers timeout after request body', (t) => {
5152
const clock = FakeTimers.install()
5253
t.teardown(clock.uninstall.bind(clock))
5354

55+
const orgTimers = { ...timers }
56+
Object.assign(timers, { setTimeout, clearTimeout })
57+
t.teardown(() => {
58+
Object.assign(timers, orgTimers)
59+
})
60+
5461
const server = createServer((req, res) => {
5562
})
5663
t.teardown(server.close.bind(server))
@@ -101,6 +108,12 @@ test('start headers timeout after async iterator request body', (t) => {
101108
const clock = FakeTimers.install()
102109
t.teardown(clock.uninstall.bind(clock))
103110

111+
const orgTimers = { ...timers }
112+
Object.assign(timers, { setTimeout, clearTimeout })
113+
t.teardown(() => {
114+
Object.assign(timers, orgTimers)
115+
})
116+
104117
const server = createServer((req, res) => {
105118
})
106119
t.teardown(server.close.bind(server))
@@ -167,7 +180,7 @@ test('parser resume with no body timeout', (t) => {
167180
onConnect () {
168181
},
169182
onHeaders (statusCode, headers, resume) {
170-
setTimeout(resume, 100)
183+
setTimeout(resume, 2000)
171184
return false
172185
},
173186
onData () {

test/fetch/fetch-timeouts.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const { test } = require('tap')
44

55
const { fetch, Agent } = require('../..')
6+
const timers = require('../../lib/timers')
67
const { createServer } = require('http')
78
const FakeTimers = require('@sinonjs/fake-timers')
89

@@ -16,6 +17,12 @@ test('Fetch very long request, timeout overridden so no error', (t) => {
1617
const clock = FakeTimers.install()
1718
t.teardown(clock.uninstall.bind(clock))
1819

20+
const orgTimers = { ...timers }
21+
Object.assign(timers, { setTimeout, clearTimeout })
22+
t.teardown(() => {
23+
Object.assign(timers, orgTimers)
24+
})
25+
1926
const server = createServer((req, res) => {
2027
setTimeout(() => {
2128
res.end('hello')

0 commit comments

Comments
 (0)