fix(adminui): Monaco dispose on a gone circuit is expected, not an error (#902)
Closing a tab with the script editor open ended the circuit; MonacoEditor.DisposeAsync then called MonacoBlazor.dispose through SafeInvokeAsync, which caught only JSDisconnectedException, so the TaskCanceledException from the torn-down circuit escaped and was logged at [ERR]. DisposeAsync now treats JSDisconnectedException, OperationCanceledException (incl. TaskCanceledException) and ObjectDisposedException as the expected gone-circuit outcome and logs them at Debug; a real JSException from a live page is still logged (Warning); the DotNetObjectReference is released in a finally. MonacoEditor is the only component in the repo that uses JS interop, so there is nothing else to align. bUnit tests: dispose throwing each of the three gone-circuit exceptions neither escapes nor logs above Debug (with a positive control that the failing call ran), and a JSException still warns. They fail against the previous DisposeAsync. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
This commit is contained in:
@@ -237,12 +237,40 @@
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_initialized)
|
||||
try
|
||||
{
|
||||
// Disposal commonly races a circuit disconnect — JSDisconnectedException
|
||||
// here is expected and silent; a real JSException is still logged.
|
||||
await SafeInvokeAsync("MonacoBlazor.dispose", "dispose editor", _id);
|
||||
if (_initialized)
|
||||
{
|
||||
try
|
||||
{
|
||||
await JS.InvokeVoidAsync("MonacoBlazor.dispose", _id);
|
||||
}
|
||||
catch (Exception ex) when (IsGoneCircuit(ex))
|
||||
{
|
||||
// lmxopcua#902: disposal normally runs because the circuit is ending (tab closed, navigation,
|
||||
// reconnect timeout), so the JS side — and the editor with it — is already gone. The interop
|
||||
// call then fails with JSDisconnectedException, or with TaskCanceledException /
|
||||
// ObjectDisposedException when the circuit tore down mid-call. Nothing is left to clean up.
|
||||
Logger.LogDebug("Monaco editor {EditorId}: JS dispose skipped, the circuit is gone ({Reason}).",
|
||||
_id, ex.GetType().Name);
|
||||
}
|
||||
catch (JSException ex)
|
||||
{
|
||||
// The page is alive but Monaco's own dispose threw — a real fault, surfaced.
|
||||
Logger.LogWarning(ex, "Monaco editor {EditorId}: failed to dispose editor.", _id);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dotNetRef?.Dispose();
|
||||
}
|
||||
_dotNetRef?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True for the failures a JS-interop call raises when the circuit (or the page) has already gone away — the
|
||||
/// expected outcome of interop issued from <see cref="DisposeAsync"/>.
|
||||
/// </summary>
|
||||
private static bool IsGoneCircuit(Exception ex) =>
|
||||
ex is JSDisconnectedException or OperationCanceledException or ObjectDisposedException;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user