Feat/mulitple data channel(llm) connections#4661
Conversation
…eat/mulitLLMConnections
…onsWithShutdown # Conflicts: # packages/@webex/internal-plugin-mercury/src/mercury.js
…down' into feat/mulitLLMConnectionsWithShutdown
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0045311f81
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| for (const sessionId of this.sockets.keys()) { | ||
| disconnectPromises.push(this.disconnect(options, sessionId)); | ||
| } |
There was a problem hiding this comment.
Abort in-flight connects when disconnecting all sessions
disconnectAll() only iterates this.sockets.keys(), but connect() now tracks in-flight attempts in _connectPromises before any socket exists (during device.register() and early backoff setup). If logout()/disconnectAll() is called in that window, the session is skipped here and the underlying connect attempt continues, so a socket can still open after the caller believes all sessions were disconnected.
Useful? React with 👍 / 👎.
…down' into feat/mulitLLMConnectionsWithShutdown
rarajes2
left a comment
There was a problem hiding this comment.
- Have reviewed the Mercury plugin. will do another round of review for llm plugin.
- Let's update the README.md for both the llm and mercury plugins with old and new ways to consume the connections.
- If possible please add a vidcast showcasing the working of existing features such as meeting/calling as this is affecting a lot of packages
| * @returns {boolean} True if at least one socket is connected | ||
| */ | ||
| hasConnectedSockets() { | ||
| const socket = this.sockets.get(this.defaultSessionId); |
There was a problem hiding this comment.
- Can we also add support for the provided
sessionIdand default/fallback could bedefaultSessionId? - We can simply return the
socket?.connected
| }, | ||
|
|
||
| // @oneFlight | ||
| connect(webSocketUrl, sessionId = this.defaultSessionId) { |
There was a problem hiding this comment.
- Please add js-doc for this method
- Does this
sessionIdneed to be generated and maintained by Consumer application? See if we can generate the sessionId internally and return the connection object to send and listen for the messages to/from backend where the webSocketUrl will be passed manually from the Application. The default listening on thewebex.internal.mercury.on()should work as expected for the usual connection
There was a problem hiding this comment.
Yes we can generate the sessionId internally if Consumer application tell us which data channel it intend to connect. We can improve this in later PR
| return this._connectWithBackoff(webSocketUrl, sessionId); | ||
| }) | ||
| .finally(() => { | ||
| this._connectPromises.delete(sessionId); |
There was a problem hiding this comment.
Q. Why are we deleting the created promise here ? Shouldn't this be deleted on disconnect ?
There was a problem hiding this comment.
_connectPromises is an internal map (keyed by sessionId) that caches the in-flight “connect” Promise for each Mercury session.
Functionally it is used to:
De-duplicate concurrent connect calls: if connect() (or the internal connect path) is called multiple times for the same sessionId while a connection attempt is already running, the code can return the existing promise from _connectPromises instead of starting another socket open/backoff cycle.
Provide a single completion signal per session: all callers awaiting a connect for that sessionId resolve/reject from the same promise, so state (connected, connecting, socket refs) stays consistent.
Allow cleanup/reset: once the connect attempt finishes (success, permanent failure, or abort/disconnect), the entry should be removed/cleared so a future connect starts a fresh attempt rather than reusing a settled promise.
In short: _connectPromises is the per-session “only one connect attempt at a time” mechanism.
| @@ -361,8 +498,8 @@ const Mercury = WebexPlugin.extend({ | |||
| return Promise.reject(err); | |||
| } | |||
|
|
|||
| if (!isShutdownSwitchover && !this.backoffCall) { | |||
| const msg = `${this.namespace}: prevent socket open when backoffCall no longer defined`; | |||
| if (!isShutdownSwitchover && !backoffCall) { | |||
| const msg = `${this.namespace}: prevent socket open when backoffCall no longer defined for ${sessionId}`; | |||
| const err = new Error(msg); | |||
|
|
|||
There was a problem hiding this comment.
Not same, but can simplify it
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eef7a6d14c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| this.onmessage = this.onmessage.bind(this); | ||
| this.onclose = this.onclose.bind(this); | ||
| // Increase max listeners to avoid memory leak warning in tests | ||
| this.setMaxListeners(5); |
There was a problem hiding this comment.
Increase Socket max listeners above connection baseline
Setting this.setMaxListeners(5) is lower than the number of listeners a normal connection adds: Mercury attaches five listeners in _attachSocketEventListeners() (close, message, pong, sequence-mismatch, ping-pong-latency), and Socket._authorize() adds another message listener with once(). That means each connection now crosses the limit and emits MaxListenersExceededWarning during normal operation, creating noisy diagnostics and masking real leak signals.
Useful? React with 👍 / 👎.
…down' into feat/mulitLLMConnectionsWithShutdown
rarajes2
left a comment
There was a problem hiding this comment.
For calling we should test with the web.webex.com/calling?calling url. The one shown in the vidcast uses the meeting itself
| ```js | ||
| const mercury = webex.internal.mercury; | ||
|
|
||
| // Default session | ||
| await mercury.connect(); | ||
|
|
||
| // Additional session | ||
| await mercury.connect(undefined, 'secondary-session'); | ||
|
|
||
| // Disconnect only one session | ||
| await mercury.disconnect(undefined, 'secondary-session'); | ||
|
|
||
| // Disconnect everything | ||
| await mercury.disconnectAll(); | ||
| ``` |
There was a problem hiding this comment.
Would be nice to have sample code for how to listen to multiple connections
| await mercury.connect(); | ||
|
|
||
| // Additional session | ||
| await mercury.connect(undefined, 'secondary-session'); |
There was a problem hiding this comment.
Q. Instead of undefined we can pass a custom wss url which will be able to connect to any wss server, right?
| await webex.internal.llm.disconnectLLM({code: 1000, reason: 'done'}, 'session-a'); | ||
|
|
||
| // Disconnect all sessions | ||
| await webex.internal.llm.disconnectAllLLM({code: 1000, reason: 'shutdown'}); |
There was a problem hiding this comment.
Same here, it will be nice to have listener logic documented as well
| const SOCKET_READY_STATE = Object.freeze({ | ||
| CONNECTING: 0, | ||
| OPEN: 1, | ||
| CLOSING: 2, | ||
| CLOSED: 3, | ||
| }); |
There was a problem hiding this comment.
Should we create a constants.ts file for consistency ?
| export default // @ts-ignore | ||
| // @ts-ignore |
There was a problem hiding this comment.
@ts-ignore is repeated and seems bit off
| * @returns {Promise<void>} | ||
| */ | ||
| public disconnectAllLLM = (options?: {code: number; reason: string}): Promise<void> => | ||
| this.disconnectAll(options).then(() => { |
There was a problem hiding this comment.
Q. Is this coming from Mercury plugin ?
There was a problem hiding this comment.
meeting-plugin will call the interface
|
https://app.vidcast.io/share/9052d53e-0a6c-4970-a21d-8ddf71fa8ff3 |
rarajes2
left a comment
There was a problem hiding this comment.
Approving the PR. Did a sanity test on the calling sdk. It seems to be working fine with the updated changes in the socket plugins.
Please check one last comment regarding connected state and take a call on how do we want to handle it.
| resolve(); | ||
|
|
||
| // Update overall connected status | ||
| this.connected = this.hasConnectedSockets(); |
There was a problem hiding this comment.
Suppose we have multiple connections alive and if the default connection is closed but other connections are still intact, in that case we are marking here that overall connection is lost. This feels bit misleading.
Should we maintain a map of session IDs including the default one with the connected property and based on that every connection can be decided to be connected or disconnected ?
This change will need to be updated with for the derived ampersand property as well. Shown below:
derived: {
listening: {
deps: ['connected'],
fn() {
return this.connected;
},
},
},Please discuss this with your team and take a call on this.
🎉 Your changes are now available!Released in: Thank you for your contribution! 🤖 This is an automated message. For questions, please refer to the documentation. |
Co-authored-by: Shreyas Sharma <72344404+Shreyas281299@users.noreply.github.com>
Co-authored-by: Shreyas Sharma <72344404+Shreyas281299@users.noreply.github.com>

COMPLETES #< INSERT LINK TO ISSUE >
https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-697743
This pull request addresses
To support the 5k webinar, client needs to keep the main LLM connection after entring practice session with practice session LLM connection. So client needs to maintain more than one LLM connections in a single meeting. Currently the llm and mercury lib only support one connection at same time. We need a way to implement multiple data channel (LLM) connections at same time.
by making the following changes
Consider the backward compatibility, I use a map to manage mulitple socket connections with different session ids. If no session id passed when connect/disconnect, we will use default session id by which the lib can be compatibility with previous caller.
Change Type
The following scenarios were tested
Join meeting, check CC, reaction, whiteboard feature can work normally
The GAI Coding Policy And Copyright Annotation Best Practices
I certified that
Make sure to have followed the contributing guidelines before submitting.