Skip to content
This repository has been archived by the owner on Dec 18, 2018. It is now read-only.

Add response minimum data rate feature #1939

Merged
merged 22 commits into from
Jul 8, 2017

Conversation

cesarblum
Copy link
Contributor

@cesarblum cesarblum commented Jul 7, 2017

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).

@cesarblum
Copy link
Contributor Author

Perf:

dev: 1,196,131.76 RPS
PR: 1,191,081.72

TimeSpan.FromSeconds(size / minResponseDataRate.BytesPerSecond).Ticks);
var timeoutTimestamp = (_writeTimingTimeouts.Count == 0
? _lastTimestamp
: _writeTimingTimeouts.Last()) + timeoutTicks;
Copy link
Contributor

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.
Copy link
Member

@halter73 halter73 Jul 7, 2017

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.
Copy link
Member

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.
Copy link
Member

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)
Copy link
Member

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.

AddServerHeader = false,
Limits =
{
MinResponseDataRate = new MinDataRate(bytesPerSecond: 1, gracePeriod: TimeSpan.MaxValue)
Copy link
Member

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)
Copy link
Member

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)
Copy link
Member

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)

Copy link
Contributor Author

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.

@cesarblum cesarblum force-pushed the cesarbs/response-minimum-rate-no-loop branch from 13454c3 to 093a6fe Compare July 7, 2017 19:16
// Add Heartbeat.Interval since this can be called right before the next heartbeat.
_writeTimingLastTimeoutTimestamp += timeoutTicks + Heartbeat.Interval.Ticks;

_writeTimingTimeouts.Enqueue(_writeTimingLastTimeoutTimestamp);
Copy link
Member

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.

Copy link
Contributor Author

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()
Copy link
Contributor

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?

@natemcmaster
Copy link
Contributor

What do benchmarks look like that we've dropped the queue ?


if (_writeTimingWrites == 0)
{
_writeTimingTimeoutTimestamp = _lastTimestamp;
Copy link
Member

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.

@cesarblum
Copy link
Contributor Author

@natemcmaster Still good, 1,194,528.36 RPS.

Copy link
Contributor

@natemcmaster natemcmaster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@cesarblum cesarblum force-pushed the cesarbs/response-minimum-rate-no-loop branch from 89e58b1 to 73c73a2 Compare July 8, 2017 00:03
{
throw new ArgumentOutOfRangeException(nameof(bytesPerSecond), CoreStrings.NonNegativeNumberRequired);
throw new ArgumentOutOfRangeException(nameof(bytesPerSecond), CoreStrings.PositiveNumberRequired);
Copy link
Member

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: remove using.

@cesarblum cesarblum force-pushed the cesarbs/response-minimum-rate-no-loop branch from ca11731 to 8e53ff0 Compare July 8, 2017 00:52
@cesarblum cesarblum force-pushed the cesarbs/response-minimum-rate-no-loop branch from 8e53ff0 to 81d1691 Compare July 8, 2017 00:56
@cesarblum cesarblum merged commit eca4bfe into dev Jul 8, 2017
@cesarblum cesarblum deleted the cesarbs/response-minimum-rate-no-loop branch July 8, 2017 05:37
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants