AdminUI: closing a tab with the script editor open logs [ERR] TaskCanceledException from MonacoEditor.DisposeAsync #902

Closed
opened 2026-11-13 01:43:12 -05:00 by dohertj2 · 2 comments
Owner

Seen on DEV vd03 (OtOpcUa 6c4d2990) on 2026-11-13, during the lmxopcua#700 deployed gate (record docs/deploy/2026-11-13-vd03-fea7de0a-to-6c4d2990.md, 9375c9e2).

What happened. The script editor (Monaco) was open in an AdminUI tab. Closing the tab, which ended the Blazor circuit, logged an [ERR] line: TaskCanceledException from MonacoEditor.DisposeAsync.

Why it matters. Nothing breaks, but it is noise at error level in the host log. Anyone reading the logs or alerting on [ERR] will be misled.

To do.

  • In DisposeAsync, treat the JS-interop call failing because the circuit or page is already gone as expected: catch TaskCanceledException, JSDisconnectedException and ObjectDisposedException, and log at Debug or not at all.
  • Check other components that dispose JS objects the same way.
  • Add a bUnit test for the disposed-circuit path if feasible.
Seen on DEV vd03 (OtOpcUa `6c4d2990`) on 2026-11-13, during the lmxopcua#700 deployed gate (record `docs/deploy/2026-11-13-vd03-fea7de0a-to-6c4d2990.md`, `9375c9e2`). **What happened.** The script editor (Monaco) was open in an AdminUI tab. Closing the tab, which ended the Blazor circuit, logged an `[ERR]` line: `TaskCanceledException` from `MonacoEditor.DisposeAsync`. **Why it matters.** Nothing breaks, but it is noise at error level in the host log. Anyone reading the logs or alerting on `[ERR]` will be misled. **To do.** - In `DisposeAsync`, treat the JS-interop call failing because the circuit or page is already gone as expected: catch `TaskCanceledException`, `JSDisconnectedException` and `ObjectDisposedException`, and log at Debug or not at all. - Check other components that dispose JS objects the same way. - Add a bUnit test for the disposed-circuit path if feasible.
dohertj2 added the type:bugpriority:p3 labels 2026-11-13 01:43:12 -05:00
Author
Owner

Fixed on master in b85e004b.

Root cause. MonacoEditor.DisposeAsync (src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/MonacoEditor.razor, was line 244) called MonacoBlazor.dispose through SafeInvokeAsync, which caught only JSDisconnectedException. When the tab closes the circuit is torn down mid-call, the call fails with TaskCanceledException, which escaped and was logged as an unhandled circuit exception at [ERR].

Fix. DisposeAsync now treats JSDisconnectedException, OperationCanceledException (which covers TaskCanceledException) and ObjectDisposedException as the normal result of disposing on a gone circuit, and logs them at Debug. A real JSException from a live page is still logged at Warning. The DotNetObjectReference is released in a finally. Audit: MonacoEditor is the only component in the repo that uses JS interop (IJSRuntime/IJSObjectReference), so no other component needed the change.

Tests. New bUnit tests in MonacoEditorDisposeTests: dispose throwing each of the three exceptions does not escape and logs nothing above Debug, with a positive control that the failing call really ran. A JSException still logs a warning. Against the old code, all three gone-circuit cases fail.

Not re-checked in a browser; the next deploy will confirm that the [ERR] line is gone from the host log.

Fixed on `master` in `b85e004b`. **Root cause.** `MonacoEditor.DisposeAsync` (`src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/MonacoEditor.razor`, was line 244) called `MonacoBlazor.dispose` through `SafeInvokeAsync`, which caught only `JSDisconnectedException`. When the tab closes the circuit is torn down mid-call, the call fails with `TaskCanceledException`, which escaped and was logged as an unhandled circuit exception at `[ERR]`. **Fix.** `DisposeAsync` now treats `JSDisconnectedException`, `OperationCanceledException` (which covers `TaskCanceledException`) and `ObjectDisposedException` as the normal result of disposing on a gone circuit, and logs them at Debug. A real `JSException` from a live page is still logged at Warning. The `DotNetObjectReference` is released in a `finally`. **Audit:** `MonacoEditor` is the only component in the repo that uses JS interop (`IJSRuntime`/`IJSObjectReference`), so no other component needed the change. **Tests.** New bUnit tests in `MonacoEditorDisposeTests`: dispose throwing each of the three exceptions does not escape and logs nothing above Debug, with a positive control that the failing call really ran. A `JSException` still logs a warning. Against the old code, all three gone-circuit cases fail. Not re-checked in a browser; the next deploy will confirm that the `[ERR]` line is gone from the host log.
Author
Owner

Deployed to DEV vd03 + QA vq03/vq04 in c9640f5c on 2026-11-13.

Live check on vd03: opened /scripts/new (Monaco loaded), closed the tab without saving, then read the host log over SMB from just before the open to 3+ min after the close: 0 MonacoEditor / Unhandled exception in circuit lines, 0 [ERR]. Control: the 6c4d2990 build logged that [ERR] for the same action earlier the same day.

Record: docs/deploy/2026-11-13-vd03-qa-to-c9640f5c.md (c0ba450d).

Deployed to DEV vd03 + QA vq03/vq04 in `c9640f5c` on 2026-11-13. Live check on vd03: opened `/scripts/new` (Monaco loaded), closed the tab without saving, then read the host log over SMB from just before the open to 3+ min after the close: **0 `MonacoEditor` / `Unhandled exception in circuit` lines, 0 `[ERR]`**. Control: the `6c4d2990` build logged that `[ERR]` for the same action earlier the same day. Record: `docs/deploy/2026-11-13-vd03-qa-to-c9640f5c.md` (`c0ba450d`).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#902