Accurately count only newly examined bytes (#12639)
- Ensure Kestrel count all bytes read using TryRead - Ensure Kestrel doesn't double count examined but not consumed bytes
This commit is contained in:
parent
4debc9c455
commit
75f4159e5a
|
|
@ -44,9 +44,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
public override void AdvanceTo(SequencePosition consumed, SequencePosition examined)
|
public override void AdvanceTo(SequencePosition consumed, SequencePosition examined)
|
||||||
{
|
{
|
||||||
var dataLength = _readResult.Buffer.Slice(_readResult.Buffer.Start, consumed).Length;
|
OnAdvance(_readResult, consumed, examined);
|
||||||
_requestBodyPipe.Reader.AdvanceTo(consumed, examined);
|
_requestBodyPipe.Reader.AdvanceTo(consumed, examined);
|
||||||
OnDataRead(dataLength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool TryRead(out ReadResult readResult)
|
public override bool TryRead(out ReadResult readResult)
|
||||||
|
|
@ -63,6 +62,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
var boolResult = _requestBodyPipe.Reader.TryRead(out _readResult);
|
var boolResult = _requestBodyPipe.Reader.TryRead(out _readResult);
|
||||||
|
|
||||||
readResult = _readResult;
|
readResult = _readResult;
|
||||||
|
CountBytesRead(readResult.Buffer.Length);
|
||||||
|
|
||||||
if (_readResult.IsCompleted)
|
if (_readResult.IsCompleted)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
private bool _readCompleted;
|
private bool _readCompleted;
|
||||||
private bool _isReading;
|
private bool _isReading;
|
||||||
private int _userCanceled;
|
private int _userCanceled;
|
||||||
private long _totalExaminedInPreviousReadResult;
|
|
||||||
private bool _finalAdvanceCalled;
|
private bool _finalAdvanceCalled;
|
||||||
|
|
||||||
public Http1ContentLengthMessageBody(bool keepAlive, long contentLength, Http1Connection context)
|
public Http1ContentLengthMessageBody(bool keepAlive, long contentLength, Http1Connection context)
|
||||||
|
|
@ -85,6 +84,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
if (Interlocked.Exchange(ref _userCanceled, 0) == 1)
|
if (Interlocked.Exchange(ref _userCanceled, 0) == 1)
|
||||||
{
|
{
|
||||||
// Ignore the readResult if it wasn't by the user.
|
// Ignore the readResult if it wasn't by the user.
|
||||||
|
CreateReadResultFromConnectionReadResult();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -156,6 +157,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
CreateReadResultFromConnectionReadResult();
|
CreateReadResultFromConnectionReadResult();
|
||||||
|
|
||||||
readResult = _readResult;
|
readResult = _readResult;
|
||||||
|
CountBytesRead(readResult.Buffer.Length);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -174,11 +176,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
private void CreateReadResultFromConnectionReadResult()
|
private void CreateReadResultFromConnectionReadResult()
|
||||||
{
|
{
|
||||||
if (_readResult.Buffer.Length >= _inputLength + _totalExaminedInPreviousReadResult)
|
if (_readResult.Buffer.Length >= _inputLength + _examinedUnconsumedBytes)
|
||||||
{
|
{
|
||||||
_readCompleted = true;
|
_readCompleted = true;
|
||||||
_readResult = new ReadResult(
|
_readResult = new ReadResult(
|
||||||
_readResult.Buffer.Slice(0, _inputLength + _totalExaminedInPreviousReadResult),
|
_readResult.Buffer.Slice(0, _inputLength + _examinedUnconsumedBytes),
|
||||||
_readResult.IsCanceled && Interlocked.Exchange(ref _userCanceled, 0) == 1,
|
_readResult.IsCanceled && Interlocked.Exchange(ref _userCanceled, 0) == 1,
|
||||||
_readCompleted);
|
_readCompleted);
|
||||||
}
|
}
|
||||||
|
|
@ -217,18 +219,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var consumedLength = _readResult.Buffer.Slice(_readResult.Buffer.Start, consumed).Length;
|
_inputLength -= OnAdvance(_readResult, consumed, examined);
|
||||||
var examinedLength = consumedLength + _readResult.Buffer.Slice(consumed, examined).Length;
|
|
||||||
|
|
||||||
_context.Input.AdvanceTo(consumed, examined);
|
_context.Input.AdvanceTo(consumed, examined);
|
||||||
|
|
||||||
var newlyExamined = examinedLength - _totalExaminedInPreviousReadResult;
|
|
||||||
|
|
||||||
OnDataRead(newlyExamined);
|
|
||||||
_totalExaminedInPreviousReadResult += newlyExamined;
|
|
||||||
_inputLength -= newlyExamined;
|
|
||||||
|
|
||||||
_totalExaminedInPreviousReadResult -= consumedLength;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnReadStarting()
|
protected override void OnReadStarting()
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
protected bool _timingEnabled;
|
protected bool _timingEnabled;
|
||||||
protected bool _backpressure;
|
protected bool _backpressure;
|
||||||
protected long _alreadyTimedBytes;
|
protected long _alreadyTimedBytes;
|
||||||
|
protected long _examinedUnconsumedBytes;
|
||||||
|
|
||||||
protected MessageBody(HttpProtocol context)
|
protected MessageBody(HttpProtocol context)
|
||||||
{
|
{
|
||||||
|
|
@ -165,10 +166,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
return readAwaitable;
|
return readAwaitable;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void StopTimingRead(long bytesRead)
|
protected void CountBytesRead(long bytesInReadResult)
|
||||||
{
|
{
|
||||||
_context.TimeoutControl.BytesRead(bytesRead - _alreadyTimedBytes);
|
var numFirstSeenBytes = bytesInReadResult - _alreadyTimedBytes;
|
||||||
_alreadyTimedBytes = 0;
|
|
||||||
|
if (numFirstSeenBytes > 0)
|
||||||
|
{
|
||||||
|
_context.TimeoutControl.BytesRead(numFirstSeenBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void StopTimingRead(long bytesInReadResult)
|
||||||
|
{
|
||||||
|
CountBytesRead(bytesInReadResult);
|
||||||
|
|
||||||
if (_backpressure)
|
if (_backpressure)
|
||||||
{
|
{
|
||||||
|
|
@ -176,5 +186,62 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
_context.TimeoutControl.StopTimingRead();
|
_context.TimeoutControl.StopTimingRead();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected long OnAdvance(ReadResult readResult, SequencePosition consumed, SequencePosition examined)
|
||||||
|
{
|
||||||
|
// This code path is fairly hard to understand so let's break it down with an example
|
||||||
|
// ReadAsync returns a ReadResult of length 50.
|
||||||
|
// Advance(25, 40). The examined length would be 40 and consumed length would be 25.
|
||||||
|
// _totalExaminedInPreviousReadResult starts at 0. newlyExamined is 40.
|
||||||
|
// OnDataRead is called with length 40.
|
||||||
|
// _totalExaminedInPreviousReadResult is now 40 - 25 = 15.
|
||||||
|
|
||||||
|
// The next call to ReadAsync returns 50 again
|
||||||
|
// Advance(5, 5) is called
|
||||||
|
// newlyExamined is 5 - 15, or -10.
|
||||||
|
// Update _totalExaminedInPreviousReadResult to 10 as we consumed 5.
|
||||||
|
|
||||||
|
// The next call to ReadAsync returns 50 again
|
||||||
|
// _totalExaminedInPreviousReadResult is 10
|
||||||
|
// Advance(50, 50) is called
|
||||||
|
// newlyExamined = 50 - 10 = 40
|
||||||
|
// _totalExaminedInPreviousReadResult is now 50
|
||||||
|
// _totalExaminedInPreviousReadResult is finally 0 after subtracting consumedLength.
|
||||||
|
|
||||||
|
long examinedLength, consumedLength, totalLength;
|
||||||
|
|
||||||
|
if (consumed.Equals(examined))
|
||||||
|
{
|
||||||
|
examinedLength = readResult.Buffer.Slice(readResult.Buffer.Start, examined).Length;
|
||||||
|
consumedLength = examinedLength;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
consumedLength = readResult.Buffer.Slice(readResult.Buffer.Start, consumed).Length;
|
||||||
|
examinedLength = consumedLength + readResult.Buffer.Slice(consumed, examined).Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (examined.Equals(readResult.Buffer.End))
|
||||||
|
{
|
||||||
|
totalLength = examinedLength;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
totalLength = readResult.Buffer.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newlyExamined = examinedLength - _examinedUnconsumedBytes;
|
||||||
|
|
||||||
|
if (newlyExamined > 0)
|
||||||
|
{
|
||||||
|
OnDataRead(newlyExamined);
|
||||||
|
_examinedUnconsumedBytes += newlyExamined;
|
||||||
|
}
|
||||||
|
|
||||||
|
_examinedUnconsumedBytes -= consumedLength;
|
||||||
|
_alreadyTimedBytes = totalLength - consumedLength;
|
||||||
|
|
||||||
|
return newlyExamined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
{
|
{
|
||||||
private readonly Http2Stream _context;
|
private readonly Http2Stream _context;
|
||||||
private ReadResult _readResult;
|
private ReadResult _readResult;
|
||||||
private long _alreadyExaminedInNextReadResult;
|
|
||||||
|
|
||||||
private Http2MessageBody(Http2Stream context)
|
private Http2MessageBody(Http2Stream context)
|
||||||
: base(context)
|
: base(context)
|
||||||
|
|
@ -64,55 +63,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
|
|
||||||
public override void AdvanceTo(SequencePosition consumed, SequencePosition examined)
|
public override void AdvanceTo(SequencePosition consumed, SequencePosition examined)
|
||||||
{
|
{
|
||||||
// This code path is fairly hard to understand so let's break it down with an example
|
OnAdvance(_readResult, consumed, examined);
|
||||||
// ReadAsync returns a ReadResult of length 50.
|
|
||||||
// Advance(25, 40). The examined length would be 40 and consumed length would be 25.
|
|
||||||
// _totalExaminedInPreviousReadResult starts at 0. newlyExamined is 40.
|
|
||||||
// OnDataRead is called with length 40.
|
|
||||||
// _totalExaminedInPreviousReadResult is now 40 - 25 = 15.
|
|
||||||
|
|
||||||
// The next call to ReadAsync returns 50 again
|
|
||||||
// Advance(5, 5) is called
|
|
||||||
// newlyExamined is 5 - 15, or -10.
|
|
||||||
// Update _totalExaminedInPreviousReadResult to 10 as we consumed 5.
|
|
||||||
|
|
||||||
// The next call to ReadAsync returns 50 again
|
|
||||||
// _totalExaminedInPreviousReadResult is 10
|
|
||||||
// Advance(50, 50) is called
|
|
||||||
// newlyExamined = 50 - 10 = 40
|
|
||||||
// _totalExaminedInPreviousReadResult is now 50
|
|
||||||
// _totalExaminedInPreviousReadResult is finally 0 after subtracting consumedLength.
|
|
||||||
|
|
||||||
long examinedLength;
|
|
||||||
long consumedLength;
|
|
||||||
if (consumed.Equals(examined))
|
|
||||||
{
|
|
||||||
examinedLength = _readResult.Buffer.Slice(_readResult.Buffer.Start, examined).Length;
|
|
||||||
consumedLength = examinedLength;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
consumedLength = _readResult.Buffer.Slice(_readResult.Buffer.Start, consumed).Length;
|
|
||||||
examinedLength = consumedLength + _readResult.Buffer.Slice(consumed, examined).Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
_context.RequestBodyPipe.Reader.AdvanceTo(consumed, examined);
|
_context.RequestBodyPipe.Reader.AdvanceTo(consumed, examined);
|
||||||
|
|
||||||
var newlyExamined = examinedLength - _alreadyExaminedInNextReadResult;
|
|
||||||
|
|
||||||
if (newlyExamined > 0)
|
|
||||||
{
|
|
||||||
OnDataRead(newlyExamined);
|
|
||||||
_alreadyExaminedInNextReadResult += newlyExamined;
|
|
||||||
}
|
|
||||||
|
|
||||||
_alreadyExaminedInNextReadResult -= consumedLength;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool TryRead(out ReadResult readResult)
|
public override bool TryRead(out ReadResult readResult)
|
||||||
{
|
{
|
||||||
var result = _context.RequestBodyPipe.Reader.TryRead(out readResult);
|
var result = _context.RequestBodyPipe.Reader.TryRead(out readResult);
|
||||||
_readResult = readResult;
|
_readResult = readResult;
|
||||||
|
CountBytesRead(readResult.Buffer.Length);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue