Frontend leaks y-websocket (/rtc) connections, exhausting the shared-editing connection cap and breaking collaborative cursors
- Dominant language
- Scala
- Stars
- 314
- Forks
- 187
- Avg merge
- 1d 21h
- Merged PRs (30d)
- 214
Description
### What happened?
The Texera frontend leaks `y-websocket` (`/rtc`) connections to the shared-editing server. During a collaborative editing session (roughly a dozen users co-editing at once), the number of established connections to the y-websocket server grows far out of proportion to the number of users and keeps climbing while people edit. Once a gateway/proxy's per-cluster connection limit is reached, new upstream connections are refused, so users **stop seeing each other's cursors** and awareness/presence stops syncing for anyone who (re)connects.
Symptoms:
- Tens of connections per user (far more than the expected ~1 per open tab).
- The connection count **climbs in real time while users edit**.
- It does **not** drop when a user leaves the workspace, and it **refills within seconds after the y-websocket server restarts** — because the leaked providers are detached, self-reconnecting objects living in open browser tabs.
The y-websocket server itself is not resource-constrained — this is a client-side connection leak, not a server capacity problem.
**Root cause — two distinct leaks, both on the `SharedModel` → `WebsocketProvider` code path:**
**Leak B (dominant, per-edit).** `WorkflowCompilingService` runs a compile-on-edit subscription that, on *every* operator/link/property edit, builds a throwaway `WorkflowGraph` for validation (`ValidationWorkflowService.getValidTexeraGraph()`). `WorkflowGraph`'s constructor **unconditionally** creates a `SharedModel`, which **unconditionally** opens a new `/rtc` WebSocket (auto-connecting, random room). The throwaway graph is read once (by `ExecuteWorkflowService.getLogicalPlanRequest`) and **never destroyed** → a brand-new WebSocket leaks on every edit and reconnects forever. A user building a 20-operator workflow generates dozens of edits → dozens of permanent sockets. This drives the disproportionate per-user count and the real-time climb.
**Leak A (secondary, per-navigation).** `SharedModel.destroy()` calls `wsProvider.disconnect()` (and only when currently connected) instead of `wsProvider.destroy()`. In `y-websocket@1.4.0`, `disconnect()` does **not** clear the provider's internal reconnect timer (`_checkInterval`), so the provider is never garbage-collected and — if it wasn't OPEN at destroy time (e.g. mid-reconnect during a workflow switch) — keeps reconnecting → a zombie socket per workflow switch/navigation.
Relevant source (all under `frontend/src/app/workspace/service/`):
- `workflow-graph/model/shared-model.ts` — the only `new WebsocketProvider` construction site; buggy `destroy()`.
- `workflow-graph/model/workflow-graph.ts` — constructor unconditionally does `new SharedModel()`.
- `validation/validation-workflow.service.ts` — `getValidTexeraGraph()` builds the throwaway graph per edit.
- `compile-workflow/workflow-compiling.service.ts` — the compile-on-edit subscription that drives it.
**Expected:** Opening a workflow should establish exactly **one** `/rtc` WebSocket. Editing (add/delete operators, edit properties, add/remove links) should not open any additional sockets. Switching or closing a workflow should tear the socket down cleanly (no lingering/reconnecting sockets).
### How to reproduce?
Client-side (browser DevTools → Network → **WS** filter):
1. Open a workflow. Observe **one** `/rtc` WebSocket.
2. Add/delete operators, edit operator properties, add/remove links repeatedly. **Observed:** a new `/rtc` WS appears on each edit and stays open (and keeps reconnecting), instead of the count staying at one.
3. Switch workflow A → B, or navigate away / close the tab. **Observed:** old `/rtc` sockets linger and reconnect instead of closing.
The same leak can be observed server-side by counting established connections to the y-websocket server while a single user edits a workflow: the count climbs steadily with each edit and never falls, even though only one browser tab is open.
### Version/Branch
1.3.0-incubating-SNAPSHOT (main)
### Commit Hash (Optional)
_Both leaks are present in current `main` and reproduce with `y-websocket@1.4.0` / `yjs@13.5.41`._
### What browsers are you seeing the problem on?
Chrome
### Relevant log output
```shell
```
Contributor guide
Research direction
Start with frontend/src/app/workspace/service/workflow-graph/model/shared-model.ts and workflow-graph.ts, then trace getValidTexeraGraph() in validation/validation-workflow.service.ts from the compile-on-edit subscription in compile-workflow/workflow-compiling.service.ts. Reproduce with the browser DevTools WS filter while editing and switching workflows. Done means one /rtc socket per open workflow, no new sockets during edits, and no lingering or reconnecting sockets after teardown.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend, networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 64/100