Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Implement dynamic HTTP/2 window scaling#54755

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
antonfirsov merged 40 commits intodotnet:mainfromantonfirsov:http/dynamic-window-02
Jul 8, 2021
Merged
Show file tree
Hide file tree
Changes from1 commit
Commits
Show all changes
40 commits
Select commitHold shift + click to select a range
1d41853
Implement dynamic HTTP2 window scaling
antonfirsovJun 25, 2021
4067443
Merge branch 'main' into http/dynamic-window-02
antonfirsovJun 25, 2021
a3800d3
actually fix the conflict with #54437
antonfirsovJun 25, 2021
9f50764
add comment on PING payloads
antonfirsovJun 25, 2021
3c7c474
delete StringBuilderOutput
antonfirsovJun 25, 2021
6eb66e9
DisableDynamicWindowSizing: fix name
antonfirsovJun 25, 2021
4c7f3ac
make RttEstimator.MinRtt thread-safe
antonfirsovJun 26, 2021
473139d
simplify RuntimeSettingParserTest code
antonfirsovJun 28, 2021
f2ff4ee
respond to PING while reading CONTINUATION frames in ProcessHeadersFrame
antonfirsovJun 28, 2021
2bf13e0
fix test
antonfirsovJun 28, 2021
526d723
add PingBeforeContinuationFrame_Success
antonfirsovJun 28, 2021
7362e87
WIP: SetupAutomaticPingResponse by default
antonfirsovJun 30, 2021
4dfcfeb
WIP: SetupAutomaticPingResponse by default
antonfirsovJun 30, 2021
6e9142b
fix Http2_PingKeepAlive formatting
antonfirsovJun 30, 2021
e30ab75
delete manual ping response setup code
antonfirsovJun 30, 2021
20416ac
disallow PING frames before CONTINUATION again
antonfirsovJun 30, 2021
9f53e0a
move process-wide settings to GlobalHttpSettings & remove MaximumWind…
antonfirsovJun 30, 2021
13d2066
cleanup defaults
antonfirsovJun 30, 2021
e6cc7c3
nits
antonfirsovJun 30, 2021
b89e4bc
reduce footprint of Http2StreamWindowManager & RttEstimator
antonfirsovJun 30, 2021
1df65a2
comments
antonfirsovJun 30, 2021
3e18b0b
allow receiving PING ACK after GOAWAY
antonfirsovJun 30, 2021
fb3b90e
Merge branch 'main' into http/dynamic-window-02
antonfirsovJun 30, 2021
a54dfdc
nit
antonfirsovJun 30, 2021
dd8d2cc
defer _lastWindowUpdate = Stopwatch.GetTimestamp()
antonfirsovJun 30, 2021
4f302c7
delete extra newlines
antonfirsovJun 30, 2021
e4d8639
remove _respondToPing
antonfirsovJun 30, 2021
c7761e2
Http2LoopbackConnection: allow PING ACK after GOAWAY
antonfirsovJun 30, 2021
e23cac8
EnableTransparentPingResponse = false in Http2_PingKeepAlive
antonfirsovJun 30, 2021
70a04c3
commit suggestion
antonfirsovJun 30, 2021
ddd6ea1
Merge branch 'main' into http/dynamic-window-02
antonfirsovJul 1, 2021
7b006a5
sync DiagnosticsHandler with #54437
antonfirsovJul 1, 2021
bc2b9b5
fix build
antonfirsovJul 1, 2021
95f9e0d
Apply suggestions
antonfirsovJul 2, 2021
aba1735
separate _expectPingFrame and _transparentPingResponse functionality
antonfirsovJul 2, 2021
5cbf0e1
check for _streamWindowSize < MaxStreamWindowSize before trying to ex…
antonfirsovJul 2, 2021
cd94dcf
nit
antonfirsovJul 5, 2021
6954f61
move DefaultInitialHttp2StreamWindowSize
antonfirsovJul 5, 2021
6dbfa47
harden LowBandwidthDelayProduct_ClientStreamReceiveWindowStopsScaling
antonfirsovJul 5, 2021
3009773
delete unreliable LowBandwidthDelayProduct_ClientStreamReceiveWindowS…
antonfirsovJul 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
PrevPrevious commit
NextNext commit
reduce footprint of Http2StreamWindowManager & RttEstimator
  • Loading branch information
@antonfirsov
antonfirsov committedJun 30, 2021
commitb89e4bca0a3a36cdbaa95a354530d96ffc70b51c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,7 +135,7 @@ public Http2Connection(HttpConnectionPool pool, Stream stream)
_connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialWindowSize);
_concurrentStreams = new CreditManager(this, nameof(_concurrentStreams), InitialMaxConcurrentStreams);

