Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP2 Dynamic Window (prototype/early draft) #52862

Closed
wants to merge 114 commits into from
Closed
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
803f47d
Big buffer test
CarnaViire Dec 2, 2020
2b8922d
Merge branch 'main' into http2-big-buffer
CarnaViire Mar 23, 2021
93b691b
Clean up
CarnaViire Mar 23, 2021
6a86867
Merge remote-tracking branch 'upstream/main' into http2-big-buffer
CarnaViire Mar 23, 2021
0767cc4
Clean up
CarnaViire Mar 23, 2021
9a3668c
LargeFileBenchmark
antonfirsov Apr 27, 2021
2949234
Merge remote-tracking branch 'CarnaViire/http2-big-buffer' into http/…
antonfirsov Apr 27, 2021
de6274f
use original DefaultInitialWindowSize
antonfirsov Apr 27, 2021
67ee0a5
Merge branch 'main' into http/window-experiments
antonfirsov May 3, 2021
791ad96
ability to disable initial window + trace in tests
antonfirsov May 4, 2021
1a79274
choose remote peer
antonfirsov May 4, 2021
519fbac
Merge branch 'http/window-experiments' of https://github.com/antonfir…
antonfirsov May 4, 2021
ff533a5
fix build
antonfirsov May 4, 2021
81c38e0
trap black magic
antonfirsov May 4, 2021
b60489c
Merge branch 'http/window-experiments' of https://github.com/antonfir…
antonfirsov May 4, 2021
037484a
size of initial connection window is always 65535
antonfirsov May 5, 2021
8578ba7
move window management algorithm to separate class
antonfirsov May 10, 2021
7ed17f7
FakeRtt
antonfirsov May 10, 2021
869577b
move constants around
antonfirsov May 10, 2021
74df445
ping for fake RTT
antonfirsov May 10, 2021
6e931cd
initial attempt for dynamic window
antonfirsov May 10, 2021
f0a2992
disable magic for now
antonfirsov May 10, 2021
623dfe9
InitialStreamWindowSize argument
antonfirsov May 12, 2021
a9591fb
move LargeFileBenchmark
antonfirsov May 13, 2021
acc64c5
Winsock RTT estimation
antonfirsov May 13, 2021
1c655c3
timeout
antonfirsov May 13, 2021
7d46cdb
benchmarks
antonfirsov May 17, 2021
0e4040b
confugrable StreamWindowUpdateRatio & StreamWindowMagicMultiplier
antonfirsov May 17, 2021
e2d002f
separate update ratios
antonfirsov May 17, 2021
1409ad5
Revert "separate update ratios"
antonfirsov May 17, 2021
da86afa
comment out _LargeFileBenchmark
antonfirsov May 17, 2021
8739547
remove some junk code
antonfirsov May 17, 2021
57ade94
remove some junk code
antonfirsov May 18, 2021
dd2e982
Simplify logic around windowSizeIncrement
antonfirsov May 18, 2021
90dce09
Simplify logic around windowSizeIncrement
antonfirsov May 18, 2021
a8bd26a
use content stream in benchmark
antonfirsov May 18, 2021
4e521e5
use Stopwatch instead of DateTime.Now
antonfirsov May 19, 2021
9bef28c
fix tracing
antonfirsov May 19, 2021
aa256aa
better tests
antonfirsov May 19, 2021
48cf3d2
fix EstimateRttAsync in tests
antonfirsov May 19, 2021
ec2d81a
Merge branch 'main' into http/window-experiments
antonfirsov May 28, 2021
cd73395
use PING for RTT estimation
antonfirsov May 28, 2021
9df8166
Merge branch 'main' into http/window-experiments
antonfirsov Jun 1, 2021
b34df9f
_LargeFileBenchmark custom port
antonfirsov Jun 1, 2021
1645b39
test both kestrel & go
antonfirsov Jun 1, 2021
8355c75
try more regular pings
antonfirsov Jun 1, 2021
e503e25
multi stream test + opt-in static RTT
antonfirsov Jun 2, 2021
461291c
Task.Run
antonfirsov Jun 2, 2021
0e17349
recreate request
antonfirsov Jun 2, 2021
869c377
more specific benchmarks
antonfirsov Jun 2, 2021
3ddd788
EnableDynamicHttp2StreamWindowSizing
antonfirsov Jun 2, 2021
d1cd6d8
benchmark stuff
antonfirsov Jun 3, 2021
197d962
more benchmark tweaking
antonfirsov Jun 3, 2021
85e70ca
utilize SETTINGS frame, calc min RTT , initial burst
antonfirsov Jun 3, 2021
3b95797
benchmark tweak
antonfirsov Jun 3, 2021
9e2a68f
if (_staticRtt.HasValue) return
antonfirsov Jun 3, 2021
0a27183
Merge branch 'http/window-experiments' of https://github.com/antonfir…
antonfirsov Jun 3, 2021
03e154f
better filter
antonfirsov Jun 7, 2021
c01860b
trace time to reach 8M window
antonfirsov Jun 7, 2021
72f290b
Merge branch 'http/window-experiments' of https://github.com/antonfir…
antonfirsov Jun 7, 2021
0683044
do not print "reached 8M window" multiple times
antonfirsov Jun 7, 2021
ee375ca
meh
antonfirsov Jun 7, 2021
ca19ab4
SpecificWindow_KiloBytes
antonfirsov Jun 7, 2021
56700dd
cleanup Download_Dynamic_* data
antonfirsov Jun 7, 2021
66914db
repurpose magic factor
antonfirsov Jun 7, 2021
144c47f
TestHandler: bind to custom local endpoint
antonfirsov Jun 10, 2021
d24d332
duo
antonfirsov Jun 10, 2021
13a5322
meh
antonfirsov Jun 10, 2021
fe053df
rrrrr
antonfirsov Jun 10, 2021
763f368
TestRunCount
antonfirsov Jun 10, 2021
fbdfc29
report to csv
antonfirsov Jun 10, 2021
00fb8a9
round elapsedSec
antonfirsov Jun 10, 2021
1b48ac7
PingEx
antonfirsov Jun 10, 2021
5c59fe7
report RTT
antonfirsov Jun 11, 2021
dab35ab
more benchmark cases
antonfirsov Jun 11, 2021
da9656a
more benchmark cases
antonfirsov Jun 11, 2021
a692cf9
Merge branch 'http/window-experiments' of https://github.com/antonfir…
antonfirsov Jun 11, 2021
c48e340
pass 'details'
antonfirsov Jun 11, 2021
3ef586c
have a warmup
antonfirsov Jun 11, 2021
8a349af
split benchmarks because of limit
antonfirsov Jun 11, 2021
4a0d057
benchmark more
antonfirsov Jun 15, 2021
a5c95b7
StreamWindowMagicMultiplier -> StreamWindowThresholdMultiplier
antonfirsov Jun 15, 2021
ed18c5b
remove Stopwatch allocation
antonfirsov Jun 16, 2021
ce1920a
RttEstimator: do not react when there is no active stream
antonfirsov Jun 16, 2021
f30e6b1
handle GoAway
antonfirsov Jun 16, 2021
86e1d68
fix more tests
antonfirsov Jun 18, 2021
ebb80a9
do not send RTT ping when EndStreamFlag is set
antonfirsov Jun 20, 2021
0afed46
Merge branch 'main' into http/window-experiments
antonfirsov Jun 21, 2021
79024f1
send RTT ping when receiving headers
antonfirsov Jun 21, 2021
bf15458
test
antonfirsov Jun 21, 2021
d42194c
fixed a bunch of Inner Loop HTTP2 tests
antonfirsov Jun 21, 2021
6e0dc5e
fix some OuterLoop tests
antonfirsov Jun 21, 2021
b02415f
window scaling tests
antonfirsov Jun 22, 2021
4b9d7f2
Merge branch 'http/window-experiments' into http/dynamic-window-draft
antonfirsov Jun 22, 2021
9a86ec9
comment out _LargeFileBenchmark
antonfirsov Jun 22, 2021
25f7757
fix test
antonfirsov Jun 22, 2021
37ee6f9
remote "static RTT" tests
antonfirsov Jun 22, 2021
145ace9
improve naming
antonfirsov Jun 22, 2021
3776806
fix SendAsync_Unexpected1xxResponses_DropAllInterimResponses
antonfirsov Jun 23, 2021
8b16b7c
multi-stream benchmarks
antonfirsov Jun 23, 2021
511bf56
API
antonfirsov Jun 23, 2021
49841af
RuntimeSettingParser
antonfirsov Jun 23, 2021
b75827c
final API & runtime switches
antonfirsov Jun 23, 2021
5e388f4
fix _LargeFileBenchmark
antonfirsov Jun 23, 2021
1050b15
disable _LargeFileBenchmark for CI
antonfirsov Jun 23, 2021
e03d3a1
InitialHttp2StreamWindowSize argument validation
antonfirsov Jun 24, 2021
857b8ac
more flow control tests
antonfirsov Jun 24, 2021
a3f0dde
refactor
antonfirsov Jun 24, 2021
03d70ca
remove sandboxing
antonfirsov Jun 24, 2021
0f9f065
StreamWindowScaleThresholdMultiplier tests
antonfirsov Jun 24, 2021
0485d63
clean-up tracing
antonfirsov Jun 24, 2021
bd1b177
make Http2StreamWindowManager struct
antonfirsov Jun 24, 2021
201641e
make RttEstimator non-nullable
antonfirsov Jun 24, 2021
071676a
make RttEstimator a struct
antonfirsov Jun 24, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Net.Sockets;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Winsock
{
private const int SioTcpInfo = unchecked((int)3623878695L);

[DllImport(Interop.Libraries.Ws2_32, SetLastError = true, EntryPoint = "WSAIoctl")]
private static extern SocketError WSAIoctl_Blocking(
SafeSocketHandle socketHandle,
[In] int ioControlCode,
[In] ref int inBuffer,
[In] int inBufferSize,
[Out] out _TCP_INFO_v0 outBuffer,
[In] int outBufferSize,
[Out] out int bytesTransferred,
[In] IntPtr overlapped,
[In] IntPtr completionRoutine);

internal static unsafe SocketError GetTcpInfoV0(SafeSocketHandle socketHandle, out _TCP_INFO_v0 tcpInfo)
{
int input = 0;
return WSAIoctl_Blocking(socketHandle, SioTcpInfo, ref input, sizeof(int), out tcpInfo, sizeof(_TCP_INFO_v0), out _, IntPtr.Zero, IntPtr.Zero);
}

internal struct _TCP_INFO_v0
{
internal System.Net.NetworkInformation.TcpState State;
internal uint Mss;
internal ulong ConnectionTimeMs;
internal byte TimestampsEnabled;
internal uint RttUs;
internal uint MinRttUs;
internal uint BytesInFlight;
internal uint Cwnd;
internal uint SndWnd;
internal uint RcvWnd;
internal uint RcvBuf;
internal ulong BytesOut;
internal ulong BytesIn;
internal uint BytesReordered;
internal uint BytesRetrans;
internal uint FastRetrans;
internal uint DupAcksIn;
internal uint TimeoutEpisodes;
internal byte SynRetrans;
}
}
}
4 changes: 4 additions & 0 deletions src/libraries/System.Net.Http/ref/System.Net.Http.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,10 @@ public sealed partial class SocketsHttpHandler : System.Net.Http.HttpMessageHand
{
public SocketsHttpHandler() { }
public static bool IsSupported { get { throw null; } }
public TimeSpan? FakeRtt { get { throw null; } set { } }
public int InitialStreamWindowSize { get { throw null; } set { } }
public int StreamWindowUpdateRatio { get { throw null; } set { } }
public int StreamWindowMagicMultiplier { get { throw null; } set { } }
public bool AllowAutoRedirect { get { throw null; } set { } }
public System.Net.DecompressionMethods AutomaticDecompression { get { throw null; } set { } }
public System.TimeSpan ConnectTimeout { get { throw null; } set { } }
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@
<Compile Include="System\Net\Http\SocketsHttpHandler\Http2ProtocolException.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\Http2Stream.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\Http2StreamException.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\Http2StreamWindowManager.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\Http3Connection.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\Http3ConnectionException.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\Http3ProtocolException.cs" />
Expand Down Expand Up @@ -411,6 +412,8 @@
Link="Common\Interop\Windows\WinHttp\Interop.winhttp_types.cs" />
<Compile Include="$(CommonPath)\Interop\Windows\WinHttp\Interop.winhttp.cs"
Link="Common\Interop\Windows\WinHttp\Interop.winhttp.cs" />
<Compile Include="$(CommonPath)\Interop\Windows\WinSock\Interop.WSAIoctl.TcpInfo.cs"
Link="Common\Interop\Windows\WinSock\Interop.WSAIoctl.TcpInfo.cs" />
<Compile Include="$(CommonPath)\System\CharArrayHelpers.cs"
Link="Common\System\CharArrayHelpers.cs" />
<Compile Include="$(CommonPath)\System\StringExtensions.cs"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,30 @@ public bool UseCookies
set => throw new PlatformNotSupportedException();
}

