Skip to content

fix(mobius-socket): add async event dedup#4880

Open
rsarika wants to merge 3 commits intowebex:mobius-socketfrom
rsarika:mobius-socket-dedup
Open

fix(mobius-socket): add async event dedup#4880
rsarika wants to merge 3 commits intowebex:mobius-socketfrom
rsarika:mobius-socket-dedup

Conversation

@rsarika
Copy link
Copy Markdown
Contributor

@rsarika rsarika commented Apr 20, 2026

COMPLETES #https://jira-eng-sjc12.cisco.com/jira/browse/CAI-7843

This pull request addresses

Duplicate async_event messages can be retransmitted by Mobius, which can cause the same event to be processed multiple times by consumers during an active session.

by making the following changes

  • Add session-scoped deduplication for async_event messages in MobiusSocket, keyed by eventId
  • Keep dedup state at the MobiusSocket layer so duplicate suppression survives socket replacement for the same session
  • Bound the dedup cache with oldest-entry eviction instead of clearing the entire cache at capacity
  • Clear the session dedup cache on disconnect() and all caches on disconnectAll()
  • Keep low-level socket behavior focused on parsing and ACKing async_event messages
  • Add unit coverage for duplicate suppression, socket replacement without disconnect, bounded eviction, disconnect cache reset, and flat async envelope parsing

Change Type

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Tooling change
  • Internal code refactor

The following scenarios were tested

  • Targeted unit tests for Mobius socket async event deduplication
  • Targeted unit tests for async event ACK behavior
  • Targeted unit test verifying flat async event envelopes preserve top-level type and eventId
  • Verified git diff --check
  • Verified eslint on the updated source files

Note: the package test run completed successfully for the targeted Mocha tests, but the sandbox also prints unrelated EPERM noise from the helper server attempting to bind to 0.0.0.0:8000.

The GAI Coding Policy And Copyright Annotation Best Practices

  • GAI was not used (or, no additional notation is required)
  • Code was generated entirely by GAI
  • GAI was used to create a draft that was subsequently customized or modified
  • Coder created a draft manually that was non-substantively modified by GAI (e.g., refactoring was performed by GAI on manually written code)
  • Tool used for AI assistance (GitHub Copilot / Other - specify)
    • Github Copilot
    • Other - Please Specify
    • OpenAI Codex
  • This PR is related to
    • Feature
    • Defect fix
    • Tech Debt
    • Automation

I certified that

  • I have read and followed contributing guidelines
  • I discussed changes with code owners prior to submitting this pull request
  • I have not skipped any automated checks
  • All existing and new tests passed
  • I have updated the documentation accordingly

Make sure to have followed the contributing guidelines before submitting.

@rsarika rsarika requested a review from a team as a code owner April 20, 2026 04:24
@rsarika rsarika added validated If the pull request is validated for automation. mobius-wss labels Apr 20, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b12c2b9e5

ℹ️ 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".

const sessionSocket = this.sockets.get(sessionId);
const suffix = sessionId === this.defaultSessionId ? '' : `:${sessionId}`;

this._clearSeenAsyncEventIds(sessionId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear dedup state on server-initiated session teardown

_seenAsyncEventIdsBySession is only purged from explicit disconnect()/disconnectAll() paths, so sessions that are closed by the server and never explicitly disconnected keep their dedup Map indefinitely. In _onclose() we already remove this.sockets entries for those sessions, but the new dedup cache remains reachable and can grow to dedupCacheMaxSize per stale session ID, which creates an unbounded memory leak in multi-session deployments over time.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5ab823af82

ℹ️ 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".

* duplicate async_event messages retransmitted by the server.
* @type {Number}
*/
dedupCacheMaxSize: process.env.MOBIUS_SOCKET_DEDUP_CACHE_MAX_SIZE || 1000,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse dedup cache size as a number

dedupCacheMaxSize is read directly from process.env as a raw string, but _shouldSuppressDuplicateAsyncEvent() compares Map.size numerically against it. If this env var is set to a non-numeric value (for example from a typo), the comparison becomes size > NaN, eviction never runs, and the per-session dedup map can grow unbounded. Please coerce/validate this value to a finite number with a safe fallback so the cache bound is always enforced.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@mkesavan13 mkesavan13 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple of nitpick comments. Approving.

Could you please add a VidCast as well before merging the PR?

Comment on lines +142 to +145
if (!seenAsyncEventIds) {
seenAsyncEventIds = new Map();
this._seenAsyncEventIdsBySession.set(sessionId, seenAsyncEventIds);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be treated as just get and set should happen only in set?

this._seenAsyncEventIdsBySession.clear();
},

_shouldSuppressDuplicateAsyncEvent(sessionId, envelope) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be a rename to reflect the action would help as it does not just check but also set if it is not preexisting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mobius-wss validated If the pull request is validated for automation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants