Skip to content

Commit 470e415

Browse files
committed
fix: deferring pings should only delay by at most one interval
1 parent 6455902 commit 470e415

File tree

1 file changed

+26
-12
lines changed

1 file changed

+26
-12
lines changed

lib/client.js

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const { randomUUID} = require('crypto');
22
const { EventEmitter, once } = require('events');
33
const { setTimeout } = require('timers/promises');
4+
const { setTimeout: setTimeoutCb } = require('timers');
45
const WebSocket = require('ws');
56
const { ExponentialStrategy } = require('backoff');
67
const { CONNECTING, OPEN, CLOSING, CLOSED } = WebSocket;
@@ -30,7 +31,6 @@ class RPCClient extends EventEmitter {
3031
this._keepAliveAbortController = undefined;
3132
this._pendingPingResponse = false;
3233
this._lastPingTime = 0;
33-
this._skipNextPing = false;
3434
this._closePromise = undefined;
3535
this._protocolOptions = [];
3636
this._protocol = undefined;
@@ -398,12 +398,12 @@ class RPCClient extends EventEmitter {
398398
ws.on('error', err => this.emit('socketError', err));
399399
ws.on('ping', () => {
400400
if (this._options.deferPingsOnActivity) {
401-
this._skipNextPing = true;
401+
this._deferNextPing();
402402
}
403403
});
404404
ws.on('pong', () => {
405405
if (this._options.deferPingsOnActivity) {
406-
this._skipNextPing = true;
406+
this._deferNextPing();
407407
}
408408
this._pendingPingResponse = false;
409409
const rtt = Date.now() - this._lastPingTime;
@@ -557,10 +557,24 @@ class RPCClient extends EventEmitter {
557557
return this._connectPromise;
558558
}
559559

560+
_deferNextPing() {
561+
if (!this._nextPingTimeout) {
562+
return;
563+
}
564+
565+
this._nextPingTimeout.refresh();
566+
}
567+
560568
async _keepAlive() {
561569
// abort any previously running keepAlive
562570
this._keepAliveAbortController?.abort();
563571

572+
const timerEmitter = new EventEmitter();
573+
const nextPingTimeout = setTimeoutCb(()=>{
574+
timerEmitter.emit('next')
575+
}, this._options.pingIntervalMs);
576+
this._nextPingTimeout = nextPingTimeout;
577+
564578
try {
565579
if (this._state !== OPEN) {
566580
// don't start pinging if connection not open
@@ -574,20 +588,16 @@ class RPCClient extends EventEmitter {
574588

575589
// setup new abort controller
576590
this._keepAliveAbortController = new AbortController();
577-
591+
578592
while (true) {
579-
await setTimeout(this._options.pingIntervalMs, undefined, {signal: this._keepAliveAbortController.signal});
580-
593+
await once(timerEmitter, 'next', {signal: this._keepAliveAbortController.signal}),
594+
this._keepAliveAbortController.signal.throwIfAborted();
595+
581596
if (this._state !== OPEN) {
582597
// keepalive no longer required
583598
break;
584599
}
585600

586-
if (this._skipNextPing) {
587-
this._skipNextPing = false;
588-
continue;
589-
}
590-
591601
if (this._pendingPingResponse) {
592602
// we didn't get a response to our last ping
593603
throw Error("Ping timeout");
@@ -596,13 +606,17 @@ class RPCClient extends EventEmitter {
596606
this._lastPingTime = Date.now();
597607
this._pendingPingResponse = true;
598608
this._ws.ping();
609+
nextPingTimeout.refresh();
599610
}
600611

601612
} catch (err) {
613+
// console.log('keepalive failed', err);
602614
if (err.name !== 'AbortError') {
603615
// throws on ws.ping() error
604616
this._ws.terminate();
605617
}
618+
} finally {
619+
clearTimeout(nextPingTimeout);
606620
}
607621
}
608622

@@ -648,7 +662,7 @@ class RPCClient extends EventEmitter {
648662

649663
_onMessage(buffer) {
650664
if (this._options.deferPingsOnActivity) {
651-
this._skipNextPing = true;
665+
this._deferNextPing();
652666
}
653667

654668
const message = buffer.toString('utf8');

0 commit comments

Comments
 (0)