public TimeSpan? FakeRtt
{
get => throw new PlatformNotSupportedException();
set => throw new PlatformNotSupportedException();
}

public int InitialStreamWindowSize
{
get => throw new PlatformNotSupportedException();
set => throw new PlatformNotSupportedException();
}

public int StreamWindowUpdateRatio
{
get => throw new PlatformNotSupportedException();
set => throw new PlatformNotSupportedException();
}

public int StreamWindowMagicMultiplier
{
get => throw new PlatformNotSupportedException();
set => throw new PlatformNotSupportedException();
}

[AllowNull]
public CookieContainer CookieContainer
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable

private readonly CreditManager _connectionWindow;
private readonly CreditManager _concurrentStreams;
private readonly RttEstimator? _rttEstimator;

private int _nextStream;
private bool _expectingSettingsAck;
Expand Down Expand Up @@ -79,21 +80,26 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable
#else
private const int InitialConnectionBufferSize = 4096;
#endif

private const int DefaultInitialWindowSize = 65535;
// According to https://httpwg.org/specs/rfc7540.html#rfc.section.6.9.2:
// "The connection flow-control window can only be changed using WINDOW_UPDATE frames."
// We need to initialize _connectionWindow with the value 65535, since
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment isn't correct.

The initial connection window size is defined in the RFC here: https://datatracker.ietf.org/doc/html/rfc7540#section-5.2.1

Copy link
Member Author

Choose a reason for hiding this comment

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

Which part of the commend do you mean? My point is that the initial connection window sizes is a always 65535 according to another section (6.9.2), and can't be configured by SETTINGS frames, therefore we need a different constant.

The initial stream window size, we may want to make it configurable.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree we need two constants, I just think the comment is confusing...

"higher values may violate the protocol" -- higher values are completely in violation of the protocol.

I would suggest just calling this InitialConnectionWindowSize and remove the comment entirely, or just comment with a link to https://datatracker.ietf.org/doc/html/rfc7540#section-5.2.1.

// higher values may violate the protocol, and lead to overflows,
// when the server (or an intermediate proxy) sens a very high connection WINDOW_UPDATE increment.
private const int DefaultInitialConnectionWindowSize = 65535;

// We don't really care about limiting control flow at the connection level.
// We limit it per stream, and the user controls how many streams are created.
// So set the connection window size to a large value.
private const int ConnectionWindowSize = 64 * 1024 * 1024;
private const int ConnectionWindowUpdateRatio = 8;

// We hold off on sending WINDOW_UPDATE until we hit thi minimum threshold.
// This value is somewhat arbitrary; the intent is to ensure it is much smaller than
// the window size itself, or we risk stalling the server because it runs out of window space.
// If we want to further reduce the frequency of WINDOW_UPDATEs, it's probably better to
// increase the window size (and thus increase the threshold proportionally)
// rather than just increase the threshold.
private const int ConnectionWindowThreshold = ConnectionWindowSize / 8;
private const int ConnectionWindowThreshold = ConnectionWindowSize / ConnectionWindowUpdateRatio;

