Skip to content

Commit 547519d

Browse files
feat: timerVariant option to choose between native and worker timers (#1818)
* feat: option to choose between native and worker timers * feat: initialize timer once * fix: pingTimer test check correct timerId prop --------- Co-authored-by: Daniel Lando <[email protected]>
1 parent 50776a7 commit 547519d

File tree

8 files changed

+91
-35
lines changed

8 files changed

+91
-35
lines changed

DEVELOPMENT.md

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ npm test
1919

2020
This will run both `browser` and `node` tests.
2121

22+
23+
### Running specific tests
24+
25+
For example, you can run `node -r esbuild-register --test test/pingTimer.ts`
26+
2227
### Browser
2328

2429
Browser tests use [`wtr`](https://modern-web.dev/docs/test-runner/overview/) as the test runner. To build browser bundle using [esbuild](https://esbuild.github.io/) and run browser tests, you can use the following command:

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@ The arguments are:
457457
- `messageIdProvider`: custom messageId provider. when `new UniqueMessageIdProvider()` is set, then non conflict messageId is provided.
458458
- `log`: custom log function. Default uses [debug](https://www.npmjs.com/package/debug) package.
459459
- `manualConnect`: prevents the constructor to call `connect`. In this case after the `mqtt.connect` is called you should call `client.connect` manually.
460+
- `timerVariant`: defaults to `auto`, which tries to determine which timer is most appropriate for you environment, if you're having detection issues, you can set it to `worker` or `native`
460461

461462
In case mqtts (mqtt over tls) is required, the `options` object is passed through to [`tls.connect()`](http://nodejs.org/api/tls.html#tls_tls_connect_options_callback). If using a **self-signed certificate**, set `rejectUnauthorized: false`. However, be cautious as this exposes you to potential man in the middle attacks and isn't recommended for production.
462463

src/lib/PingTimer.ts

+16-8
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,40 @@
1-
import timers from './timers'
1+
import getTimer, { type Timer } from './get-timer'
2+
import type { TimerVariant } from './shared'
23

34
export default class PingTimer {
45
private keepalive: number
56

6-
private timer: any
7+
private timerId: number
8+
9+
private timer: Timer
710

811
private checkPing: () => void
912

10-
constructor(keepalive: number, checkPing: () => void) {
13+
constructor(
14+
keepalive: number,
15+
checkPing: () => void,
16+
variant: TimerVariant,
17+
) {
1118
this.keepalive = keepalive * 1000
1219
this.checkPing = checkPing
20+
this.timer = getTimer(variant)
1321
this.reschedule()
1422
}
1523

1624
clear() {
17-
if (this.timer) {
18-
timers.clear(this.timer)
19-
this.timer = null
25+
if (this.timerId) {
26+
this.timer.clear(this.timerId)
27+
this.timerId = null
2028
}
2129
}
2230

2331
reschedule() {
2432
this.clear()
25-
this.timer = timers.set(() => {
33+
this.timerId = this.timer.set(() => {
2634
this.checkPing()
2735
// prevent possible race condition where the timer is destroyed on _cleauUp
2836
// and recreated here
29-
if (this.timer) {
37+
if (this.timerId) {
3038
this.reschedule()
3139
}
3240
}, this.keepalive)

src/lib/client.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
GenericCallback,
3333
IStream,
3434
StreamBuilder,
35+
TimerVariant,
3536
VoidCallback,
3637
nextTick,
3738
} from './shared'
@@ -49,7 +50,7 @@ const setImmediate =
4950
})
5051
}) as typeof globalThis.setImmediate)
5152

52-
const defaultConnectOptions = {
53+
const defaultConnectOptions: IClientOptions = {
5354
keepalive: 60,
5455
reschedulePings: true,
5556
protocolId: 'MQTT',
@@ -59,6 +60,7 @@ const defaultConnectOptions = {
5960
clean: true,
6061
resubscribe: true,
6162
writeCache: true,
63+
timerVariant: 'auto',
6264
}
6365

6466
export type MqttProtocol =
@@ -266,6 +268,10 @@ export interface IClientOptions extends ISecureClientOptions {
266268
will?: IConnectPacket['will']
267269
/** see `connect` packet: https://github.com/mqttjs/mqtt-packet/blob/master/types/index.d.ts#L65 */
268270
properties?: IConnectPacket['properties']
271+
/**
272+
* @description 'auto', set to 'native' or 'worker' if you're having issues with 'auto' detection
273+
*/
274+
timerVariant?: TimerVariant
269275
}
270276

271277
export interface IClientPublishOptions {
@@ -2078,9 +2084,13 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac
20782084

20792085
if (!this.pingTimer && this.options.keepalive) {
20802086
this.pingResp = true
2081-
this.pingTimer = new PingTimer(this.options.keepalive, () => {
2082-
this._checkPing()
2083-
})
2087+
this.pingTimer = new PingTimer(
2088+
this.options.keepalive,
2089+
() => {
2090+
this._checkPing()
2091+
},
2092+
this.options.timerVariant,
2093+
)
20842094
}
20852095
}
20862096

src/lib/get-timer.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import isBrowser, { isWebWorker } from './is-browser'
2+
import { clearTimeout as clearT, setTimeout as setT } from 'worker-timers'
3+
import type { TimerVariant } from './shared'
4+
5+
// dont directly assign globals to class props otherwise this throws in web workers: Uncaught TypeError: Illegal invocation
6+
// See: https://stackoverflow.com/questions/9677985/uncaught-typeerror-illegal-invocation-in-chrome
7+
8+
export interface Timer {
9+
set: typeof setT
10+
clear: typeof clearT
11+
}
12+
13+
const workerTimer: Timer = {
14+
set: setT,
15+
clear: clearT,
16+
}
17+
18+
const nativeTimer: Timer = {
19+
set: (func, time) => setTimeout(func, time),
20+
clear: (timerId) => clearTimeout(timerId),
21+
}
22+
23+
const getTimer = (variant: TimerVariant): Timer => {
24+
switch (variant) {
25+
case 'native': {
26+
return nativeTimer
27+
}
28+
case 'worker': {
29+
return workerTimer
30+
}
31+
case 'auto':
32+
default: {
33+
return isBrowser && !isWebWorker ? workerTimer : nativeTimer
34+
}
35+
}
36+
}
37+
38+
export default getTimer

src/lib/shared.ts

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export type PacketHandler = (
2727
done?: DoneCallback,
2828
) => void
2929

30+
export type TimerVariant = 'auto' | 'worker' | 'native'
31+
3032
export class ErrorWithReasonCode extends Error {
3133
public code: number
3234

src/lib/timers.ts

-15
This file was deleted.

test/pingTimer.ts

+15-8
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ describe('PingTimer', () => {
1616
it('should schedule and clear', () => {
1717
const keepalive = 10 // seconds
1818
const cb = spy()
19-
const pingTimer = new PingTimer(keepalive, cb)
19+
const pingTimer = new PingTimer(keepalive, cb, 'auto')
2020

21-
assert.ok(pingTimer['timer'], 'timer should be created automatically')
21+
assert.ok(pingTimer['timerId'], 'timer should be created automatically')
2222

2323
clock.tick(keepalive * 1000 + 1)
2424
assert.equal(
@@ -29,16 +29,23 @@ describe('PingTimer', () => {
2929
clock.tick(keepalive * 1000 + 1)
3030
assert.equal(cb.callCount, 2, 'should reschedule automatically')
3131
pingTimer.clear()
32-
assert.ok(!pingTimer['timer'], 'timer should not exists after clear()')
32+
assert.ok(
33+
!pingTimer['timerId'],
34+
'timer should not exists after clear()',
35+
)
3336
})
3437

3538
it('should not re-schedule if timer has been cleared in check ping', () => {
3639
const keepalive = 10 // seconds
3740
const cb = spy()
38-
const pingTimer = new PingTimer(keepalive, () => {
39-
pingTimer.clear()
40-
cb()
41-
})
41+
const pingTimer = new PingTimer(
42+
keepalive,
43+
() => {
44+
pingTimer.clear()
45+
cb()
46+
},
47+
'auto',
48+
)
4249

4350
clock.tick(keepalive * 1000 + 1)
4451
assert.equal(
@@ -48,6 +55,6 @@ describe('PingTimer', () => {
4855
)
4956
clock.tick(keepalive * 1000 + 1)
5057
assert.equal(cb.callCount, 1, 'should not re-schedule')
51-
assert.ok(!pingTimer['timer'], 'timer should not exists')
58+
assert.ok(!pingTimer['timerId'], 'timer should not exists')
5259
})
5360
})

0 commit comments

Comments
 (0)