Skip to content

Commit 4655c3c

Browse files
HTTP/3: Use new QuicStream.ReadsCompleted property in transport (#35483)
Co-authored-by: James Newton-King <[email protected]>
1 parent 781f4fb commit 4655c3c

File tree

2 files changed

+68
-2
lines changed

2 files changed

+68
-2
lines changed

src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,30 @@ private async Task DoReceive()
187187

188188
input.Advance(bytesReceived);
189189

190-
var flushTask = input.FlushAsync();
190+
ValueTask<FlushResult> flushTask;
191+
192+
if (_stream.ReadsCompleted)
193+
{
194+
// If the data returned from ReadAsync is the final chunk on the stream then
195+
// flush data and end pipe together with CompleteAsync.
196+
//
197+
// Getting data and complete together is important for HTTP/3 when parsing headers.
198+
// It is important that it knows that there is no body after the headers.
199+
var completeTask = input.CompleteAsync(ResolveCompleteReceiveException(error));
200+
if (completeTask.IsCompletedSuccessfully)
201+
{
202+
// Fast path. CompleteAsync completed immediately.
203+
flushTask = ValueTask.FromResult(new FlushResult(isCanceled: false, isCompleted: true));
204+
}
205+
else
206+
{
207+
flushTask = AwaitCompleteTaskAsync(completeTask);
208+
}
209+
}
210+
else
211+
{
212+
flushTask = input.FlushAsync();
213+
}
191214

192215
var paused = !flushTask.IsCompleted;
193216

@@ -240,12 +263,23 @@ private async Task DoReceive()
240263
finally
241264
{
242265
// If Shutdown() has already bee called, assume that was the reason ProcessReceives() exited.
243-
Input.Complete(_shutdownReadReason ?? _shutdownReason ?? error);
266+
Input.Complete(ResolveCompleteReceiveException(error));
244267

245268
FireStreamClosed();
246269

247270
await _waitForConnectionClosedTcs.Task;
248271
}
272+
273+
async static ValueTask<FlushResult> AwaitCompleteTaskAsync(ValueTask completeTask)
274+
{
275+
await completeTask;
276+
return new FlushResult(isCanceled: false, isCompleted: true);
277+
}
278+
}
279+
280+
private Exception? ResolveCompleteReceiveException(Exception? error)
281+
{
282+
return _shutdownReadReason ?? _shutdownReason ?? error;
249283
}
250284

251285
private void FireStreamClosed()

src/Servers/Kestrel/Transport.Quic/test/QuicStreamContextTests.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,38 @@ public async Task ClientToServerUnidirectionalStream_ClientAbort_ServerReceivesA
255255
await closedTcs.Task.DefaultTimeout();
256256
}
257257

258+
[ConditionalFact]
259+
[MsQuicSupported]
260+
public async Task ClientToServerUnidirectionalStream_CompleteWrites_PipeProvidesDataAndCompleteTogether()
261+
{
262+
// Arrange
263+
await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory);
264+
265+
var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint);
266+
using var quicConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options);
267+
await quicConnection.ConnectAsync().DefaultTimeout();
268+
269+
await using var serverConnection = await connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout();
270+
271+
// Act
272+
await using var clientStream = quicConnection.OpenUnidirectionalStream();
273+
await clientStream.WriteAsync(TestData).DefaultTimeout();
274+
275+
await using var serverStream = await serverConnection.AcceptAsync().DefaultTimeout();
276+
var readResult = await serverStream.Transport.Input.ReadAtLeastAsync(TestData.Length).DefaultTimeout();
277+
serverStream.Transport.Input.AdvanceTo(readResult.Buffer.End);
278+
279+
var readResultTask = serverStream.Transport.Input.ReadAsync();
280+
281+
await clientStream.WriteAsync(TestData, endStream: true).DefaultTimeout();
282+
283+
// Assert
284+
var completeReadResult = await readResultTask.DefaultTimeout();
285+
286+
Assert.Equal(TestData, completeReadResult.Buffer.ToArray());
287+
Assert.True(completeReadResult.IsCompleted);
288+
}
289+
258290
[ConditionalFact]
259291
[MsQuicSupported]
260292
public async Task ServerToClientUnidirectionalStream_ServerWritesDataAndCompletes_GracefullyClosed()

0 commit comments

Comments
 (0)