// When buffering outgoing writes, we will automatically buffer up to this number of bytes.
// Single writes that are larger than the buffer can cause the buffer to expand beyond
Expand All @@ -117,7 +123,7 @@ internal enum KeepAliveState
private long _keepAlivePingTimeoutTimestamp;
private volatile KeepAliveState _keepAliveState;

public Http2Connection(HttpConnectionPool pool, Stream stream)
public Http2Connection(HttpConnectionPool pool, Stream stream, System.Net.Sockets.Socket? socket)
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't always have a socket. What do we do if we don't have a socket?

Copy link
Member Author

Choose a reason for hiding this comment

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

If we don't have a NetworkStream and a Socket, I would assume it's an in-memory connection with an RTT close to zero, so we should fall back to a small static window.

If this assumption does not work for some reason, we'll have to introduce an abstraction for providing RTT estimations.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think that's a reasonable assumption in general.

It seems like another option would be to try to measure RTT ourselves, e.g. by detecting how long it takes to ACK a SETTINGS frame or a PING.

Copy link
Member Author

Choose a reason for hiding this comment

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

That would require a bunch of extra code and changes. To get an accurate estimation, we should send out the PINGs multiple times, on a regular basis. We'll basically end up duplicating the corresponding logic from the TCP stack. (And note that TCP can rely on ACK, and doesn't have to do extra pings)

Unless it blocks some important scenarios, I don't think it's worth to do it now, I would rather handle it as an improvement opportunity for future.

Copy link
Contributor

Choose a reason for hiding this comment

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

If you think that's easier and/or provides better data, then I'm fine with that.

