-
Notifications
You must be signed in to change notification settings - Fork 527
Add response minimum data rate feature #1939
Conversation
Perf: dev: 1,196,131.76 RPS |
TimeSpan.FromSeconds(size / minResponseDataRate.BytesPerSecond).Ticks); | ||
var timeoutTimestamp = (_writeTimingTimeouts.Count == 0 | ||
? _lastTimestamp | ||
: _writeTimingTimeouts.Last()) + timeoutTicks; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be clearer if timeoutTimestamp += timeoutTicks
was on its own line. I missed it at first because it looks like it's part of the false
condition.
Also, I believe the call to Last()
is going to be expensive. It calls a Queue.GetEnumerator()
and walks the collection to the end.
@@ -4,7 +4,7 @@ | |||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Features | |||
{ | |||
/// <summary> | |||
/// Represents a minimum data rate for the request body of an HTTP request. | |||
/// Feature to set the minimum data rate at which the the request body should be received. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I would go with "should be received" to "must be sent". I would change the MinDataRate property doc comment to match.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Features | ||
{ | ||
/// <summary> | ||
/// Feature to set the minimum data rate at which the response should be sent. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would change "should be sent" to "must be received".
public interface IHttpMinResponseDataRateFeature | ||
{ | ||
/// <summary> | ||
/// The minimum data rate in bytes/second at which the response should be sent. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
diit.
{ | ||
// If the pipe writer was closed with a TimeoutException, a write timed out. | ||
// Disposing the socket will cancel the pending UvWriteReq. | ||
if (ex is TimeoutException) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's reasonable to have the contract be to close the socket immediate if the output/response pipe is completed with any exception. That would allow us to just make this a null check.
test/shared/TestServiceContext.cs
Outdated
AddServerHeader = false, | ||
Limits = | ||
{ | ||
MinResponseDataRate = new MinDataRate(bytesPerSecond: 1, gracePeriod: TimeSpan.MaxValue) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is super scary. If we have to do this, let's try to make this change to specific tests.
else | ||
} | ||
|
||
private void CheckForReadTimeout(long timestamp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I would rename this methods to CheckForReadRateTimeout and CheckForWriteRateTimeout to make it clear that these aren't absolute timeouts.
|
||
private void CheckForReadTimeout(long timestamp) | ||
{ | ||
if (TimedOut || _timeoutTimestamp < long.MaxValue) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this deserves a comment.
We don't check for read timeouts if there's a _timeoutTimestamp set? Why not? Isn't this taken care of by _readTimingEnabled?
If we really don't want to do this, It would probably be both more correct an easier to read as if (TimedOut || Interlocked.Read(_timeoutTimestamp) != long.MaxValue)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added comment and a test.
13454c3
to
093a6fe
Compare
// Add Heartbeat.Interval since this can be called right before the next heartbeat. | ||
_writeTimingLastTimeoutTimestamp += timeoutTicks + Heartbeat.Interval.Ticks; | ||
|
||
_writeTimingTimeouts.Enqueue(_writeTimingLastTimeoutTimestamp); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of maintaining the queue, I would just add to the previous write timeout timestamp. While this could make a timeout take longer than it would with the queue, it's no different than a larger buffer being written.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. It was my initial thought, but I figured it would be easier to remove the queue if I got that feedback than add it later 😁
@@ -1382,6 +1385,15 @@ private IPipe CreateRequestBodyPipe() | |||
MaximumSizeLow = 1 | |||
}); | |||
|
|||
private IPipe CreateResponsePipe() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still used anywhere?
What do benchmarks look like that we've dropped the queue ? |
|
||
if (_writeTimingWrites == 0) | ||
{ | ||
_writeTimingTimeoutTimestamp = _lastTimestamp; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can just add Heartbeat.Interval.Ticks
here and not below.
@natemcmaster Still good, 1,194,528.36 RPS. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
89e58b1
to
73c73a2
Compare
{ | ||
throw new ArgumentOutOfRangeException(nameof(bytesPerSecond), CoreStrings.NonNegativeNumberRequired); | ||
throw new ArgumentOutOfRangeException(nameof(bytesPerSecond), CoreStrings.PositiveNumberRequired); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to include something like the following in the exception message. "If you want to disable a minimum rate, use a null MinDataRate instead."
@@ -1,6 +1,7 @@ | |||
// Copyright (c) .NET Foundation. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | |||
|
|||
using System; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: remove using.
ca11731
to
8e53ff0
Compare
8e53ff0
to
81d1691
Compare
Alternative to #1937 that doesn't kill perf.
Instead of piping writes to a loop, it keeps track of each write's timeout in a queue of timeouts.
@natemcmaster I've addressed your feedback here (except the bit about connection state in
ResponseTests
, I'm currently working on that).