Description
Description
Hello, we noticed in our logs that node-redis was able to get into a broken state during a ongoing networking issue causing socket closures. The last logs from some machines were the "Socket closed unexpectedly" error message, with no follow up reconnect event. We also noticed that some machines were firing multiple connecting / reconnecting events in parallel for the same client.
I believe there is an issue in error handling when a socket is closed during the #initiator promise in #connect.
this.#socket = await this.#createSocket();
this.#writableNeedDrain = false;
this.emit('connect');
try {
await this.#initiator(); // <------ Socket closed here results in multiple #connect chains.
} catch (err) {
On that line, the state of socket is:
- isOpen - true
- #socket - set as expected
Looking at createSocket:
.once('close', hadError => {
if (!hadError && this.#isOpen && this.#socket === socket) {
this.#onSocketError(new SocketClosedUnexpectedlyError());
}
})
- hasError can be falsey
- isOpen is true - we were in the middle of #connect
- #socket === socket is true, we had just set the instance variable
And finally looking at #onSocketError
#onSocketError(err: Error): void {
this.#isReady = false;
this.emit('error', err);
if (!this.#isOpen || typeof this.#shouldReconnect(0, err) !== 'number') return;
this.emit('reconnecting');
this.#connect().catch(() => {
// the error was already emitted, silently ignore it
});
}
- isOpen is true, we've established that
- shouldReconnect will always be a number if no option is provided
So we see that the socket close event will cause this.#connect to be called again, even though we are in the middle of connecting to begin with.
A potential solution would be to check #isReady in onSocketError to know whether we are already in the process of connecting or not.
Node.js Version
lts/gallium
Redis Server Version
No response
Node Redis Version
4.3.1, but source taken from 4.6.5
Platform
linux
Logs
23:45:43.678 redis client error Socket closed unexpectedly
23:45:43.678 redis client is reconnecting
23:45:43.678 redis client is connecting
23:45:58.694 redis client error Socket closed unexpectedly
23:45:58.694 redis client error Socket closed unexpectedly
23:53:40.370 redis client error read ECONNRESET
23:53:40.370 redis client is reconnecting
23:53:40.370 redis client error read ECONNRESET
23:53:40.370 redis client is reconnecting
23:53:40.370 redis client is connecting
23:53:40.370 redis client is ready
23:53:41.371 redis client error Connection timeout
23:53:41.371 redis client is reconnecting
23:53:43.373 redis client error Connection timeout
23:53:45.375 redis client error Connection timeout
23:54:11.402 redis client error Socket closed unexpectedly
23:54:11.402 redis client is reconnecting
23:54:11.402 redis client is connecting
23:54:26.416 redis client error Socket closed unexpectedly
23:54:26.416 redis client is reconnecting
23:54:26.416 redis client error Socket closed unexpectedly
23:54:26.416 redis client is reconnecting
23:54:26.416 redis client is connecting
23:54:26.416 redis client is ready
23:54:26.416 redis client is connecting
23:54:26.416 redis client is ready