Guard and observe RunJavascriptFireAndForget to stop UnobservedTaskException (BL-16571)#8079
Guard and observe RunJavascriptFireAndForget to stop UnobservedTaskException (BL-16571)#8079hatton wants to merge 2 commits into
Conversation
…ception (BL-16571) WebView2Browser.RunJavascriptFireAndForget called _webview.ExecuteScriptAsync with no readiness guard and never observed the returned Task. When CoreWebView2 is not yet initialized (startup) or the control is disposing (shutdown), ExecuteScriptAsync throws from inside its async state machine; because nothing awaited or observed that faulted Task, the GC finalizer thread rethrew it as an UnobservedTaskException. That is Sentry BLOOM-DESKTOP-D07 (~11,005 events/90d, recurring through Bloom 6.4.104.0). Fix: early-return when the browser isn't in a usable state (mirroring the guard in UpdateDisplay), and attach an OnlyOnFaulted continuation that logs via Logger.WriteEvent, so the Task is always observed even in the residual race between the guard check and the call. This is an expected startup/shutdown race, so we only log; no MessageBox, no rethrow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
[Claude Fable 5] Consulted Devin on 2026-07-18 19:04 up to commit cc6bd44. Result: no Bugs, no Investigate flags. One Informational note confirming the OnlyOnFaulted continuation correctly observes the fault. CI and Greptile status tracked separately. |
|
| Filename | Overview |
|---|---|
| src/BloomExe/WebView2Browser.cs | Adds a readiness guard and a faulted-task observation continuation to RunJavascriptFireAndForget, eliminating the UnobservedTaskException Sentry issue (BLOOM-DESKTOP-D07). |
Reviews (2): Last reviewed commit: "Harden fire-and-forget continuation agai..." | Re-trigger Greptile
Greptile flagged that the OnlyOnFaulted continuation returns a Task we discard, so if Logger.WriteEvent itself threw, that continuation's own Task would fault and go unobserved - reintroducing the UnobservedTaskException category this PR fixes. Read the exception first (so it is observed regardless) and wrap the log call in try/catch so the continuation can never fault. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
[Claude Fable 5] Re-consulted Devin on 2026-07-18 19:09 up to commit 1e64532 (after the Greptile hardening fix). Re-review clean: no Bugs, no Investigate flags, 2 Informational notes. Greptile re-review passed with no new findings; CI green. |
[Claude Fable 5]
What
Fixes Sentry BLOOM-DESKTOP-D07 —
System.AggregateException: A Task's exception(s) were not observed ... (The instance of CoreWebView2 is uninitialized and unable to complete this operation), mechanismUnobservedTaskException. ~11,005 events / 0 users in the last 90 days, still recurring through Bloom 6.4.104.0.Root cause
WebView2Browser.RunJavascriptFireAndForget(string script)was exactly:No readiness guard, and the returned
Taskis never awaited or observed. WhenCoreWebView2isn't initialized yet (startup) or the control is disposing (shutdown),ExecuteScriptAsync'sVerifyInitializedGuardthrows inside the async state machine. Nothing observes the faultedTask, so the GC finalizer thread later rethrows it as anUnobservedTaskException, which Sentry reports. This method is the single choke point; its fire-and-forget callers have no lifecycle guard of their own.Fix
In
RunJavascriptFireAndForget:_webview == null || IsDisposed || Disposing || CoreWebView2 == null), mirroring the guard inUpdateDisplay().OnlyOnFaultedcontinuation that logs viaLogger.WriteEvent. This is the structurally-important half: it closes the residual race window between the guard check and the call, so the exception is always observed. This is an expected startup/shutdown race, not a user-facing failure — log only, no MessageBox, no rethrow.The awaited variants (
RunJavascriptAsync,GetStringFromJavascriptAsync,GetObjectFromJavascriptAsync) are intentionally left unchanged: their callers await them, so their exceptions are already observed.Testing
No unit test: WebView2 lifecycle can't be driven into the uninitialized/disposing state in a headless unit test, so a test here would be hollow. Verified the change compiles (BloomExe builds clean) and is CSharpier-formatted.
Ref: https://issues.bloomlibrary.org/youtrack/issue/BL-16571
Sentry: https://bloom-app.sentry.io/issues/6252535033/
Devin review
This change is