[Review agent] Calibrate Blazor DI scope teardown IJSObjectReference disposal findings (#69970, #70340)

Aperta
#70,360 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
45/100
Tipo di issue
Documentazione
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
csharp, javascript

Direzione di ricerca

Start with docs/BlazorComponentsGuidance.md and .github/agents/review-candidate-verifier.agent.md, then check the cited Storage.cs, CookieStore.cs, Window.cs, and Screen.cs conventions and the JSObjectReference.DisposeAsync behavior. Document the scope teardown behavior for Interactive Server and WebAssembly, and update verifier guidance to check sibling patterns, exception handling, and whether disposal is reachable outside teardown. Done when the guidance covers both findings and the cited review cases.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

area-blazor area-infrastructure
Is there an existing issue for this?
  • I have searched the existing issues
Is your feature request related to a problem? Please describe the problem.

Across two Microsoft.AspNetCore.Components.Platform PRs (#69970 and #70340), review passes produced P2 lifecycle findings around IJSObjectReference disposal during Blazor DI scope teardown (IInternalAsyncDisposal.InternalDisposeAsync) without establishing a reachable production trigger or accounting for existing repository conventions:

  1. Non-waiting pending acquisition at DI scope disposal (#69970 Screen.cs:103 and #70340 Window.cs:88):

    • In Screen.cs (#69970) and Window.cs (#70340), GetReferenceAsync() caches _jsRuntime.GetValueAsync<IJSObjectReference>(...).AsTask(), and InternalDisposeAsync() intentionally only disposes _referenceTask.Result when _referenceTask is { IsCompletedSuccessfully: true } with the comment:
      // Don't wait for a pending acquisition. It could hold up scope disposal until the JS interop timeout.
      if (_referenceTask is { IsCompletedSuccessfully: true } referenceTask)
      {
          await referenceTask.Result.DisposeAsync().ConfigureAwait(false);
      }
      
    • Review arms (gpt-5.6-sol and gpt-6.1-sol on #69970; gpt-6.1-sol on #70340) flagged a P2 leak if the scope is disposed while GetReferenceAsync() is still in flight and the JS reference arrives afterward.
    • While the source-level mechanism is conditionally possible if an IJSRuntime completes GetValueAsync after InternalDisposeAsync checks _referenceTask.IsCompletedSuccessfully (and JSCallbackFactory.ImportModuleAsync in #70340 explicitly disposes a late-arriving module to satisfy BrowserJSRuntime unit tests that call ServiceProvider.DisposeAsync() while holding "import"), this exact non-waiting pattern is already established in Storage.cs:111-115 and CookieStore.cs:179-183 on main.
    • Furthermore, Window.AddEventListenerAsync already throws ObjectDisposedException after await GetReferenceAsync() (Window.cs:70), and as the author explained in #69970 comment 4489650667, Blazor DI scope disposal in supported hosts runs during Interactive Server circuit teardown (where inbound interop completion and disposal are serialized on the circuit/renderer dispatcher) or WebAssembly page unload — so a P2 production leak claim is not established without a concrete supported-host trigger.
  2. Exception propagation during IJSObjectReference.DisposeAsync at DI scope disposal (#70340 Window.cs:159, CCR comment 4489112121):

    • Separately from the pending-acquisition pattern above, CCR comment 4489112121 (and corroborating review notes) claimed that await referenceTask.Result.DisposeAsync() in Window.InternalDisposeAsync() could throw JSDisconnectedException or TaskCanceledException during circuit teardown and skip subsequent feature cleanup.
    • As the author noted in #70340 comment 4489969683, CircuitHost.DisposeAsync calls JSRuntime.MarkPermanentlyDisconnected() before disposing the DI scope (preventing new outbound calls from being sent), and JSObjectReference.DisposeAsync already suppresses JSDisconnectedException (since #64773).
Describe the solution you'd like
  1. Calibrate Blazor Components / JSInterop review guidance (docs/BlazorComponentsGuidance.md and/or .github/agents/review-candidate-verifier.agent.md):
    • Document the Blazor DI scope teardown contract for scoped JS interop projections (Window, Screen, Storage, CookieStore):
      • In Interactive Server, CircuitHost marks JSRuntime permanently disconnected before scope disposal runs, inbound completions are coordinated through the circuit/renderer dispatcher, and JSObjectReference.DisposeAsync already ignores JSDisconnectedException.
      • In WebAssembly, the DI scope for browser platform services lives until page unload.
      • Non-waiting disposal of a lazy Task<IJSObjectReference> (if (_referenceTask is { IsCompletedSuccessfully: true })) that matches existing Storage/CookieStore conventions is intentional so scope disposal cannot hang on a pending JS interop call, and should not be elevated as a P2 blocking production leak unless a reachable non-teardown disposal path is established in the PR.
  2. Require review-candidate-verifier to check existing sibling conventions and JSObjectReference.DisposeAsync exception contracts:
    • Before keeping a P2 disposal-leak or JSDisconnectedException propagation candidate on an IJSObjectReference, verify whether JSObjectReference.DisposeAsync already suppresses the claimed exception and whether the disposal path is reachable in a supported host outside circuit/page teardown.
Additional context
  • Tracked under #70231.
  • Evidence PRs:
    • #69970 (src/Components/Platform/src/Screen.cs:103, author explanation in 4489650667)
    • #70340 (src/Components/Platform/src/Window.cs:88 and Window.cs:159, author explanation in 4489969683)
Lingua principale
C#
Stelle
38.5k
Fork
11.6k
Merge medio
3g 1h
PR unite (30g)
289

Preparare l'ambiente

Apri in Codespaces

Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di dotnet/aspnetcore

Tutte le issue di dotnet/aspnetcore

Issue simili

Altre issue su C#

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.