_rttEstimator =newRttEstimator(this);
_rttEstimator = RttEstimator.Create();

_writeChannel = Channel.CreateUnbounded<WriteQueueEntry>(s_channelOptions);

Expand DownExpand Up@@ -454,7 +454,7 @@ private async ValueTask ProcessHeadersFrame(FrameHeader frameHeader)
if (http2Stream != null)
{
http2Stream.OnHeadersStart();
_rttEstimator.OnDataOrHeadersReceived();
_rttEstimator.OnDataOrHeadersReceived(this);
headersHandler = http2Stream;
}
else
Expand DownExpand Up@@ -590,7 +590,7 @@ private void ProcessDataFrame(FrameHeader frameHeader)

if (!endStream && frameData.Length > 0)
{
_rttEstimator.OnDataOrHeadersReceived();
_rttEstimator.OnDataOrHeadersReceived(this);
}
}

Expand DownExpand Up@@ -626,7 +626,7 @@ private void ProcessSettingsFrame(FrameHeader frameHeader, bool initialFrame = f
// We only send SETTINGS once initially, so we don't need to do anything in response to the ACK.
// Just remember that we received one and we won't be expecting any more.
_expectingSettingsAck = false;
_rttEstimator.OnInitialSettingsAckReceived();
_rttEstimator.OnInitialSettingsAckReceived(this);
}
else
{
Expand DownExpand Up@@ -2005,7 +2005,7 @@ private void ProcessPingAck(long payload)
// _keepAlivePingPayload is always non-negative.
if (payload < 0) // RTT ping
{
_rttEstimator.OnPingAckReceived(payload);
_rttEstimator.OnPingAckReceived(payload, this);
return;
}
else // Keepalive ping
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -147,6 +147,8 @@ public void Initialize(int streamId, int initialWindowSize)

public bool ExpectResponseData => _responseProtocolState == ResponseProtocolState.ExpectingData;

public Http2Connection Connection => _connection;

public HttpResponseMessage GetAndClearResponse()
{
// Once SendAsync completes, the Http2Stream should no longer hold onto the response message.
Expand DownExpand Up@@ -1063,7 +1065,7 @@ public int ReadData(Span<byte> buffer, HttpResponseMessage responseMessage)

if (bytesRead != 0)
{
_windowManager.AdjustWindow(bytesRead);
_windowManager.AdjustWindow(bytesRead, this);
}
else
{
Expand DownExpand Up@@ -1092,7 +1094,7 @@ public async ValueTask<int> ReadDataAsync(Memory<byte> buffer, HttpResponseMessa

if (bytesRead != 0)
{
_windowManager.AdjustWindow(bytesRead);
_windowManager.AdjustWindow(bytesRead, this);
}
else
{
Expand DownExpand Up@@ -1122,7 +1124,7 @@ public void CopyTo(HttpResponseMessage responseMessage, Stream destination, int

if (bytesRead != 0)
{
_windowManager.AdjustWindow(bytesRead);
_windowManager.AdjustWindow(bytesRead, this);
destination.Write(new ReadOnlySpan<byte>(buffer, 0, bytesRead));
}
else
Expand DownExpand Up@@ -1158,7 +1160,7 @@ public async Task CopyToAsync(HttpResponseMessage responseMessage, Stream destin

if (bytesRead != 0)
{
_windowManager.AdjustWindow(bytesRead);
_windowManager.AdjustWindow(bytesRead, this);
await destination.WriteAsync(new ReadOnlyMemory<byte>(buffer, 0, bytesRead), cancellationToken).ConfigureAwait(false);
}
else
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,35 +17,30 @@ private struct Http2StreamWindowManager
private static int MaxStreamWindowSize => GlobalHttpSettings.SocketsHttpHandler.MaxHttp2StreamWindowSize;
private static bool WindowScalingEnabled => !GlobalHttpSettings.SocketsHttpHandler.DisableDynamicHttp2WindowSizing;

private readonly Http2Connection _connection;
private readonly Http2Stream _stream;

private int _deliveredBytes;
private int _streamWindowSize;
private long _lastWindowUpdate;
Copy link
Member

Choose a reason for hiding this comment

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

You could potentially useValueStopwatch here. It remembers_startTimestamp and haspublic TimeSpan GetElapsedTime(). I haven't used it extensively but@MihaZupan did for telemetry/counters.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I stole the the unit conversion logic fromValueStopWatch because it seemed to be too heavy 😆 We don't need_startTimestamp here, we only need differences compared to the last timestamp.

Copy link
Member

Choose a reason for hiding this comment

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

Well, the_startTimestamp would be your_lastWindowUpdate...

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

A bit hesitant to change & rebenchmark at this point, may try on Monday.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

What I mean is that:

TimeSpandt=_stopwatch.GetElapsedTime();_stopwatch=ValueStopwatch.StartNew();

will callStopwatch.GetTimestamp() (OS call) twice. Although this wouldn't be a noticeable perf issue, I was actually afraid that someone will complain about this in the code review out of perfectionism, so went with manual timestamping instead ofValueStopwatch 😄 Don't want to change this now, want to fix my test and the remaining nits, and get this merged ASAP.


public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream)
{
_connection = connection;
_stream = stream;
HttpConnectionSettings settings = connection._pool.Settings;
_streamWindowSize = settings._initialHttp2StreamWindowSize;
_lastWindowUpdate = Stopwatch.GetTimestamp();
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like this will be initialized when the Http2Stream is created. Shouldn't we wait to initialize this, until we receive response HEADERS or something like that?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Since this is a struct, we need to initialize it anyways. Created aStart() method for_lastWindowUpdate = Stopwatch.GetTimestamp();, which is called when headers are complete.

_deliveredBytes = 0;

if (NetEventSource.Log.IsEnabled())_stream.Trace($"[FlowControl] InitialClientStreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}, WindowScaleThresholdMultiplier: {WindowScaleThresholdMultiplier}");
if (NetEventSource.Log.IsEnabled())stream.Trace($"[FlowControl] InitialClientStreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}, WindowScaleThresholdMultiplier: {WindowScaleThresholdMultiplier}");
}

internal int StreamWindowSize => _streamWindowSize;

internal int StreamWindowThreshold => _streamWindowSize / StreamWindowUpdateRatio;

public void AdjustWindow(int bytesConsumed)
public void AdjustWindow(int bytesConsumed, Http2Stream stream)
{
Debug.Assert(bytesConsumed > 0);
Debug.Assert(_deliveredBytes < StreamWindowThreshold);

if (!_stream.ExpectResponseData)
if (!stream.ExpectResponseData)
{
// We are not expecting any more data (because we've either completed or aborted).
// So no need to send any more WINDOW_UPDATEs.
Expand All@@ -54,15 +49,15 @@ public void AdjustWindow(int bytesConsumed)

if (WindowScalingEnabled)
{
AdjustWindowDynamic(bytesConsumed);
AdjustWindowDynamic(bytesConsumed, stream);
}
else
{
AjdustWindowStatic(bytesConsumed);
AjdustWindowStatic(bytesConsumed, stream);
}
}

private void AjdustWindowStatic(int bytesConsumed)
private void AjdustWindowStatic(int bytesConsumed, Http2Stream stream)
{
_deliveredBytes += bytesConsumed;
if (_deliveredBytes < StreamWindowThreshold)
Expand All@@ -73,11 +68,12 @@ private void AjdustWindowStatic(int bytesConsumed)
int windowUpdateIncrement = _deliveredBytes;
_deliveredBytes = 0;

Task sendWindowUpdateTask = _connection.SendWindowUpdateAsync(_stream.StreamId, windowUpdateIncrement);
_connection.LogExceptions(sendWindowUpdateTask);
Http2Connection connection = stream.Connection;
Task sendWindowUpdateTask = connection.SendWindowUpdateAsync(stream.StreamId, windowUpdateIncrement);
connection.LogExceptions(sendWindowUpdateTask);
}

private void AdjustWindowDynamic(int bytesConsumed)
private void AdjustWindowDynamic(int bytesConsumed, Http2Stream stream)
{
_deliveredBytes += bytesConsumed;

Expand All@@ -88,10 +84,11 @@ private void AdjustWindowDynamic(int bytesConsumed)

int windowUpdateIncrement = _deliveredBytes;
long currentTime = Stopwatch.GetTimestamp();
Http2Connection connection = stream.Connection;

if (_connection._rttEstimator.MinRtt > TimeSpan.Zero)
if (connection._rttEstimator.MinRtt > TimeSpan.Zero)
{
TimeSpan rtt =_connection._rttEstimator.MinRtt;
TimeSpan rtt =connection._rttEstimator.MinRtt;
TimeSpan dt = StopwatchTicksToTimeSpan(currentTime - _lastWindowUpdate);

// We are detecting bursts in the amount of data consumed within a single 'dt' window update period.
Expand All@@ -109,20 +106,20 @@ private void AdjustWindowDynamic(int bytesConsumed)
windowUpdateIncrement += extendedWindowSize - _streamWindowSize;
_streamWindowSize = extendedWindowSize;

if (NetEventSource.Log.IsEnabled())_stream.Trace($"[FlowControl] Updated Stream Window. StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}");
if (NetEventSource.Log.IsEnabled())stream.Trace($"[FlowControl] Updated Stream Window. StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}");

Debug.Assert(_streamWindowSize <= MaxStreamWindowSize);
if (_streamWindowSize == MaxStreamWindowSize)
{
if (NetEventSource.Log.IsEnabled())_stream.Trace($"[FlowControl] StreamWindowSize reached the configured maximum of {MaxStreamWindowSize}.");
if (NetEventSource.Log.IsEnabled())stream.Trace($"[FlowControl] StreamWindowSize reached the configured maximum of {MaxStreamWindowSize}.");
}
}
}

_deliveredBytes = 0;

Task sendWindowUpdateTask =_connection.SendWindowUpdateAsync(_stream.StreamId, windowUpdateIncrement);
_connection.LogExceptions(sendWindowUpdateTask);
Task sendWindowUpdateTask =connection.SendWindowUpdateAsync(stream.StreamId, windowUpdateIncrement);
connection.LogExceptions(sendWindowUpdateTask);

_lastWindowUpdate = currentTime;
}
Expand All@@ -144,11 +141,10 @@ private enum State
PingSent
}

private const double PingIntervalInSeconds = 1;
private const double PingIntervalInSeconds = 2;
private const int InitialBurstCount = 4;
private static readonly long PingIntervalInTicks =(long)(PingIntervalInSeconds * Stopwatch.Frequency);

private Http2Connection _connection;

private State _state;
private long _pingSentTimestamp;
private long _pingCounter;
Expand All@@ -157,14 +153,12 @@ private enum State

public TimeSpan MinRtt => new TimeSpan(_minRtt);

public RttEstimator(Http2Connection connection)
publicstaticRttEstimator Create()
{
_connection = connection;
_state = GlobalHttpSettings.SocketsHttpHandler.DisableDynamicHttp2WindowSizing ? State.Disabled : State.Init;
_pingCounter = 0;
_initialBurst = 4;
_pingSentTimestamp = default;
_minRtt = 0;
RttEstimator e = default;
e._state = GlobalHttpSettings.SocketsHttpHandler.DisableDynamicHttp2WindowSizing ? State.Disabled : State.Init;
e._initialBurst = InitialBurstCount;
return e;
}

internal void OnInitialSettingsSent()
Expand All@@ -173,14 +167,14 @@ internal void OnInitialSettingsSent()
_pingSentTimestamp = Stopwatch.GetTimestamp();
}

internal void OnInitialSettingsAckReceived()
internal void OnInitialSettingsAckReceived(Http2Connection connection)
{
if (_state == State.Disabled) return;
RefreshRtt();
RefreshRtt(connection);
_state = State.Waiting;
}

internal void OnDataOrHeadersReceived()
internal void OnDataOrHeadersReceived(Http2Connection connection)
{
if (_state != State.Waiting) return;

Expand All@@ -192,13 +186,13 @@ internal void OnDataOrHeadersReceived()

// Send a PING
_pingCounter--;
_connection.LogExceptions(_connection.SendPingAsync(_pingCounter, isAck: false));
connection.LogExceptions(connection.SendPingAsync(_pingCounter, isAck: false));
_pingSentTimestamp = now;
_state = State.PingSent;
}
}

internal void OnPingAckReceived(long payload)
internal void OnPingAckReceived(long payload, Http2Connection connection)
{
if (_state != State.PingSent)
{
Expand All@@ -211,7 +205,7 @@ internal void OnPingAckReceived(long payload)
if (_pingCounter != payload)
ThrowProtocolError();

RefreshRtt();
RefreshRtt(connection);
_state = State.Waiting;
}

Expand All@@ -220,7 +214,7 @@ internal void OnGoAwayReceived()
_state = State.Disabled;
}

private void RefreshRtt()
private void RefreshRtt(Http2Connection connection)
{
long elapsedTicks = Stopwatch.GetTimestamp() - _pingSentTimestamp;
long prevRtt = _minRtt == 0 ? long.MaxValue : _minRtt;
Expand All@@ -229,7 +223,7 @@ private void RefreshRtt()

Interlocked.Exchange(ref _minRtt, minRtt); // MinRtt is being queried from another thread

if (NetEventSource.Log.IsEnabled())_connection.Trace($"[FlowControl] Updated MinRtt: {MinRtt.TotalMilliseconds} ms");
if (NetEventSource.Log.IsEnabled())connection.Trace($"[FlowControl] Updated MinRtt: {MinRtt.TotalMilliseconds} ms");
}
}
}
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp