Skip to content

Commit

Permalink
fix: possible race condition in ping timer
Browse files Browse the repository at this point in the history
Ref: #1845
  • Loading branch information
robertsLando committed Apr 19, 2024
1 parent a24cf14 commit e991fa5
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 20 deletions.
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 20 additions & 6 deletions src/lib/PingTimer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export default class PingTimer {

private checkPing: () => void

private destroyed = false

constructor(
keepalive: number,
checkPing: () => void,
Expand All @@ -21,22 +23,34 @@ export default class PingTimer {
this.reschedule()
}

clear() {
private clear() {
if (this.timerId) {
this.timer.clear(this.timerId)
this.timerId = null
}
}

destroy() {
this.clear()
this.destroyed = true
}

reschedule() {
if (this.destroyed) {
return
}

this.clear()
this.timerId = this.timer.set(() => {
this.checkPing()
// prevent possible race condition where the timer is destroyed on _cleauUp
// and recreated here
if (this.timerId) {
this.reschedule()
// this should never happen, but just in case
if (this.destroyed) {
return
}

this.checkPing()
// this must be called after `checkPing` otherwise in case `destroy`
// is called in `checkPing` the timer would be rescheduled anyway
this.reschedule()
}, this.keepalive)
}
}
8 changes: 4 additions & 4 deletions src/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -659,9 +659,9 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac
this.log('close :: clearing connackTimer')
clearTimeout(this.connackTimer)

this.log('close :: clearing ping timer')
this.log('close :: destroy ping timer')
if (this.pingTimer) {
this.pingTimer.clear()
this.pingTimer.destroy()
this.pingTimer = null
}

Expand Down Expand Up @@ -1783,8 +1783,8 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac
}

if (this.pingTimer) {
this.log('_cleanUp :: clearing pingTimer')
this.pingTimer.clear()
this.log('_cleanUp :: destroy pingTimer')
this.pingTimer.destroy()
this.pingTimer = null
}

Expand Down
13 changes: 9 additions & 4 deletions test/pingTimer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('PingTimer', () => {
clock.restore()
})

it('should schedule and clear', () => {
it('should schedule and destroy', () => {
const keepalive = 10 // seconds
const cb = spy()
const pingTimer = new PingTimer(keepalive, cb, 'auto')
Expand All @@ -28,10 +28,15 @@ describe('PingTimer', () => {
)
clock.tick(keepalive * 1000 + 1)
assert.equal(cb.callCount, 2, 'should reschedule automatically')
pingTimer.clear()
pingTimer.destroy()
assert.ok(
!pingTimer['timerId'],
'timer should not exists after clear()',
'timer should not exists after destroy()',
)

assert.ok(
pingTimer['destroyed'],
'timer should have `destroyed` set to true after destroy()',
)
})

Expand All @@ -41,7 +46,7 @@ describe('PingTimer', () => {
const pingTimer = new PingTimer(
keepalive,
() => {
pingTimer.clear()
pingTimer.destroy()
cb()
},
'auto',
Expand Down

0 comments on commit e991fa5

Please sign in to comment.