The GitHub Actions job "Required Checks" on texera.git/main has failed.
Run started by GitHub user github-merge-queue[bot] (triggered by 
github-merge-queue[bot]).

Head commit for run:
65386d38ba114ba7e25b56e6264b04b066def16b / Eugene Gu <[email protected]>
fix(frontend): keep the computing-unit refresh and poll alive after a failed 
request (#8854)

### What changes were proposed in this PR?

`ComputingUnitStatusService` keeps the computing-unit list and the
selected unit's status fresh through two RxJS pipelines: the list
refresh (`refreshComputingUnitListSignal → switchMap(listComputingUnits)
→ subscribe`) and the selected-unit poll (`interval(2000) →
switchMap(getComputingUnit) → subscribe`). Neither inner HTTP observable
had any error handling. In RxJS an error is a terminal notification:
`switchMap` forwards an inner error downstream unchanged, and a
subscriber that receives it is closed for good. Both subscriptions are
created once, in the constructor of this root-provided service, and
nothing ever re-creates them.

So a single failed request (a 500, a 502/504 from the proxy while the
computing-unit managing service restarts, a network blip) killed the
pipeline for the rest of the page's life. The refresh signal kept being
pushed every second by the dashboard, into a `Subject` with no
subscriber left. The only trace was one `ERROR HttpErrorResponse` in the
console; the UI showed nothing.

What users saw until they reloaded the page:

- the dashboard's Computing Units list froze: units created elsewhere
never appeared, terminated ones never disappeared;
- the workspace's computing-unit dropdown stopped refreshing;
- the selected unit's status never changed again, so the Failed /
Terminating states from #7944 could not reach the UI;
- the canvas run button could stay on "Connecting" after the socket was
actually up, because its state is recomputed on poll emissions.

**The change** wraps the inner HTTP observable of both `switchMap`s in
`catchError(this.swallowFetchError(...))`. The helper completes the
failed fetch without emitting, which `switchMap` treats as "this round
is over, wait for the next signal"; the stream stays subscribed and the
next refresh signal or poll tick fetches again. Completing with `EMPTY`
rather than an empty list keeps the cached list and the selected unit
untouched during the failed round, so nothing flickers.

The helper distinguishes two kinds of failure. An `HttpErrorResponse`,
which is what `HttpClient` delivers for every transport-level failure
(non-2xx status, dropped connection, timeout, unparsable body) and what
both app interceptors rethrow, is dropped without logging: it is
expected while the backend is unavailable, it would otherwise print once
a second during a restart, and the failed requests remain visible in the
browser's Network panel. Anything else, for example an exception thrown
inside the managing service's `map(parseDashboardUnit)` step, is a bug
rather than an environment condition, so it is written to
`console.error` with the pipeline name before being dropped. Before this
PR such a bug surfaced once as an uncaught error and then froze the
page; now it is reported on every tick and the page keeps working.

#### Before: one failed request freezes the dashboard

The black panel in the top-right corner is a debugging overlay injected
into the page for the reproduction (it counts `GET api/computing-unit`
requests and can make exactly one of them fail); it is not part of
Texera.

Normal state: the dashboard requests the list once a second.

<img width="1568" height="562"
alt="bug1-01-normal-list-refreshing-every-second"
src="https://github.com/user-attachments/assets/ab652efb-eebe-47a2-9d25-d136733275eb";
/>


One request is made to fail. Eight seconds later no further request has
been sent, although the page still asks for a refresh every second.

<img width="1568" height="562"
alt="bug1-02-one-injected-failure-no-requests-for-8s"
src="https://github.com/user-attachments/assets/9ae568da-292c-405f-b419-f8fc2fa9ae04";
/>

>From a second tab, a computing unit is terminated; that tab shows one
unit immediately.

<img width="1568" height="562"
alt="bug1-03-other-tab-unit-4-removed-shows-1-unit"
src="https://github.com/user-attachments/assets/dececa82-2f90-4131-a933-d80b0b42e33c";
/>

87 seconds after the failure the first tab still shows two units and has
sent no request. Reloading the page restores the list at once, so the
backend was healthy the whole time.

<img width="1568" height="562"
alt="bug1-04-this-tab-frozen-87s-still-shows-2-units-BUG"
src="https://github.com/user-attachments/assets/fe9b6f68-f54e-4ab0-baf0-ae208a41d56b";
/>

#### After: the same failure, on this branch

Same page, same overlay, served from this branch. One request (45) is
made to fail; eight seconds later eight more requests have gone out and
the list is still refreshing.

<img width="1568" height="562"
alt="fixed-bug1-02-one-injected-failure-requests-keep-flowing-8-since"
src="https://github.com/user-attachments/assets/d9c8efe1-f014-488f-abfe-8f02027002da";
/>

A unit created from a second tab appears on this page within a second
(78 requests since the failure), and disappears again when it is
terminated from the other tab (138 requests since the failure), all
without a reload.

<img width="1568" height="562"
alt="fixed-bug1-03-unit-created-in-other-tab-appears-here"
src="https://github.com/user-attachments/assets/775fc80e-7b3e-487c-8fab-8df660d4fbcd";
/>

<img width="1568" height="562"
alt="fixed-bug1-04-unit-removed-in-other-tab-disappears-here-138-since-fail"
src="https://github.com/user-attachments/assets/4dd8495c-fc35-40c3-9a67-c4d9ca899d6a";
/>

### Any related issues, documentation, discussions?

Closes #8850

Related follow-ups that depend on this fix landing first (not part of
this PR): a selected unit whose pod vanishes polls back to Pending
forever, and dropdown rows do not refresh while the dropdown is open.
Both of their fixes call `refreshComputingUnitList()`, which only works
if this pipeline is alive.

### How was this PR tested?

- **New unit tests** in `computing-unit-status.service.spec.ts`, four in
total, all red before the fix. Failures are modelled as
`HttpErrorResponse` (status 500), which is what `HttpClient` delivers.
`keeps refreshing the list through failed fetches` sends two failures
then a success and asserts three fetches, no list emission for a
failure, and the good list on the next success. `keeps polling the
selected unit through a failed fetch` asserts that after a failed tick
the selected unit object is untouched, the poll subscription is still
open and `stopPollingSelectedUnit` was not called, and that the
following four ticks all fetch and the next good result replaces the
selection. `logs a non-HTTP list failure ...` and `logs a non-HTTP poll
failure ...` feed a `TypeError`, then an `HttpErrorResponse`, then a
success, and assert `console.error` is called exactly once, with the
pipeline name and the `TypeError`, while the stream keeps going.
- **Mutation check**, each mutation restored afterwards: removing the
`catchError` from either pipeline, dropping the `instanceof
HttpErrorResponse` check, or swapping the two pipeline labels each turns
exactly the two tests for that pipeline or branch red. Replacing `EMPTY`
with `of(null)` is a compile error, because the helper is typed
`Observable<never>`; with the type loosened to let it compile, it turns
three tests red, the two survival tests through their emission-count and
selection assertions and the poll logging test.
- **Spec file**: 34 passed. **Full frontend suite** (`ng test
--watch=false`): 231 files, 6447 passed, 1 skipped (pre-existing), 0
failed. `prettier --check` clean on both files.
- **Bug reproduced in a running local instance** (screenshots above):
Chrome against the Angular dev server and the local computing-unit
managing service, with a page-level `XMLHttpRequest` wrapper that fails
exactly one list request. Reproducible without instrumentation by
stopping the computing-unit managing service for a few seconds while the
dashboard's Compute page is open, starting it again, and changing a unit
from another tab.
- **Fixed build exercised in the same local instance** (screenshots in
the "After" section): after one injected failure the request counter
kept increasing once a second (138 requests over the following two
minutes), a unit created from another tab appeared within a second, and
the same unit disappeared within a second after being terminated from
the other tab. No reload. The logging branch does not change what the
page does, so those screenshots still apply.

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

Yes. Generated-by: Claude Code (claude-fable-5-1). Co-authored with
Claude; the author reviewed the change before submission.

---------

Co-authored-by: Kunwoo (Chris) <[email protected]>

Report URL: https://github.com/apache/texera/actions/runs/40445450351

With regards,
GitHub Actions via GitBox

Reply via email to