Skip to content

Commit 7f6eaba

Browse files
authored
Fix flaky test ContentLength_Received_NoDataFrames_Reset (#31787)
1 parent 6868d7f commit 7f6eaba

File tree

2 files changed

+66
-8
lines changed

2 files changed

+66
-8
lines changed

src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ public async Task ProcessRequestAsync<TContext>(IHttpApplication<TContext> appli
364364
Log.Http3FrameReceived(ConnectionId, _streamIdFeature.StreamId, _incomingFrame);
365365

366366
consumed = examined = framePayload.End;
367-
await ProcessHttp3Stream(application, framePayload);
367+
await ProcessHttp3Stream(application, framePayload, result.IsCompleted && readableBuffer.IsEmpty);
368368
}
369369
}
370370

@@ -448,14 +448,14 @@ private ValueTask OnEndStreamReceived()
448448
return RequestBodyPipe.Writer.CompleteAsync();
449449
}
450450

451-
private Task ProcessHttp3Stream<TContext>(IHttpApplication<TContext> application, in ReadOnlySequence<byte> payload) where TContext : notnull
451+
private Task ProcessHttp3Stream<TContext>(IHttpApplication<TContext> application, in ReadOnlySequence<byte> payload, bool isCompleted) where TContext : notnull
452452
{
453453
switch (_incomingFrame.Type)
454454
{
455455
case Http3FrameType.Data:
456456
return ProcessDataFrameAsync(payload);
457457
case Http3FrameType.Headers:
458-
return ProcessHeadersFrameAsync(application, payload);
458+
return ProcessHeadersFrameAsync(application, payload, isCompleted);
459459
case Http3FrameType.Settings:
460460
case Http3FrameType.CancelPush:
461461
case Http3FrameType.GoAway:
@@ -478,7 +478,7 @@ private Task ProcessUnknownFrameAsync()
478478
return Task.CompletedTask;
479479
}
480480

481-
private Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> application, ReadOnlySequence<byte> payload) where TContext : notnull
481+
private async Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> application, ReadOnlySequence<byte> payload, bool isCompleted) where TContext : notnull
482482
{
483483
// HEADERS frame after trailing headers is invalid.
484484
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1
@@ -506,19 +506,24 @@ private Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> appli
506506
case RequestHeaderParsingState.Trailers:
507507
// trailers
508508
// TODO figure out if there is anything else to do here.
509-
return Task.CompletedTask;
509+
return;
510510
default:
511511
Debug.Fail("Unexpected header parsing state.");
512512
break;
513513
}
514514

515515
InputRemaining = HttpRequestHeaders.ContentLength;
516516

517+
// If the stream is complete after receiving the headers then run OnEndStreamReceived.
518+
// If there is a bad content length then this will throw before the request delegate is called.
519+
if (isCompleted)
520+
{
521+
await OnEndStreamReceived();
522+
}
523+
517524
_appCompleted = new TaskCompletionSource();
518525

519526
ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false);
520-
521-
return Task.CompletedTask;
522527
}
523528

524529
private Task ProcessDataFrameAsync(in ReadOnlySequence<byte> payload)

src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,11 +639,64 @@ public async Task ContentLength_Received_NoDataFrames_Reset()
639639
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
640640
};
641641

642-
var requestStream = await InitializeConnectionAndStreamsAsync(_noopApplication);
642+
var requestDelegateCalled = false;
643+
var requestStream = await InitializeConnectionAndStreamsAsync(c =>
644+
{
645+
// Bad content-length + end stream means the request delegate
646+
// is never called by the server.
647+
requestDelegateCalled = true;
648+
return Task.CompletedTask;
649+
});
643650

644651
await requestStream.SendHeadersAsync(headers, endStream: true);
645652

646653
await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.ProtocolError, CoreStrings.Http3StreamErrorLessDataThanLength);
654+
655+
Assert.False(requestDelegateCalled);
656+
}
657+
658+
[Fact]
659+
public async Task EndRequestStream_ContinueReadingFromResponse()
660+
{
661+
var headersTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
662+
663+
var headers = new[]
664+
{
665+
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
666+
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
667+
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
668+
};
669+
670+
var data = new byte[] { 1, 2, 3, 4, 5, 6 };
671+
672+
var requestStream = await InitializeConnectionAndStreamsAsync(async context =>
673+
{
674+
await context.Response.BodyWriter.FlushAsync();
675+
676+
await headersTcs.Task;
677+
678+
for (var i = 0; i < data.Length; i++)
679+
{
680+
await Task.Delay(50);
681+
await context.Response.BodyWriter.WriteAsync(new byte[] { data[i] });
682+
}
683+
});
684+
685+
await requestStream.SendHeadersAsync(headers, endStream: true);
686+
await requestStream.ExpectHeadersAsync();
687+
688+
headersTcs.SetResult();
689+
690+
var receivedData = new List<byte>();
691+
while (receivedData.Count < data.Length)
692+
{
693+
var frameData = await requestStream.ExpectDataAsync();
694+
receivedData.AddRange(frameData.ToArray());
695+
}
696+
697+
Assert.Equal(data, receivedData);
698+
699+
await requestStream.ExpectReceiveEndOfStream();
647700
}
648701

649702
[Fact]

0 commit comments

Comments
 (0)