Skip to content

fix(frontend): unsubscribe connection-status subscription in WorkflowWebsocketService.closeWebsocket#4377

Merged
mengw15 merged 5 commits intoapache:mainfrom
officialasishkumar:fix/websocket-status-subscription-leak
Apr 17, 2026
Merged

fix(frontend): unsubscribe connection-status subscription in WorkflowWebsocketService.closeWebsocket#4377
mengw15 merged 5 commits intoapache:mainfrom
officialasishkumar:fix/websocket-status-subscription-leak

Conversation

@officialasishkumar
Copy link
Copy Markdown
Contributor

@officialasishkumar officialasishkumar commented Apr 13, 2026

What changes were proposed in this PR?

WorkflowWebsocketService.openWebsocket subscribed to the internal
webSocketResponseSubject observable to track cluster status and mark the
connection as live, but did not store the returned Subscription. As a
result, closeWebsocket could not unsubscribe it, causing the handler to
accumulate on every call to openWebsocket (e.g. when the user switches
workflows or computing units).

Root cause (before):

// openWebsocket() — subscription discarded, never cleaned up
this.websocketEvent().subscribe(evt => { ... });

// closeWebsocket() — only wsWithReconnectSubscription was torn down
this.wsWithReconnectSubscription?.unsubscribe();

Fix (after):

// openWebsocket()
this.statusUpdateSubscription = this.websocketEvent().subscribe(evt => { ... });

// closeWebsocket()
this.wsWithReconnectSubscription?.unsubscribe();
this.statusUpdateSubscription?.unsubscribe();   // ← new

The new statusUpdateSubscription field is declared as Subscription | undefined;
the ?.unsubscribe() call is safely a no-op when closeWebsocket is called before
openWebsocket was ever invoked.

Any related issues, documentation, discussions?

Closes #4376

How was this PR tested?

Two new unit tests were added to workflow-websocket.service.spec.ts:

"should close the previous status subscription when openWebsocket is called again" uses a lightweight WebSocket test double, calls openWebsocket() twice and closeWebsocket() once, and verifies that the previous statusUpdateSubscription is torn down on each reopen and on close

The existing service-creation test continues to pass.

Was this PR authored or co-authored using generative AI tooling?

No.

@github-actions github-actions bot added fix frontend Changes related to the frontend GUI labels Apr 13, 2026
@chenlica
Copy link
Copy Markdown
Contributor

@mengw15 please review it.

@chenlica chenlica requested a review from mengw15 April 14, 2026 02:56
@chenlica
Copy link
Copy Markdown
Contributor

@Ma77Ball Please review it as well.

@mengw15 mengw15 assigned mengw15 and officialasishkumar and unassigned mengw15 Apr 14, 2026
Copy link
Copy Markdown
Contributor

@mengw15 mengw15 left a comment

Choose a reason for hiding this comment

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

Thanks for the PR! Nice catch! Left some comments.

Copy link
Copy Markdown
Contributor

@Ma77Ball Ma77Ball left a comment

Choose a reason for hiding this comment

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

Overall, I agree with @mengw15's comments and have added one suggestion to help guard against leaked subscriptions.

Copy link
Copy Markdown
Contributor

@mengw15 mengw15 left a comment

Choose a reason for hiding this comment

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

Generally looks good, left one minor comment

…WebsocketService.closeWebsocket

`openWebsocket` subscribed to `websocketEvent()` to track cluster status and
connection health, but did not store the returned `Subscription`.  Because
`closeWebsocket` never called `unsubscribe()` on it, each successive call to
`openWebsocket` (e.g. when the user switches computing units or workflows) added
another live subscriber to the shared `webSocketResponseSubject`, causing:

* a growing number of duplicate `updateConnectionStatus(true)` calls per event,
* repeated `numWorkers` assignments from stale handler closures, and
* a memory leak proportional to the number of websocket reconnections in the
  session.

Fix: store the subscription in the new `statusUpdateSubscription` field (typed as
`Subscription | undefined`) and unsubscribe it at the start of `closeWebsocket`,
alongside the existing `wsWithReconnectSubscription` cleanup.  The `?.unsubscribe()`
safe-navigation operator makes the call a no-op when the field is still `undefined`
(e.g. `closeWebsocket` is called before the socket was ever opened).

Two new unit tests in `workflow-websocket.service.spec.ts` verify that the
subscription is closed after `closeWebsocket` and that repeated closes do not throw.

Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
@officialasishkumar officialasishkumar force-pushed the fix/websocket-status-subscription-leak branch from 320eef3 to eeab1c6 Compare April 16, 2026 14:24
Copy link
Copy Markdown
Contributor

@mengw15 mengw15 left a comment

Choose a reason for hiding this comment

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

LGTM!

Copy link
Copy Markdown
Contributor

@Ma77Ball Ma77Ball left a comment

Choose a reason for hiding this comment

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

LGTM!

@mengw15 mengw15 merged commit b3db209 into apache:main Apr 17, 2026
18 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix frontend Changes related to the frontend GUI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WorkflowWebsocketService leaks subscriptions when openWebsocket is called more than once

4 participants