Skip to content

Guard and observe RunJavascriptFireAndForget to stop UnobservedTaskException (BL-16571)#8079

Open
hatton wants to merge 2 commits into
masterfrom
BL-16571-sentry-webview2-fireandforget
Open

Guard and observe RunJavascriptFireAndForget to stop UnobservedTaskException (BL-16571)#8079
hatton wants to merge 2 commits into
masterfrom
BL-16571-sentry-webview2-fireandforget

Conversation

@hatton

@hatton hatton commented Jul 19, 2026

Copy link
Copy Markdown
Member

[Claude Fable 5]

What

Fixes Sentry BLOOM-DESKTOP-D07System.AggregateException: A Task's exception(s) were not observed ... (The instance of CoreWebView2 is uninitialized and unable to complete this operation), mechanism UnobservedTaskException. ~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:

public override void RunJavascriptFireAndForget(string script)
{
    _webview.ExecuteScriptAsync(script);
}

No readiness guard, and the returned Task is never awaited or observed. When CoreWebView2 isn't initialized yet (startup) or the control is disposing (shutdown), ExecuteScriptAsync's VerifyInitializedGuard throws inside the async state machine. Nothing observes the faulted Task, so the GC finalizer thread later rethrows it as an UnobservedTaskException, 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:

  1. Early-return when the browser isn't usable (_webview == null || IsDisposed || Disposing || CoreWebView2 == null), mirroring the guard in UpdateDisplay().
  2. Observe the Task with an OnlyOnFaulted continuation that logs via Logger.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 Reviewable

…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>
@hatton

hatton commented Jul 19, 2026

Copy link
Copy Markdown
Member Author

[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.

@greptile-apps

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a high-volume Sentry issue (BLOOM-DESKTOP-D07) caused by RunJavascriptFireAndForget discarding the Task returned by ExecuteScriptAsync without observing it, allowing startup/shutdown faults to reach the GC finalizer thread as UnobservedTaskException.

  • Adds a readiness guard (_webview == null || IsDisposed || Disposing || CoreWebView2 == null) that mirrors the existing check in UpdateDisplay(), short-circuiting the call when the control isn't usable.
  • Attaches an OnlyOnFaulted continuation that first reads t.Exception (marking it observed) and then logs via Logger.WriteEvent wrapped in a try/catch, so the continuation task itself can never fault and re-introduce the same problem.
  • The fix correctly leaves the awaited variants (RunJavascriptAsync, GetStringFromJavascriptAsync, GetObjectFromJavascriptAsync) unchanged, as their exceptions are already observed by callers.

Important Files Changed

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

Comment thread src/BloomExe/WebView2Browser.cs
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>
@hatton

hatton commented Jul 19, 2026

Copy link
Copy Markdown
Member Author

[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.

@hatton
hatton marked this pull request as ready for review July 19, 2026 01:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant