-
Notifications
You must be signed in to change notification settings - Fork 384
feat(mercury): handle mercury shutdown #4497
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: next
Are you sure you want to change the base?
Conversation
@CodeRabbit review |
This pull request is automatically being deployed by Amplify Hosting (learn more). |
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.
- Are we not adding/updating UTs ?
- Please add vidcast
|
||
if (this.webex.config.defaultMercuryOptions) { | ||
this.logger.info(`${this.namespace}: setting custom options for switchover`); | ||
options = {...options, ...this.webex.config.defaultMercuryOptions}; |
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.
Order of spreading the options seems incorrect. The default options should come first so that the later can override it.
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.
Thanks for catching this
} | ||
this._shutdownSwitchoverInProgress = true; | ||
this._shutdownSwitchoverId = `${Date.now()}`; | ||
this.logger.info( |
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.
this.logger.log
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.
In this file the deafult has been logger.info for the existint code. I feel we can keep this as info only.
return pendingSocket.open(webSocketUrl, options); | ||
}) | ||
.then(() => { | ||
this.logger.info( |
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.
Same here. We were keeping info only at the beginning of the function
`${this.namespace}: [shutdown] switchover start, id=${this._shutdownSwitchoverId}` | ||
); | ||
|
||
const pendingSocket = new Socket(); |
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.
Let's rename it newSocket
|
||
Promise.all([this._prepareUrl(), this.webex.credentials.getUserToken()]) | ||
.then(([webSocketUrl, token]) => { | ||
attemptWSUrl = webSocketUrl; |
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.
attemptWSUrl
-> newWSUrl
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.
Updated
pendingSocket.on('close', (event) => this._onclose(event, pendingSocket)); | ||
pendingSocket.on('message', (...args) => this._onmessage(...args)); | ||
pendingSocket.on('pong', (...args) => this._setTimeOffset(...args)); | ||
pendingSocket.on('sequence-mismatch', (...args) => this._emit('sequence-mismatch', ...args)); | ||
pendingSocket.on('ping-pong-latency', (...args) => this._emit('ping-pong-latency', ...args)); |
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.
This seems like repetitive code. We can create a method which accepts the socket param and adds these listeners and call that method in other paces too
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.
Made a new method for adding the event listeners
// Do not force-close oldSocket; server will close it with 4001. | ||
if (oldSocket) { | ||
this.logger.info( | ||
`${this.namespace}: [shutdown] old socket retained; awaiting 4001 close` | ||
); | ||
} |
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.
Q. Is 4001
a status code? We can improve the message
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.
Yes, 4001 is 'replacement of the connection due to shutdown'. Updated the message
/* eslint complexity: [0] */ | ||
|
||
try { | ||
const isActiveSocket = !sourceSocket || (this.socket && sourceSocket === this.socket); |
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.
The sourceSocket
passed is a new socket created (line number 300) which will never be undefined so the first condition will always return false and proceed with the next condition. Please correct if I am wrong
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 think we are already updating the this.socket
with the new one or active one. Shouldn't we be able to call the close on it directly ?
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.
So in this condition all we need to know is that
- If the closing socket is the current socket (this is a normal closing of mercury connection)
- If the closing socket is an older inactive socket (this is the case of mercury shutdown)
Simplified the condition
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 think I got confused with the name activeSocket
. I thought it means the socket is active but here it means that it is the currently being used socket.
try { | ||
this._emit('event:mercury_shutdown_imminent', envelope); | ||
} catch (e) { | ||
// ignore observer errors | ||
} |
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.
Same here. Try-catch not needed
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.
Removed
typeof data.sequenceNumber === 'number' || | ||
(typeof data.sequenceNumber === 'string' && data.sequenceNumber.trim() !== '') | ||
) { | ||
const sequenceNumber = parseInt(data.sequenceNumber, 10); |
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.
Are we getting sequenceNumber in different number system ? We are already checking the type. I think the parsing should have been done before the condition
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.
Did we test calling flows when the shutdown occurs to see if we have any impact there due to connection replacement ?
Please update the description with details of testing done for this change and check the boxes required |
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.
- Unit test is failing mercury plugin
- Did we test calling flows with mercury shutdown ?
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.
Did we test this by locally linking the web client once? We need one of WWC member's review as well on this one.
this._emit('offline', event); | ||
this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(false); | ||
let socketUrl; | ||
if (isActiveSocket && this.socket) { |
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.
isActiveSocket
is already ensuring that the this.socket
has some value so we don't need to add both the checks.
Yes, Ive locally linked and tested a call and a meeting in the WWC |
.then(([webSocketUrl, token]) => { | ||
newWSUrl = webSocketUrl; | ||
|
||
let options = { |
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.
these options are a complete duplicate of the code in the open method. This could have been factored out to a private method. In fact, most of what is in this .then could have been factored out. It only really differs by the log strings
|
||
this.logger.info(`${this.namespace}: [shutdown] switchover url: ${webSocketUrl}`); | ||
|
||
return newSocket.open(webSocketUrl, options); |
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.
if the socket fails to open, I think it won't ever have the event listeners removed. You should call removeAllListeners on the socket in the catch
if (isActiveSocket) { | ||
// Only tear down state if the currently active socket closed | ||
if (this.socket) { | ||
this.socket.removeAllListeners(); |
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.
question: When are the listeners removed from the old socket?
|
||
const oldSocket = this.socket; | ||
// Atomically switch active socket reference without closing the old one. | ||
this.socket = newSocket; |
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.
There's a potential race condition here. If the old mercury websocket disconnects before it reaches this line, then the plugin will still emit the offline. This code won't emit the online either so the consuming application would not realise that it has reconnected
|
||
this.logger.info(`${this.namespace}: [shutdown] switchover url: ${webSocketUrl}`); | ||
|
||
return newSocket.open(webSocketUrl, options); |
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.
This directly connects the websocket where as the first connection uses the backoff strategy. Is this deliberate? Are we not worried that this reconnection attempt is more fragile than the normal connection?
COMPLETES # https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6913
This pull request addresses
by making the following changes
Change Type
The following scenarios were tested
The GAI Coding Policy And Copyright Annotation Best Practices
I certified that
Make sure to have followed the contributing guidelines before submitting.