That said,

(a) We need to work reasonably well for cases where we don't have access to a socket. A very common case here is just a wrapper stream on top of NetworkStream/SslStream that's used for logging or diagnostics, or just for initial connection creation, etc. Ideally this should work just as well as if you don't use ConnectCallback etc.

It's a really crappy customer story to say "hey we have this cool ConnectCallback, but it will cause a big perf hit in certain cases". We already fixed one major issue here in 6.0 with idle connection polling. Whenever possible, we should strive to not assume we are running on top of a Socket.

(b) Getting the RTT times is plat-specific and thus we're going to end up having to do this for Windows, Linux, and MacOS separately. That looks like a fair amount of work, and I'm not sure it's any easier to do all that vs just do our own RTT estimate.

(c) It's not clear to me how accurate we need the RTT estimate to be. Mostly we just want to grow the buffer sufficiently so it doesn't become a bottleneck. If we err on the side of buffering too much, that's probably not a huge problem, as compared to buffering too little.

{
_pool = pool;
_stream = stream;
Expand All @@ -128,13 +134,17 @@ public Http2Connection(HttpConnectionPool pool, Stream stream)

_httpStreams = new Dictionary<int, Http2Stream>();

_connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialWindowSize);
_connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialConnectionWindowSize);
_concurrentStreams = new CreditManager(this, nameof(_concurrentStreams), InitialMaxConcurrentStreams);
InitialStreamWindowSize = pool.Settings._initialStreamWindowSize;
_rttEstimator = _pool.Settings._fakeRtt != null || socket != null ?
new RttEstimator(this, _pool.Settings._fakeRtt, socket) :
null;

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

_nextStream = 1;
_initialWindowSize = DefaultInitialWindowSize;
_initialWindowSize = DefaultInitialConnectionWindowSize;
Copy link
Contributor

Choose a reason for hiding this comment

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

This is weird now that you've change the name of the constant to DefaultInitialConnectionWindowSize.

_initialWindowSize tracks the current initial window size for streams.

I think we should either leave the naming as-is, or define two separate constants, one for connection and one for stream. I would suggest InitialConnectionWindowSize and DefaultInitalStreamWindowSize. These would both have the same value (65535) but perhaps having two different constants makes their usage clearer.


_maxConcurrentStreams = InitialMaxConcurrentStreams;
_pendingWindowUpdate = 0;
Expand Down Expand Up @@ -178,18 +188,24 @@ public async ValueTask SetupAsync()
s_http2ConnectionPreface.AsSpan().CopyTo(_outgoingBuffer.AvailableSpan);
_outgoingBuffer.Commit(s_http2ConnectionPreface.Length);

// Send SETTINGS frame. Disable push promise.
FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.SettingLength, FrameType.Settings, FrameFlags.None, streamId: 0);
// Send SETTINGS frame. Disable push promise & set initial window size.
FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, 2*FrameHeader.SettingLength, FrameType.Settings, FrameFlags.None, streamId: 0);
_outgoingBuffer.Commit(FrameHeader.Size);
BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.EnablePush);
_outgoingBuffer.Commit(2);
BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, 0);
_outgoingBuffer.Commit(4);
BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.InitialWindowSize);
_outgoingBuffer.Commit(2);
BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, (uint)InitialStreamWindowSize);
Copy link
Contributor

Choose a reason for hiding this comment

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

It's confusing that we now have two very similarly named things:

_initialWindowSize -- this is the value from the peer, for outbound streams
InitialStreamWindowSize -- this is the value we send to the peer, for inbound streams

If we plan on keeping the latter, we should rename these for clarity.

_outgoingBuffer.Commit(4);

// Send initial connection-level WINDOW_UPDATE
uint windowUpdateAmount = (uint)(ConnectionWindowSize - InitialStreamWindowSize);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this using InitialStreamWindowSize instead of DefaultInitialConnectionWindowSize?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, will change.

if (NetEventSource.Log.IsEnabled()) Trace($"Initial connection-level WINDOW_UPDATE, windowUpdateAmount={windowUpdateAmount}");
FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.WindowUpdateLength, FrameType.WindowUpdate, FrameFlags.None, streamId: 0);
_outgoingBuffer.Commit(FrameHeader.Size);
BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, ConnectionWindowSize - DefaultInitialWindowSize);
BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, windowUpdateAmount);
_outgoingBuffer.Commit(4);

