Kestrel WebTransport completes a client unidirectional stream's reader at FIN, before the app reads buffered data
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
- 55/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- csharp
- Ambito
- backend, networking
Direzione di ricerca
Read WebTransportStream.cs around the ConnectionClosed registration and DisposeAsync, then trace how WebTransportSession.cs handles TryRemoveStream. Reproduce with the unidirectional stream steps and compare against the bidirectional case. Done means the application can read all bytes sent before FIN, and cleanup waits until the application completes its reader or disposes the stream.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
When a WebTransport client opens a unidirectional stream, writes some bytes and finishes it (FIN), Kestrel can complete the application's PipeReader for that stream before the app has read the buffered data. The app then gets:
System.InvalidOperationException: Reading is not allowed after reader was completed.
Or it reads a truncated stream. This happens intermittently: about 2 in 8 runs on loopback with no proxy in between.
From reading main, the path looks like this:
WebTransportStream's constructor registers oncontext.StreamContext.ConnectionClosed, and the callback callsWebTransportSession.TryRemoveStream(streamId). The comment there says it "will not trigger if closed only of of the directions of a stream. Stream must be fully ended before this will be called."- An inbound unidirectional stream has only one direction, so it counts as "fully ended" as soon as the peer's FIN is received by the transport, even while that data still sits unread in the pipe.
TryRemoveStreamcallsstream.DisposeAsync().AsTask().GetAwaiter().GetResult().WebTransportStream.DisposeAsyncrunsawait _duplexPipe.Input.CompleteAsync(). That completes the application's reader while the application may still be reading it.
Bidirectional streams don't hit this in practice, because the application's side stays open until it writes and completes.
Files:
src/Servers/Kestrel/Core/src/Internal/WebTransport/WebTransportStream.cs(theConnectionClosedregistration andDisposeAsync)src/Servers/Kestrel/Core/src/Internal/WebTransport/WebTransportSession.cs(TryRemoveStream)
Expected Behavior
Data a peer sent before its FIN on a unidirectional stream can always be read in full by the application. The stream should be cleaned up only after the application has completed its reader, or disposed the stream itself, rather than when the transport sees the stream end.
Steps To Reproduce
-
Linux, libmsquic 2.6.2, .NET SDK
11.0.100-rc.3.26522.148. A Kestrel app withWebTransportAndH3Datagramsenabled,UseQuic(), andHttp1AndHttp2AndHttp3on loopback. -
Handler:
app.Run(async context => { var session = await context.Features.GetRequiredFeature<IHttpWebTransportFeature>() .AcceptAsync(context.RequestAborted); var stream = await session.AcceptStreamAsync(context.RequestAborted); await using (stream!) { using var buffer = new MemoryStream(); await stream!.Transport.Input.CopyToAsync(buffer, context.RequestAborted); // throws intermittently Console.WriteLine(buffer.Length); } }); -
Client (any WebTransport client): open a session, open a unidirectional stream (type
0x54+ session id), write 4 bytes, finish the stream. -
Repeat. Intermittently,
CopyToAsyncthrowsInvalidOperationException: Reading is not allowed after reader was completed.
The same scenario over a bidirectional stream (client writes, then FIN; server reads to the end) passes every time.
Exceptions (if any)
System.InvalidOperationException: Reading is not allowed after reader was completed.
at System.IO.Pipelines.ThrowHelper.ThrowInvalidOperationException_NoReadingAllowed()
at System.IO.Pipelines.Pipe.ReadAsync(CancellationToken token)
at System.IO.Pipelines.PipeReader.CopyToAsync(Stream destination, CancellationToken cancellationToken)
.NET Version
11.0.100-rc.3.26522.148
Anything else?
- Related WebTransport follow-ups: #42788. A separate interop issue: #70384.
- We hit this while proxying WebTransport sessions, where client unidirectional streams are forwarded. The forwarder sees the stream cut off, with no way to tell whether all the data arrived.
- Lingua principale
- C#
- Stelle
- 38.5k
- Fork
- 11.6k
- Merge medio
- 3g 1h
- PR unite (30g)
- 289
Preparare l'ambiente
Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di dotnet/aspnetcore
-
area-networking
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
dotnet/aspnetcore#70545 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
area-infrastructure
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
dotnet/aspnetcore#70524 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
[OpenTelemetry] Hosting replaces an Activity.DisplayName that the app set, when a route matchesApertaarea-hosting Regression
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
dotnet/aspnetcore#70516 · 1 commento · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
[SSE] ServerSentEventsResult.WrapEvents is missing [EnumeratorCancellation], so RequestAborted never reaches the app's generator on the IAsyncEnumerable<T> overloadForse già presa @bharatprajapati-coder l’ha presa 1 giorno fa. Apertaarea-minimal
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
dotnet/aspnetcore#70419 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
[Blazor] DefaultInMemoryCircuitPersistenceProvider never disposes the retention CancellationTokenSource when a persist is rejected at capacityForse già presa @Vinoth2562000 l’ha presa 1 giorno fa. Apertaarea-blazor
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
dotnet/aspnetcore#70418 · 1 commento · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di dotnet/aspnetcore
Issue simili
-
bug emitter:client:csharp
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
microsoft/typespec#12572 · 1 commento · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
needs-area-label
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 2 giorni
-
AWS Lambda: KeyNotFoundException drops HTTP-triggered transactions when allowAllHeaders is disabledApertabug
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
newrelic/newrelic-dotnet-agent#3949 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 87/100
I maintainer di solito rispondono entro 1 giorno
-
priority:P2
Difficoltà 2/5 1-3 ore Idoneità per principianti 80/100