Skip to content

Commit 1f35f3a

Browse files
committed
Majority of feedback.
1 parent 86ac73b commit 1f35f3a

File tree

8 files changed

+179
-274
lines changed

8 files changed

+179
-274
lines changed

src/Servers/Kestrel/Core/src/Internal/Http/Http1ChunkedEncodingMessageBody.cs

Lines changed: 4 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ public override async ValueTask<ReadResult> ReadAsync(CancellationToken cancella
7474

7575
try
7676
{
77-
_readResult = await StartTimingReadAsync(cancellationToken);
77+
var readAwaitable = _requestBodyPipe.Reader.ReadAsync(cancellationToken);
78+
79+
_readResult = await StartTimingReadAsync(readAwaitable, cancellationToken);
7880
}
7981
catch (ConnectionAbortedException ex)
8082
{
@@ -156,17 +158,7 @@ private async Task PumpAsync()
156158
}
157159

158160
// Read() will have already have greedily consumed the entire request body if able.
159-
if (result.IsCompleted)
160-
{
161-
// OnInputOrOutputCompleted() is an idempotent method that closes the connection. Sometimes
162-
// input completion is observed here before the Input.OnWriterCompleted() callback is fired,
163-
// so we call OnInputOrOutputCompleted() now to prevent a race in our tests where a 400
164-
// response is written after observing the unexpected end of request content instead of just
165-
// closing the connection without a response as expected.
166-
_context.OnInputOrOutputCompleted();
167-
168-
BadHttpRequestException.Throw(RequestRejectionReason.UnexpectedEndOfRequestContent);
169-
}
161+
CheckCompletedReadResult(result);
170162
}
171163
finally
172164
{
@@ -218,77 +210,6 @@ private async Task StopAsyncAwaited()
218210
_requestBodyPipe.Reset();
219211
}
220212

221-
protected override Task OnConsumeAsync()
222-
{
223-
try
224-
{
225-
if (_requestBodyPipe.Reader.TryRead(out var readResult))
226-
{
227-
_requestBodyPipe.Reader.AdvanceTo(readResult.Buffer.End);
228-
229-
if (readResult.IsCompleted)
230-
{
231-
return Task.CompletedTask;
232-
}
233-
}
234-
}
235-
catch (BadHttpRequestException ex)
236-
{
237-
// At this point, the response has already been written, so this won't result in a 4XX response;
238-
// however, we still need to stop the request processing loop and log.
239-
_context.SetBadRequestState(ex);
240-
return Task.CompletedTask;
241-
}
242-
catch (InvalidOperationException ex)
243-
{
244-
var connectionAbortedException = new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication, ex);
245-
_context.ReportApplicationError(connectionAbortedException);
246-
247-
// Have to abort the connection because we can't finish draining the request
248-
_context.StopProcessingNextRequest();
249-
return Task.CompletedTask;
250-
}
251-
252-
return OnConsumeAsyncAwaited();
253-
}
254-
255-
private async Task OnConsumeAsyncAwaited()
256-
{
257-
Log.RequestBodyNotEntirelyRead(_context.ConnectionIdFeature, _context.TraceIdentifier);
258-
259-
_context.TimeoutControl.SetTimeout(Constants.RequestBodyDrainTimeout.Ticks, TimeoutReason.RequestBodyDrain);
260-
261-
try
262-
{
263-
ReadResult result;
264-
do
265-
{
266-
result = await _requestBodyPipe.Reader.ReadAsync();
267-
_requestBodyPipe.Reader.AdvanceTo(result.Buffer.End);
268-
} while (!result.IsCompleted);
269-
}
270-
catch (BadHttpRequestException ex)
271-
{
272-
_context.SetBadRequestState(ex);
273-
}
274-
catch (ConnectionAbortedException)
275-
{
276-
Log.RequestBodyDrainTimedOut(_context.ConnectionIdFeature, _context.TraceIdentifier);
277-
}
278-
catch (InvalidOperationException ex)
279-
{
280-
var connectionAbortedException = new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication, ex);
281-
_context.ReportApplicationError(connectionAbortedException);
282-
283-
// Have to abort the connection because we can't finish draining the request
284-
_context.StopProcessingNextRequest();
285-
}
286-
finally
287-
{
288-
_context.TimeoutControl.CancelTimeout();
289-
}
290-
}
291-
292213
protected void Copy(ReadOnlySequence<byte> readableBuffer, PipeWriter writableBuffer)
293214
{
294215
if (readableBuffer.IsSingleSegment)
@@ -595,34 +516,6 @@ private int CalculateChunkSize(int extraHexDigit, int currentParsedSize)
595516
return -1; // can't happen, but compiler complains
596517
}
597518

