eugenegujing opened a new issue, #8853:
URL: https://github.com/apache/texera/issues/8853

   ### What happened?
   
   `WorkflowWebsocketService` learns that the connection to the computing unit 
is lost only through `retryWhen`, that is, only when the socket **errors**. 
   
   ```ts
   // 
frontend/src/app/workspace/service/workflow-websocket/workflow-websocket.service.ts:117-136
       this.websocket = webSocket<TexeraWebsocketEvent | 
TexeraWebsocketRequest>(websocketUrl);
       // setup reconnection logic
       const wsWithReconnect = this.websocket.pipe(
         retryWhen(errors =>
           errors.pipe(
             tap(_ => this.updateConnectionStatus(false)), // update connection 
status
             tap(_ =>
               console.log(`websocket connection lost, reconnecting in 
${WS_RECONNECT_INTERVAL_MS / 1000} seconds`)
             ),
             delayWhen(_ => timer(WS_RECONNECT_INTERVAL_MS)), // reconnect 
after delay
             tap(_ => {
               this.send("HeartBeatRequest", {}); // try to send heartbeat 
immediately after reconnect
             })
           )
         )
       );
       // set up event listener on re-connectable websocket observable
       this.wsWithReconnectSubscription = wsWithReconnect.subscribe(event =>
         this.webSocketResponseSubject.next(event as TexeraWebsocketEvent)
       );
   ```
   
   When the server closes the socket with a close frame, rxjs 7.8.2 (pinned in 
`frontend/package.json`) does not error the subject. It completes it:
   
   ```js
   // 
frontend/node_modules/rxjs/dist/cjs/internal/observable/dom/WebSocketSubject.js:192-205
           socket.onclose = function (e) {
               if (socket === _this._socket) {
                   _this._resetState();
               }
               var closeObserver = _this._config.closeObserver;
               if (closeObserver) {
                   closeObserver.next(e);
               }
               if (e.wasClean) {
                   observer.complete();
               }
               else {
                   observer.error(e);
               }
   ```
   
   A completion passes straight through `retryWhen`. So after a clean close:
   
   - `updateConnectionStatus(false)` is never called, and `isConnected` (a 
`BehaviorSubject` read) stays `true`.
   - Nothing redials, because `wsWithReconnectSubscription` has simply 
completed.
   - `send()` calls `this.websocket?.next(...)`. With no open socket the frame 
lands in the `ReplaySubject` buffer that `_resetState()` just replaced; it is 
flushed only when the same subject connects again, and nothing makes it connect 
again. Every later request is silently dropped, including the 10 s heartbeat, 
so the heartbeat never exposes the dead socket either: no code checks whether a 
`HeartBeatResponse` comes back.
   - `ComputingUnitStatusService.monitorConnectionStatus()` re-fetches the unit 
list only on a connection-status change, so that does not happen either.
   
   Every control that gates on `isConnected` now treats a dead socket as live:
   
   - the "Connecting" branch of `getRunButtonBehavior()` (`menu.component.ts`, 
the `!this.workflowWebsocketService.isConnected` check) is skipped, so the run 
button stays enabled;
   - the kill button's `[disabled]` binding (`menu.component.html`) keeps it 
enabled mid-run, and the kill request goes into the dead buffer;
   - the connection badge (`menu.component.html`, `nz-badge` coloured by 
`isConnected && Running`) stays green with the tooltip "Connected to Computing 
Unit";
   - the Form View's Stop button from #7944 (`workflow-form.component.ts`, 
`isRunning && isConnected`) is enabled only because `isConnected` is wrong.
   
   A server closes the socket cleanly whenever it shuts down gracefully (a 
restart or redeploy of the service behind `/wsapi`, a Kubernetes pod being 
deleted or evicted); Jetty sends a 1001 close frame for every open session on 
shutdown. An unclean close (code 1006, e.g. the process is killed) already 
errors the subject and redials after 3 s, so only the graceful case is broken.
   
   ### How to reproduce?
   
   Reproduced in Chrome on a locally running stack. The clean close was 
produced from the browser by closing the raw workflow socket with a normal 
close code, which the browser reports with `wasClean: true`, the same path a 
server-side close frame takes.
   
   1. Open a workflow in the workspace with a Running computing unit selected. 
The `/wsapi/workflow-websocket` socket is open and the connection badge is 
green.
   2. Close that socket cleanly.
   3. The socket is closed, but no new socket is opened (the reconnect delay is 
3 s), the badge stays green, and the "websocket connection lost, reconnecting" 
line never appears in the console. Nothing changes however long the page stays 
open.
   
   Server side (not yet run): with a workflow open and connected, stop the 
service behind `/wsapi` gracefully (for example the IDE's Stop button on the 
computing-unit master) and start it again. The workflow socket should close 
with code 1001 and never reopen; the badge stays green; clicking Run sends 
nothing.
   
   Unit level: in `workflow-websocket.service.spec.ts`, open a socket with the 
spec's `FakeWebSocket` and fire `onclose` with `wasClean: true`. `isConnected` 
stays `true` and no new socket opens; with `wasClean: false` the existing retry 
path runs and a second socket opens after `WS_RECONNECT_INTERVAL_MS`.
   
   ### Version/Branch
   
   1.4.0-incubating-SNAPSHOT (main)
   
   ### Commit Hash (Optional)
   
   _No response_
   
   ### What browsers are you seeing the problem on?
   
   _No response_
   
   ### Relevant log output
   
   ```shell
   
   ```


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to