Skip to content

Commit 6b4cf67

Browse files
HTTP/3: Use new QuicStream.ReadsCompleted property in transport (#35482)
Co-authored-by: James Newton-King <[email protected]>
1 parent 1329a6f commit 6b4cf67

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
@@ -191,7 +191,30 @@ private async Task DoReceive()
191191

192192
input.Advance(bytesReceived);
193193

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

196219
var paused = !flushTask.IsCompleted;
197220

@@ -244,12 +267,23 @@ private async Task DoReceive()
244267
finally
245268
{
246269
// If Shutdown() has already bee called, assume that was the reason ProcessReceives() exited.
247-
Input.Complete(_shutdownReadReason ?? _shutdownReason ?? error);
270+
Input.Complete(ResolveCompleteReceiveException(error));
248271

249272
FireStreamClosed();
250273

251274
await _waitForConnectionClosedTcs.Task;
252275
}
276+
277+
async static ValueTask<FlushResult> AwaitCompleteTaskAsync(ValueTask completeTask)
278+
{
279+
await completeTask;
280+
return new FlushResult(isCanceled: false, isCompleted: true);
281+
}
282+
}
283+
284+
private Exception? ResolveCompleteReceiveException(Exception? error)
285+
{
286+
return _shutdownReadReason ?? _shutdownReason ?? error;
253287
}
254288

255289
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
@@ -303,6 +303,38 @@ public async Task ClientToServerUnidirectionalStream_ClientAbort_ServerReceivesA
303303
await closedTcs.Task.DefaultTimeout();
304304
}
305305

306+
[ConditionalFact]
307+
[MsQuicSupported]
308+
public async Task ClientToServerUnidirectionalStream_CompleteWrites_PipeProvidesDataAndCompleteTogether()
309+
{
310+
// Arrange
311+
await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory);
312+
313+
var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint);
314+
using var quicConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options);
315+
await quicConnection.ConnectAsync().DefaultTimeout();
316+
317+
await using var serverConnection = await connectionListener.AcceptAndAddFeatureAsync().DefaultTimeout();
318+
319+
// Act
320+
await using var clientStream = quicConnection.OpenUnidirectionalStream();
321+
await clientStream.WriteAsync(TestData).DefaultTimeout();
322+
323+
await using var serverStream = await serverConnection.AcceptAsync().DefaultTimeout();
324+
var readResult = await serverStream.Transport.Input.ReadAtLeastAsync(TestData.Length).DefaultTimeout();
325+
serverStream.Transport.Input.AdvanceTo(readResult.Buffer.End);
326+
327+
var readResultTask = serverStream.Transport.Input.ReadAsync();
328+
329+
await clientStream.WriteAsync(TestData, endStream: true).DefaultTimeout();
330+
331+
// Assert
332+
var completeReadResult = await readResultTask.DefaultTimeout();
333+
334+
Assert.Equal(TestData, completeReadResult.Buffer.ToArray());
335+
Assert.True(completeReadResult.IsCompleted);
336+
}
337+
306338
[ConditionalFact]
307339
[MsQuicSupported]
308340
public async Task ServerToClientUnidirectionalStream_ServerWritesDataAndCompletes_GracefullyClosed()

0 commit comments

Comments
 (0)