-
Notifications
You must be signed in to change notification settings - Fork 683
fix(socket-mode): observe reconnect rejections #2657
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -39,6 +39,42 @@ describe('SocketModeClient', () => { | |
| it('should resolve once Disconnected state emitted'); | ||
| }); | ||
|
|
||
| describe('reconnect', () => { | ||
| it('should observe a rejected reconnect attempt', async () => { | ||
| const logger = new ConsoleLogger(); | ||
| const errorLog = sandbox.stub(logger, 'error'); | ||
| const client = new SocketModeClient({ | ||
| appToken: 'xapp-', | ||
| clientPingTimeout: 1, | ||
| logger, | ||
| }); | ||
| sandbox.stub(client, 'start').rejects(new Error('reconnect failed')); | ||
|
|
||
| client.emit('close'); | ||
| await sleep(10); | ||
|
|
||
| sinon.assert.calledWith(errorLog, 'Failed to reconnect Socket Mode client: Error: reconnect failed'); | ||
|
Member
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. suggestion: We should add a second test case here that covers the |
||
| }); | ||
|
|
||
| it('should not reconnect or log an error while shutting down', async () => { | ||
| const logger = new ConsoleLogger(); | ||
| const errorLog = sandbox.stub(logger, 'error'); | ||
| const client = new SocketModeClient({ | ||
| appToken: 'xapp-', | ||
| clientPingTimeout: 1, | ||
| logger, | ||
| }); | ||
| const start = sandbox.stub(client, 'start').resolves({}); | ||
|
|
||
| client.emit('close'); | ||
| await client.disconnect(); | ||
| await sleep(10); | ||
|
|
||
| sinon.assert.notCalled(start); | ||
| sinon.assert.notCalled(errorLog); | ||
| }); | ||
| }); | ||
|
|
||
| describe('onWebSocketMessage', () => { | ||
| // While this method is protected and cannot be invoked directly, emitting the 'message' event directly invokes it | ||
| describe('slash_commands messages', () => { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,8 @@ import { SlackWebSocket, WS_READY_STATES } from './SlackWebSocket'; | |
| import type { SocketModeOptions } from './SocketModeOptions'; | ||
| import { UnrecoverableSocketModeStartError } from './UnrecoverableSocketModeStartError'; | ||
|
|
||
| class SocketModeClientShuttingDownError extends Error {} | ||
|
|
||
| // Lifecycle events as described in the README | ||
| enum State { | ||
| Connecting = 'connecting', | ||
|
|
@@ -142,7 +144,11 @@ export class SocketModeClient extends EventEmitter { | |
| this.on('close', () => { | ||
| // Underlying WebSocket connection was closed, possibly reconnect. | ||
| if (!this.shuttingDown && this.autoReconnectEnabled) { | ||
| this.delayReconnectAttempt(this.start); | ||
| void this.delayReconnectAttempt(this.start).catch((error) => { | ||
| if (!(error instanceof SocketModeClientShuttingDownError)) { | ||
| this.logger.error(`Failed to reconnect Socket Mode client: ${error}`); | ||
|
Member
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. praise: Nice - the |
||
| } | ||
| }); | ||
| } else { | ||
| // If reconnect is disabled or user explicitly called `disconnect()`, emit a disconnected state. | ||
| this.emit(State.Disconnected); | ||
|
|
@@ -228,14 +234,15 @@ export class SocketModeClient extends EventEmitter { | |
| this.numOfConsecutiveReconnectionFailures += 1; | ||
| const msBeforeRetry = this.clientPingTimeoutMS * this.numOfConsecutiveReconnectionFailures; | ||
| this.logger.debug(`Before trying to reconnect, this client will wait for ${msBeforeRetry} milliseconds`); | ||
| return new Promise((res, _rej) => { | ||
| return new Promise((resolve, reject) => { | ||
| setTimeout(() => { | ||
| if (this.shuttingDown) { | ||
| this.logger.debug('Client shutting down, will not attempt reconnect.'); | ||
|
Member
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. suggestion: We should handle this differently. Resolving with If A couple of options come to mind:
|
||
| reject(new SocketModeClientShuttingDownError('Socket Mode client is shutting down')); | ||
| } else { | ||
| this.logger.debug('Continuing with reconnect...'); | ||
| this.emit(State.Reconnecting); | ||
| cb.apply(this).then(res); | ||
| cb.apply(this).then(resolve, reject); | ||
| } | ||
| }, msBeforeRetry); | ||
| }); | ||
|
|
||
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.
praise: Really appreciate having a regression test alongside the fix - it locks in the main failure mode from #2656.