-
Notifications
You must be signed in to change notification settings - Fork 2k
fix(client): emit termination event when reconnecting stops #3433
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
b2705d2
6b95163
87a1fc7
36a1037
a04d4f8
104bc2e
f74c465
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -205,6 +205,13 @@ const retryIn = strategy(retries, cause); | |
| }; | ||
| } | ||
|
|
||
| /** | ||
| * The single choke point where `reconnectStrategy` giving up (`false` or an | ||
| * `Error`) is handled: closes the socket for good and emits `'terminated'` | ||
| * so a caller reacting only to `'error'` — which also fires on every | ||
| * *retried* disconnect — can tell "still retrying" apart from "reconnection | ||
| * has permanently stopped, the client is unusable from here on". | ||
| */ | ||
| #shouldReconnect(retries: number, cause: Error) { | ||
| const retryIn = this.#reconnectStrategy(retries, cause); | ||
| if (retryIn === false) { | ||
|
|
@@ -215,6 +222,7 @@ const retryIn = strategy(retries, cause); | |
| internal: false, | ||
| clientId: this.#clientId | ||
| })); | ||
| this.emit('terminated', cause); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an already-ready connection dies, Useful? React with 👍 / 👎. |
||
| this.emit('error', cause); | ||
| return cause; | ||
| } else if (retryIn instanceof Error) { | ||
|
|
@@ -225,8 +233,10 @@ const retryIn = strategy(retries, cause); | |
| internal: false, | ||
| clientId: this.#clientId | ||
| })); | ||
| const terminatedBy = new ReconnectStrategyError(retryIn, cause); | ||
| this.emit('terminated', terminatedBy); | ||
| this.emit('error', cause); | ||
| return new ReconnectStrategyError(retryIn, cause); | ||
| return terminatedBy; | ||
| } | ||
|
|
||
| return retryIn; | ||
|
|
@@ -397,22 +407,48 @@ const retryIn = strategy(retries, cause); | |
| this.#isReady = false; | ||
| const socket = this.#socket; | ||
| this.#socket = undefined; | ||
| publish(CHANNELS.ERROR, () => ({ | ||
| error: err, | ||
| origin: 'client', | ||
| internal: false, | ||
| clientId: this.#clientId | ||
| })); | ||
| this.emit('error', err); | ||
|
|
||
| socket?.removeAllListeners('data'); | ||
| socket?.destroy(); | ||
|
|
||
| if (wasReady) { | ||
| publish(CHANNELS.CONNECTION_CLOSED, () => ({ clientId: this.#clientId, reason: 'error', wasConnected: true })); | ||
| } | ||
|
|
||
| if (!wasReady || !this.#isOpen || typeof this.#shouldReconnect(0, err) !== 'number') return; | ||
| if (!wasReady) { | ||
| if (!this.#isOpen) { | ||
| publish(CHANNELS.ERROR, () => ({ | ||
| error: err, | ||
| origin: 'client', | ||
| internal: false, | ||
| clientId: this.#clientId | ||
| })); | ||
| this.emit('error', err); | ||
| } | ||
| return; | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| if (!this.#isOpen) { | ||
| publish(CHANNELS.ERROR, () => ({ | ||
| error: err, | ||
| origin: 'client', | ||
| internal: false, | ||
| clientId: this.#clientId | ||
| })); | ||
| this.emit('error', err); | ||
| return; | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| const retryIn = this.#shouldReconnect(0, err); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an established connection fails and the custom Useful? React with 👍 / 👎. |
||
| if (typeof retryIn !== 'number') return; | ||
|
|
||
| publish(CHANNELS.ERROR, () => ({ | ||
| error: err, | ||
| origin: 'client', | ||
| internal: false, | ||
| clientId: this.#clientId | ||
| })); | ||
| this.emit('error', err); | ||
| if (!this.#isOpen) return; | ||
|
|
||
| this.emit('reconnecting'); | ||
| this.#connect().catch(() => { | ||
|
Comment on lines
436
to
437
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a ready connection fails with a numeric retry strategy and an Useful? React with 👍 / 👎. |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a previously connected client has an active streaming-credentials subscription or was registered with the initialized OpenTelemetry
ClientRegistry, this callback only forwards the terminal event and leaves those client-level resources alive. Their cleanup occurs only inRedisClient.destroy()(index.ts:2236-2238), so an application that follows the new documentation by dropping this client and creating a replacement without explicitly destroying the old one leaves a registry handle that strongly retains the client and may leave its credential observer subscribed. Dispose these resources on the terminal path or explicitly requiredestroy()before replacement.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kept cleanup explicit rather than doing it in the event forwarding callback. terminated reports that reconnection has ended, while destroy() remains the client lifecycle operation that releases credentials and metrics resources. The documentation now calls destroy() before replacing the client, and the example uses it.