await _stream.WriteAsync(_outgoingBuffer.ActiveMemory).ConfigureAwait(false);
Expand Down Expand Up @@ -574,6 +590,7 @@ private void ProcessDataFrame(FrameHeader frameHeader)

if (frameData.Length > 0)
{
_rttEstimator?.UpdateEstimation();
ExtendWindow(frameData.Length);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private sealed class Http2Stream : IValueTaskSource, IHttpHeadersHandler, IHttpT
private HttpResponseHeaders? _trailers;

private MultiArrayBuffer _responseBuffer; // mutable struct, do not make this readonly
private int _pendingWindowUpdate;
private Http2StreamWindowManager _windowManager;
private CreditWaiter? _creditWaiter;
private int _availableCredit;
private readonly object _creditSyncObject = new object(); // split from SyncObject to avoid lock ordering problems with Http2Connection.SyncObject
Expand Down Expand Up @@ -85,11 +85,6 @@ private sealed class Http2Stream : IValueTaskSource, IHttpHeadersHandler, IHttpT

private int _headerBudgetRemaining;

private const int StreamWindowSize = DefaultInitialWindowSize;

// See comment on ConnectionWindowThreshold.
private const int StreamWindowThreshold = StreamWindowSize / 8;

public Http2Stream(HttpRequestMessage request, Http2Connection connection)
{
_request = request;
Expand All @@ -102,7 +97,12 @@ public Http2Stream(HttpRequestMessage request, Http2Connection connection)

_responseBuffer = new MultiArrayBuffer(InitialStreamBufferSize);

_pendingWindowUpdate = 0;
_windowManager = connection._rttEstimator != null ?
new DynamicHttp2StreamWindowManager(connection, this) :
new Http2StreamWindowManager(connection, this);

Trace($"_windowManager: {_windowManager.GetType().Name}");

_headerBudgetRemaining = connection._pool.Settings._maxResponseHeadersLength * 1024;

if (_request.Content == null)
Expand Down Expand Up @@ -149,6 +149,8 @@ public void Initialize(int streamId, int initialWindowSize)

public bool SendRequestFinished => _requestCompletionState != StreamCompletionState.InProgress;

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

public HttpResponseMessage GetAndClearResponse()
{
// Once SendAsync completes, the Http2Stream should no longer hold onto the response message.
Expand Down Expand Up @@ -805,7 +807,7 @@ public void OnResponseData(ReadOnlySpan<byte> buffer, bool endStream)
break;
}

if (_responseBuffer.ActiveMemory.Length + buffer.Length > StreamWindowSize)
if (_responseBuffer.ActiveMemory.Length + buffer.Length > _windowManager.StreamWindowSize)
{
// Window size exceeded.
ThrowProtocolError(Http2ProtocolErrorCode.FlowControlError);
Expand Down Expand Up @@ -1013,30 +1015,6 @@ public async Task ReadResponseHeadersAsync(CancellationToken cancellationToken)
}
}

private void ExtendWindow(int amount)
{
Debug.Assert(amount > 0);
Debug.Assert(_pendingWindowUpdate < StreamWindowThreshold);

if (_responseProtocolState != ResponseProtocolState.ExpectingData)
{
// We are not expecting any more data (because we've either completed or aborted).
// So no need to send any more WINDOW_UPDATEs.
return;
}

_pendingWindowUpdate += amount;
if (_pendingWindowUpdate < StreamWindowThreshold)
{
return;
}

int windowUpdateSize = _pendingWindowUpdate;
_pendingWindowUpdate = 0;

_connection.LogExceptions(_connection.SendWindowUpdateAsync(StreamId, windowUpdateSize));
}

private (bool wait, int bytesRead) TryReadFromBuffer(Span<byte> buffer, bool partOfSyncRead = false)
{
Debug.Assert(buffer.Length > 0);
Expand Down Expand Up @@ -1089,7 +1067,7 @@ public int ReadData(Span<byte> buffer, HttpResponseMessage responseMessage)

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

if (bytesRead != 0)
{
ExtendWindow(bytesRead);
_windowManager.AdjustWindow(bytesRead);
}
else
{
Expand Down Expand Up @@ -1148,7 +1126,7 @@ public void CopyTo(HttpResponseMessage responseMessage, Stream destination, int

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

if (bytesRead != 0)
{
ExtendWindow(bytesRead);
_windowManager.AdjustWindow(bytesRead);
await destination.WriteAsync(new ReadOnlyMemory<byte>(buffer, 0, bytesRead), cancellationToken).ConfigureAwait(false);
}
else
Expand Down
Loading