eugenegujing opened a new pull request, #8936: URL: https://github.com/apache/texera/pull/8936
### What changes were proposed in this PR? **Root cause.** The frontend learns that a run has ended only from the engine, over the selected unit's websocket. When the selected unit is terminated from the unit dropdown, or drops out of the unit list, that message can never arrive and nothing resets the execution state, so the run keeps being reported as running. On top of that, the Kill button's gate exempted `NoComputingUnit` from its socket check, so Kill stayed enabled although its request could no longer be delivered. #3400 (a910bcfd5) added the socket check to Kill together with that exemption, copying the Run button's translation of the old single-mode flag `!environment.computingUnitManagerEnabled`; that mode no longer exists. ``` Before: unit terminated mid-run -> no end-of-run message -> state stays Running: clock counts, Form View says "Running", Kill enabled but dead After: unit terminated mid-run -> selection drops to none -> run reset: clock stopped, Form View idle, Kill disabled; console and results kept ``` Both screenshots: a run was in flight on "Analytics Unit", which was then terminated from the unit dropdown, and Kill was clicked. | Before | After | | --- | --- | | <img width="1512" height="805" alt="kill-button-2-after-terminate-kill-still-enabled" src="https://github.com/user-attachments/assets/e0919b87-0c2b-4138-a30b-12295b749175" /> | <img width="1456" height="829" alt="kill-button-4-after-fix-terminated-kill-disabled-canvas-relocked" src="https://github.com/user-attachments/assets/729eb0df-d01a-4e4c-90e4-e2c4ac684398" /> | | Kill is enabled and does nothing; the clock keeps counting. | Kill is greyed out; the clock is back at 0:00:00. The info message comes from the separate check described under "Scope boundary". | Form View after the same steps no longer reports a run: <img width="1456" height="829" alt="kill-button-6-after-fix-form-view-not-running" src="https://github.com/user-attachments/assets/bd68cdd3-161c-4479-a2c2-889a74b5d2d8" /> **Changes.** 1. `ExecuteWorkflowService` calls its existing `resetExecutionAndWorkers()` when the selected unit drops to none while an execution is in flight. That puts the execution back to Uninitialized, which Form View and the canvas lock already follow, clears the worker ids and stops the clock. It is the narrower of the two existing resets: console messages and results stay, unlike the `connectionReset` path a manual unit switch takes. A direct switch from one unit to another never passes through none, so it does not trigger. 2. The Kill button is disabled whenever the websocket is down, without the `NoComputingUnit` exemption. This is defense in depth: with change 1, a run that is still in flight with no unit and no socket should not occur any more, and if it does, Kill no longer looks usable. **Scope boundary.** The canvas can be locked again right away by a second, independent check, which this PR leaves unchanged. Whenever the selected unit changes, including when it drops to none, `ComputingUnitSelectionComponent.updateWorkflowModificationStatus` asks the database for Running or Initializing executions of the workflow. If it finds any, it disables modification and shows "There are ongoing executions on this workflow. Modification of the workflow is currently disabled." At that moment the lost unit's execution row is still RUNNING: | Unit type | Why the row is RUNNING | Effect | | --- | --- | --- | | Local | Disconnect does not stop the shared engine, which keeps running the execution. | Accurate. The check passes again on the first reload or unit selection after that execution finishes. | | Kubernetes | The terminate endpoint deletes the pod but never updates the execution row. | The workflow is locked on every later load. That needs a backend fix, which is outside this PR. | Also not changed here: on a dropdown terminate, the existing `clearStatus()` call in `ComputingUnitStatusService.terminateComputingUnit` resets the operators' colors, while their port row counts keep the last values. When the unit leaves the list any other way, for example a Kubernetes eviction or a terminate from another tab, nothing resets the colors, so the operators keep their last state after the reset. **Side effect of change 1.** It should rarely matter in practice: this tab can reset a run that is in fact still going, but only when all of these hold: - the unit is a local one; terminating a Kubernetes unit deletes its pod, so there the run really has ended; - this tab has a run in flight on that unit; - the same unit is disconnected from another tab or page before the run ends; - this tab then refreshes its unit list before the run ends, which in the workspace happens on opening the unit dropdown, renaming or terminating a unit, or a websocket status change, not on a timer. The run itself is not affected: the reset only changes this tab's state and sends nothing to the engine, so the shared engine keeps running the execution and finishes it normally. This tab just stops showing it as running and disables Kill, whereas on main Kill still worked over this tab's open socket. Requiring the socket to be down as well would avoid this, but would make the reset depend on #8855, which makes a clean close mark the socket disconnected. ### Any related issues, documentation, discussions? Closes #8934 Related: - #8618: a stuck non-terminal execution row keeps its unit off the idle sweep. - #8853, fixed by #8855: a clean websocket close never marks the socket disconnected; the Kill gate relies on `isConnected` being accurate. ### How was this PR tested? New tests in `frontend/src/app/workspace/service/execute-workflow/execute-workflow.service.spec.ts`: - ends a running execution when the selection empties - ends a paused execution when the selection empties - ends a later run as well when its unit is lost too - stops the clock when the selection empties - ends a run that is Initializing, Running, Pausing, Paused, Resuming or Recovering when the selection empties, one case per state - leaves a run that is Uninitialized, Completed, Terminated, Failed or Killed untouched when the selection empties, one case per state - leaves a finished execution alone when the selection empties - keeps the error messages of a failed run when the selection empties - does nothing when no execution has started - keeps the run when switching straight from one unit to another - keeps the run when a unit is first selected - keeps the run when the selected unit reports Pending, Failed, Unknown or Terminating, one case per status New tests in `frontend/src/app/workspace/component/menu/menu.component.spec.ts`, for the Kill button: - is disabled while the socket is down and no unit is selected - stays enabled while the socket is up and no unit is selected - is disabled while the socket is down on a running unit - stays enabled while the socket is up on a running unit - is disabled once the execution is over, with the socket connected or disconnected, one case each The shared `beforeEach` of the service spec now installs a `BehaviorSubject` as the selected-unit stream before the service is constructed, since the service subscribes in its constructor. Form View needs no test of its own: it reads `isRunning` from the execution state stream that `resetExecutionAndWorkers()` already broadcasts. `ExecuteWorkflowService` does not inject the console or result services, so this change cannot clear them. ``` cd frontend NODE_OPTIONS="--no-experimental-webstorage" npx ng test --watch=false --include src/app/workspace/service/execute-workflow/execute-workflow.service.spec.ts # 81 passed NODE_OPTIONS="--no-experimental-webstorage" npx ng test --watch=false --include src/app/workspace/component/menu/menu.component.spec.ts # 141 passed NODE_OPTIONS="--no-experimental-webstorage" npx ng test --watch=false # 233 files, 6607 passed | 1 skipped npx tsc --noEmit -p tsconfig.json # clean npx eslint <changed files> # clean npx prettier --check <changed files> # clean ``` The `NODE_OPTIONS` setting is only needed locally on Node 25, whose built-in `localStorage` shadows jsdom's. CI runs Node 24 with `yarn --cwd frontend run test:ci` and needs no flag. Mutation check: each of these makes at least one new test fail: restoring the `NoComputingUnit` exemption, resetting regardless of the execution state, treating Paused as not in flight, removing the subscription, using `resetExecutionState()` instead of `resetExecutionAndWorkers()`, treating Completed, Terminated or Failed as in flight, treating Initializing or Recovering as settled, resetting when the selected unit only reports a non-Running status, letting only the first lost unit reset, and a reset that forgets to clear the worker ids. Manual check in Chrome, 1512x861 viewport, against a build of this branch on the local-dev stack with local computing units: 1. Workflow: Text Input -> Python UDF whose `process_tuple` does `time.sleep(900)`. 2. Select a unit and a warehouse, click Run, and wait until the UDF operator is running. 3. Open the unit dropdown, click the trash icon of the selected unit, confirm "Disconnect", then click Kill. 4. Before: Kill stays enabled and does nothing; the clock keeps counting; Form View keeps saying "Running"; operators cannot be moved. After: the execution is back to Uninitialized, the clock reads 0:00:00, Kill is disabled, and Form View no longer reports a run; console and results stay. The canvas is locked again right away with the info message quoted under "Scope boundary", because the shared local engine is still running the execution. ### Was this PR authored or co-authored using generative AI tooling? Yes. Generated-by: Claude Code (claude-fable-5-1, claude-opus-5-5). Co-authored with Claude; the author reviewed the change before submission. -- 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]
