Skip to content

Commit 995f064

Browse files
1ly4s0Uzlopak
andauthored
Fix timer guards to avoid TypeError under fake‐timers and polyfilled … (#4213)
* Fix timer guards to avoid TypeError under fake‐timers and polyfilled environments * Remove styling changes * Add tests * Add comments * fix(lint): satisfy eslint stylistic rules for EOF, indent and jest globals * Update lib/dispatcher/client-h1.js * fix linting issue * use setTimeout from node:timers to increase chance of having node specific helper methods for timers, if refresh is not existing, create a new timeout for refreshTimeout * Apply suggestions from code review --------- Co-authored-by: Aras Abbasi <[email protected]>
1 parent 916f4f5 commit 995f064

File tree

4 files changed

+79
-10
lines changed

4 files changed

+79
-10
lines changed

lib/dispatcher/client-h1.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ class Parser {
249249
this.timeout = timers.setFastTimeout(onParserTimeout, delay, new WeakRef(this))
250250
} else {
251251
this.timeout = setTimeout(onParserTimeout, delay, new WeakRef(this))
252-
this.timeout.unref()
252+
this.timeout?.unref()
253253
}
254254
}
255255

lib/util/timers.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -188,19 +188,21 @@ function onTick () {
188188
}
189189

190190
function refreshTimeout () {
191-
// If the fastNowTimeout is already set, refresh it.
192-
if (fastNowTimeout) {
191+
// If the fastNowTimeout is already set and the Timer has the refresh()-
192+
// method available, call it to refresh the timer.
193+
// Some timer objects returned by setTimeout may not have a .refresh()
194+
// method (e.g. mocked timers in tests).
195+
if (fastNowTimeout?.refresh) {
193196
fastNowTimeout.refresh()
194-
// fastNowTimeout is not instantiated yet, create a new Timer.
197+
// fastNowTimeout is not instantiated yet or refresh is not availabe,
198+
// create a new Timer.
195199
} else {
196200
clearTimeout(fastNowTimeout)
197201
fastNowTimeout = setTimeout(onTick, TICK_MS)
198-
199-
// If the Timer has an unref method, call it to allow the process to exit if
200-
// there are no other active handles.
201-
if (fastNowTimeout.unref) {
202-
fastNowTimeout.unref()
203-
}
202+
// If the Timer has an unref method, call it to allow the process to exit,
203+
// if there are no other active handles. When using fake timers or mocked
204+
// environments (like Jest), .unref() may not be defined,
205+
fastNowTimeout?.unref()
204206
}
205207
}
206208

test/jest/parser-timeout.test.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict'
2+
/* global jest, describe, it, beforeEach, afterEach, expect */
3+
4+
// test/jest/parser-timeout.test.js
5+
const EventEmitter = require('events')
6+
const connectH1 = require('../../lib/dispatcher/client-h1')
7+
const { kParser } = require('../../lib/core/symbols')
8+
9+
// DummySocket extends EventEmitter to support .on/.off/.read()
10+
class DummySocket extends EventEmitter {
11+
constructor () {
12+
super()
13+
this.destroyed = false
14+
this.errored = null
15+
}
16+
17+
read () {
18+
return null
19+
}
20+
}
21+
22+
const dummyClient = {
23+
[Symbol.for('kMaxHeadersSize')]: 1024,
24+
[Symbol.for('kMaxResponseSize')]: 1024,
25+
[Symbol.for('kQueue')]: [],
26+
[Symbol.for('kRunningIdx')]: 0,
27+
headersTimeout: 100,
28+
bodyTimeout: 100
29+
}
30+
31+
describe('Parser#setTimeout under fake timers', () => {
32+
beforeEach(() => jest.useFakeTimers('modern'))
33+
afterEach(() => jest.useRealTimers())
34+
35+
it('does not throw when calling setTimeout under fake timers', async () => {
36+
const socket = new DummySocket()
37+
await connectH1(dummyClient, socket) // connectH1 creates Parser -> socket[kParser]
38+
const parser = socket[kParser]
39+
expect(() => parser.setTimeout(200, 0)).not.toThrow()
40+
})
41+
})

test/jest/util-timers.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict'
2+
/* global jest, describe, it, beforeEach, afterEach, expect */
3+
4+
// test/jest/util-timers.test.js
5+
const timers = require('../../lib/util/timers')
6+
7+
describe('util/timers under fake timers', () => {
8+
beforeEach(() => jest.useFakeTimers('modern'))
9+
afterEach(() => {
10+
jest.useRealTimers()
11+
try {
12+
timers.reset()
13+
} catch {}
14+
})
15+
16+
it('setFastTimeout + clearFastTimeout does not throw', () => {
17+
const fast = timers.setFastTimeout(() => {}, 2000)
18+
expect(typeof fast.refresh).toBe('function')
19+
expect(() => timers.clearFastTimeout(fast)).not.toThrow()
20+
})
21+
22+
it('short setTimeout + clearTimeout does not throw', () => {
23+
const t = timers.setTimeout(() => {}, 10)
24+
expect(() => timers.clearTimeout(t)).not.toThrow()
25+
})
26+
})

0 commit comments

Comments
 (0)