Skip to content

Interop calls cut off by disposal no longer throw (3.19.8.1) - #2

Merged
Kebechet merged 2 commits into
mainfrom
fix/commands-during-teardown
Oct 28, 2026
Merged

Kebechet merged 2 commits into
mainfrom
fix/commands-during-teardown

Conversation

@Kebechet

Copy link
Copy Markdown
Owner

Summary

This follows up on #1. A command or query that was still in flight when the component was disposed threw once the JS side went away. In a reloaded BlazorWebView that is a JSException ("JS object instance with ID n does not exist"). Elsewhere it is JSDisconnectedException, ObjectDisposedException or TaskCanceledException. A call made after disposal already did nothing; these calls were issued before it.

A caller that fires a call off without awaiting it, for example scrolling a list back to the top after a filter change, saw only an unobserved task exception. So apps wrapped the calls in catches of their own. SatisFIT's exercise picker catches ObjectDisposedException around ScrollToTop, which misses the other three exceptions. Its InvalidateItemSize calls have no catch at all.

Changes

  1. Let interop calls cut off by disposal finish without throwing.
    • InvokeInstance and QueryInstance now catch a failure that arrives after disposal has started. A command completes, a query returns its default, and the failure is logged the same way teardown already logs: Debug for the known ways the JS side goes away, Warning for anything else.
    • A failure while the component is alive still throws.
    • The render path had the same gap. A render's setOptions, setItems or measureRenderedRows that was in flight at disposal stopped the render if it succeeded, but if it failed it escaped as an unhandled render exception. Those calls now go through InvokeInstance too.
    • The README notes that these methods are safe to fire off while the component may be going away.
  2. Release 3.19.8.1.

Testing

  • Unit tests (bUnit): 30/30 pass. 10 are new:
    • ScrollToTop in flight at disposal, once for each of the four exceptions and once for an unexpected one: the task completes, and the failure is logged at Debug, or at Warning for the unexpected one.
    • GetSnapshot in flight at disposal returns null.
    • The render-race theory now also fails each held call (setOptions, setItems, measureRenderedRows), and checks that no unhandled render exception is reported.
    • A JSException while the component is alive still throws.
  • With the two new catches disabled, all 9 teardown tests fail.
  • Browser tests (Playwright): 32/32 pass.
  • Release build: no errors on net6.0 through net10.0.

After merge

  1. Run the Publish to NuGet workflow to release 3.19.8.1.
  2. In SatisFIT, bump Kebechet.Blazor.TanStack.VirtualCore to 3.19.8.1 and remove the ObjectDisposedException catch in ExerciseSearchBottomSheet.ScrollVirtualizedListToTop.

A command or query still in flight when the component was disposed threw
once the JS side went away. In a reloaded BlazorWebView the page no longer
holds the instance, so the call fails with a JSException; elsewhere it is
JSDisconnectedException, ObjectDisposedException or TaskCanceledException.
Calls made after disposal already did nothing, but these were issued
before it. A caller that fired one off without awaiting - the usual way to
scroll a list back to the top after a filter change - saw it only as an
unobserved task exception, so apps wrapped the calls in catches of their
own: SatisFIT's exercise picker catches ObjectDisposedException around
ScrollToTop, which misses the other three.

InvokeInstance and QueryInstance now catch a failure that arrives after
disposal started: a command completes, a query returns its default, and
the failure is logged as teardown already does - Debug for the known ways
the JS side goes away, Warning for anything else. A failure while the
component is alive still throws.

The render path had the same gap: a render's setOptions, setItems or
measureRenderedRows in flight at disposal stopped the render when it
succeeded, but escaped as an unhandled render exception when it failed.
Those calls now go through InvokeInstance too.

Tests: ScrollToTop in flight at disposal, for each of the four exceptions
and an unexpected one (logged at Debug, Warning for the last); GetSnapshot
in flight returns null; the render-race theory now also fails each held
call. All nine fail without the catches. A failure while alive still
throws.
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.69%. Comparing base (4177bf1) to head (5c1e9ff).

Additional details and impacted files
@@            Coverage Diff             @@
##             main       #2      +/-   ##
==========================================
+ Coverage   67.82%   71.69%   +3.86%     
==========================================
  Files           6        6              
  Lines         258      265       +7     
  Branches       41       41              
==========================================
+ Hits          175      190      +15     
+ Misses         73       64       -9     
- Partials       10       11       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Kebechet
Kebechet merged commit 3c1f5bd into main Oct 28, 2026
2 checks passed
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.

2 participants