598-
private ValueTask<ReadResult> StartTimingReadAsync(CancellationToken cancellationToken)
599-
{
600-
// The only difference is which reader to use. Let's do the following.
601-
// Make an internal reader that will always be used for whatever operation is needed here
602-
// Keep external one the same always.
603-
var readAwaitable = _requestBodyPipe.Reader.ReadAsync(cancellationToken);
604-
605-
if (!readAwaitable.IsCompleted && _timingEnabled)
606-
{
607-
_backpressure = true;
608-
_context.TimeoutControl.StartTimingRead();
609-
}
610-
611-
return readAwaitable;
612-
}
613-
614-
private void StopTimingRead(long bytesRead)
615-
{
616-
_context.TimeoutControl.BytesRead(bytesRead - _alreadyTimedBytes);
617-
_alreadyTimedBytes = 0;
618-
619-
if (_backpressure)
620-
{
621-
_backpressure = false;
622-
_context.TimeoutControl.StopTimingRead();
623-
}
624-
}
625-
626519
private enum Mode
627520
{
628521
Prefix,

src/Servers/Kestrel/Core/src/Internal/Http/Http1ContentLengthMessageBody.cs

Lines changed: 30 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public override async ValueTask<ReadResult> ReadAsync(CancellationToken cancella
4545
// We internally track an int for that.
4646
while (true)
4747
{
48-
// This isn't great. The issue is that TryRead can get a canceled read result
48+
// _context.RequestTimedOut The issue is that TryRead can get a canceled read result
4949
// which is unknown to StartTimingReadAsync.
5050
if (_context.RequestTimedOut)
5151
{
@@ -54,7 +54,8 @@ public override async ValueTask<ReadResult> ReadAsync(CancellationToken cancella
5454

5555
try
5656
{
57-
_readResult = await StartTimingReadAsync(cancellationToken);
57+
var readAwaitable = _context.Input.ReadAsync(cancellationToken);
58+
_readResult = await StartTimingReadAsync(readAwaitable, cancellationToken);
5859
}
5960
catch (ConnectionAbortedException ex)
6061
{
@@ -63,43 +64,39 @@ public override async ValueTask<ReadResult> ReadAsync(CancellationToken cancella
6364

6465
if (_context.RequestTimedOut)
6566
{
66-
Debug.Assert(_readResult.IsCanceled);
6767
BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout);
6868
}
6969

70+
// Make sure to handle when this is canceled here.
7071
if (_readResult.IsCanceled)
7172
{
72-
if (Interlocked.CompareExchange(ref _userCanceled, 0, 1) == 1)
73+
if (Interlocked.Exchange(ref _userCanceled, 0) == 1)
7374
{
7475
// Ignore the readResult if it wasn't by the user.
7576
break;
7677
}
78+
else
79+
{
80+
// TODO should this reset the timing read?
81+
StopTimingRead(0);
82+
continue;
83+
}
7784
}
7885

7986
var readableBuffer = _readResult.Buffer;
8087
var readableBufferLength = readableBuffer.Length;
8188
StopTimingRead(readableBufferLength);
8289

83-
if (_readResult.IsCompleted)
84-
{
85-
// OnInputOrOutputCompleted() is an idempotent method that closes the connection. Sometimes
86-
// input completion is observed here before the Input.OnWriterCompleted() callback is fired,
87-
// so we call OnInputOrOutputCompleted() now to prevent a race in our tests where a 400
88-
// response is written after observing the unexpected end of request content instead of just
89-
// closing the connection without a response as expected.
90-
_context.OnInputOrOutputCompleted();
91-
92-
BadHttpRequestException.Throw(RequestRejectionReason.UnexpectedEndOfRequestContent);
93-
}
90+
CheckCompletedReadResult(_readResult);
9491

9592
if (readableBufferLength > 0)
9693
{
94+
CreateReadResultFromConnectionReadResult();
95+
9796
break;
9897
}
9998
}
10099

101-
CreateReadResultFromConnectionReadResult();
102-
103100
return _readResult;
104101
}
105102

@@ -115,13 +112,27 @@ public override bool TryRead(out ReadResult readResult)
115112

116113
TryStart();
117114

118-
var boolResult = _context.Input.TryRead(out _readResult);
115+
if (!_context.Input.TryRead(out _readResult))
116+
{
117+
readResult = default;
118+
return false;
119+
}
120+
121+
if (_readResult.IsCanceled)
122+
{
123+
if (Interlocked.Exchange(ref _userCanceled, 0) == 0)
124+
{
125+
// Cancellation wasn't by the user, return default ReadResult
126+
readResult = default;
127+
return false;
128+
}
129+
}
119130

120131
CreateReadResultFromConnectionReadResult();
121132

122133
readResult = _readResult;
123134

124-
return boolResult;
135+
return true;
125136
}
126137

127138
private void ThrowIfCompleted()
@@ -178,33 +189,6 @@ protected override void OnReadStarting()
178189
}
179190
}
180191

181-
private ValueTask<ReadResult> StartTimingReadAsync(CancellationToken cancellationToken)
182-
{
183-
var readAwaitable = _context.Input.ReadAsync(cancellationToken);
184-
185-
if (!readAwaitable.IsCompleted && _timingEnabled)
186-
{
187-
TryProduceContinue();
188-
189-
_backpressure = true;
190-
_context.TimeoutControl.StartTimingRead();
191-
}
192-
193-
return readAwaitable;
194-
}
195-
196-
private void StopTimingRead(long bytesRead)
197-
{
198-
_context.TimeoutControl.BytesRead(bytesRead - _alreadyTimedBytes);
199-
_alreadyTimedBytes = 0;
200-
201-
if (_backpressure)
202-
{
203-
_backpressure = false;
204-
_context.TimeoutControl.StopTimingRead();
205-
}
206-
}
207-
208192
public override void Complete(Exception exception)
209193
{
210194
_context.ReportApplicationError(exception);
@@ -227,76 +211,5 @@ protected override Task OnStopAsync()
227211
Complete(null);
228212
return Task.CompletedTask;
229213
}
230-
231-
protected override Task OnConsumeAsync()
232-
{
233-
try
234-
{
235-
if (TryRead(out var readResult))
236-
{
237-
AdvanceTo(readResult.Buffer.End);
238-
239-
if (readResult.IsCompleted)
240-
{
241-
return Task.CompletedTask;
242-
}
243-
}
244-
}
245-
catch (BadHttpRequestException ex)
246-
{
247-
// At this point, the response has already been written, so this won't result in a 4XX response;
248-
// however, we still need to stop the request processing loop and log.
249-
_context.SetBadRequestState(ex);
250-
return Task.CompletedTask;
251-
}
252-
catch (InvalidOperationException ex)
253-
{
254-
var connectionAbortedException = new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication, ex);
255-
_context.ReportApplicationError(connectionAbortedException);
256-
257-
// Have to abort the connection because we can't finish draining the request
258-
_context.StopProcessingNextRequest();
259-
return Task.CompletedTask;
260-
}
261-
262-
return OnConsumeAsyncAwaited();
263-
}
264-
265-
private async Task OnConsumeAsyncAwaited()
266-
{
267-
Log.RequestBodyNotEntirelyRead(_context.ConnectionIdFeature, _context.TraceIdentifier);
268-
269-
_context.TimeoutControl.SetTimeout(Constants.RequestBodyDrainTimeout.Ticks, TimeoutReason.RequestBodyDrain);
270-
271-
try
272-
{
273-
ReadResult result;
274-
do
275-
{
276-
result = await ReadAsync();
277-
AdvanceTo(result.Buffer.End);
278-
} while (!result.IsCompleted);
279-
}
280-
catch (BadHttpRequestException ex)
281-
{
282-
_context.SetBadRequestState(ex);
283-
}
284-
catch (ConnectionAbortedException)
285-
{
286-
Log.RequestBodyDrainTimedOut(_context.ConnectionIdFeature, _context.TraceIdentifier);
287-
}
288-
catch (InvalidOperationException ex)
289-
{
290-
var connectionAbortedException = new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication, ex);
291-
_context.ReportApplicationError(connectionAbortedException);
292-
293-
// Have to abort the connection because we can't finish draining the request
294-
_context.StopProcessingNextRequest();
295-
}
296-
finally
297-
{
298-
_context.TimeoutControl.CancelTimeout();
299-
}
300-
}
301214
}
302215
}

0 commit comments

Comments
 (0)