From 803f47dd8ea1bbb99d7c2df28dc411ba41fd0339 Mon Sep 17 00:00:00 2001 From: Natalia Kondratyeva Date: Wed, 2 Dec 2020 15:54:14 +0000 Subject: [PATCH 001/101] Big buffer test --- .../SocketsHttpHandler/Http2Connection.cs | 12 ++- .../Http/SocketsHttpHandler/Http2Stream.cs | 5 +- .../FunctionalTests/DelegatingHandlerTest.cs | 89 +++++++++++++++++++ 3 files changed, 102 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index e966271b7e5860..c922ab97b65f02 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -80,7 +80,7 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable private const int InitialConnectionBufferSize = 4096; #endif - private const int DefaultInitialWindowSize = 65535; + private const int DefaultInitialWindowSize = 8 * 1024 * 1024;//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. @@ -177,13 +177,21 @@ public async ValueTask SetupAsync() _outgoingBuffer.Commit(s_http2ConnectionPreface.Length); // Send SETTINGS frame. Disable push promise. - FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.SettingLength, FrameType.Settings, FrameFlags.None, streamId: 0); + 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); + // Send SETTINGS frame. Set initial window size. + //FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.SettingLength, FrameType.Settings, FrameFlags.None, streamId: 0); + //_outgoingBuffer.Commit(FrameHeader.Size); + BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.InitialWindowSize); + _outgoingBuffer.Commit(2); + BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, DefaultInitialWindowSize); + _outgoingBuffer.Commit(4); + // Send initial connection-level WINDOW_UPDATE FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.WindowUpdateLength, FrameType.WindowUpdate, FrameFlags.None, streamId: 0); _outgoingBuffer.Commit(FrameHeader.Size); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index 3eed838b552eef..069b09f07d4de6 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -24,7 +24,7 @@ private sealed class Http2Stream : IValueTaskSource, IHttpHeadersHandler, IHttpT #if DEBUG 10; #else - 1024; + 1024; //8 * 1024 * 1024;// #endif private static ReadOnlySpan StatusHeaderName => new byte[] { (byte)':', (byte)'s', (byte)'t', (byte)'a', (byte)'t', (byte)'u', (byte)'s' }; @@ -87,12 +87,13 @@ private sealed class Http2Stream : IValueTaskSource, IHttpHeadersHandler, IHttpT private const int StreamWindowSize = DefaultInitialWindowSize; // See comment on ConnectionWindowThreshold. - private const int StreamWindowThreshold = StreamWindowSize / 8; + private const int StreamWindowThreshold = StreamWindowSize / 2;//8; public Http2Stream(HttpRequestMessage request, Http2Connection connection) { _request = request; _connection = connection; + Trace($"StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); _requestCompletionState = StreamCompletionState.InProgress; _responseCompletionState = StreamCompletionState.InProgress; diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/DelegatingHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/DelegatingHandlerTest.cs index 2cea9dcee94f1a..42225b47b160f3 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/DelegatingHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/DelegatingHandlerTest.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.Tracing; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -223,5 +225,92 @@ protected override void Dispose(bool disposing) } } #endregion + + + [Fact] + public void Test() + //public async void Test() + { + /*var listener = new HttpEventListener(); + // Not needed in clear text scenario, just a remnant of the original test. + var handler = new HttpClientHandler() + { + ServerCertificateCustomValidationCallback = delegate { return true; } + }; + var client = new HttpClient(handler) + { + Timeout = TimeSpan.FromSeconds(100), + DefaultRequestVersion = HttpVersion.Version20, + DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact + }; + + var response = await client.GetAsync($"http://localhost:5001/sendBytes?length={BYTE_LENGTH}"); + + Console.WriteLine(response.StatusCode);*/ + + + + Console.WriteLine("Buffer size 8MB"); + + // var listener = new HttpEventListener(); + + var timer = Diagnostics.Stopwatch.StartNew(); + + for (int i = 0; i < 3; ++i) + { + timer.Restart(); + using (var handler = new SocketsHttpHandler()) + { + var result = TestHandler(handler, new Version(2, 0)); + result.Wait(); + timer.Stop(); + + Console.WriteLine($"SocketsHttpHandler (Success: {result.Result}) HTTP/2.0 in {timer.ElapsedMilliseconds}ms ({BYTE_LENGTH / timer.ElapsedMilliseconds / 1000:N3} MB/s)"); + } + } + } + + const uint BYTE_LENGTH = 26_214_400; // 25MB + + static HttpRequestMessage GenerateRequestMessage(Version httpVersion, uint bytes) + { + // Replace the URL below with the URL of server that can generate an arbitrary number of bytes + return new HttpRequestMessage(HttpMethod.Get, $"&length={bytes}") + { + Version = httpVersion + }; + } + + static async Task TestHandler(HttpMessageHandler handler, Version httpVersion) + { + using (var client = new HttpClient(handler, false)) + { + var message = GenerateRequestMessage(httpVersion, BYTE_LENGTH); + var response = await client.SendAsync(message); + + return response.IsSuccessStatusCode; + } + } + } + internal sealed class HttpEventListener : EventListener + { + + protected override void OnEventSourceCreated(EventSource eventSource) + { + if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http") + EnableEvents(eventSource, EventLevel.LogAlways); + } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] "); + for (int i = 0; i < eventData.Payload?.Count; i++) + { + if (i > 0) + sb.Append(", "); + sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]); + } + Console.WriteLine(sb.ToString()); + } } } From 93b691b8972fd5a6434a728bad1d94de2cf1eaee Mon Sep 17 00:00:00 2001 From: Natalia Kondratyeva Date: Tue, 23 Mar 2021 21:48:20 +0000 Subject: [PATCH 002/101] Clean up --- .../SocketsHttpHandler/Http2Connection.cs | 6 +- .../Http/SocketsHttpHandler/Http2Stream.cs | 4 +- .../FunctionalTests/DelegatingHandlerTest.cs | 89 ------------------- 3 files changed, 6 insertions(+), 93 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 75ae81befb477a..00bcde212b85ff 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -80,12 +80,14 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable private const int InitialConnectionBufferSize = 4096; #endif - private const int DefaultInitialWindowSize = 8 * 1024 * 1024;//65535; + private const int DefaultInitialWindowSize = 8 * 1024 * 1024; + private const int WindowUpdateRatio = 8; // 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 @@ -93,7 +95,7 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable // 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 diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index f6378225ec0e2a..2e1b0b918080a4 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -24,7 +24,7 @@ private sealed class Http2Stream : IValueTaskSource, IHttpHeadersHandler, IHttpT #if DEBUG 10; #else - 1024; //8 * 1024 * 1024;// + 1024; #endif private static ReadOnlySpan StatusHeaderName => new byte[] { (byte)':', (byte)'s', (byte)'t', (byte)'a', (byte)'t', (byte)'u', (byte)'s' }; @@ -88,7 +88,7 @@ private sealed class Http2Stream : IValueTaskSource, IHttpHeadersHandler, IHttpT private const int StreamWindowSize = DefaultInitialWindowSize; // See comment on ConnectionWindowThreshold. - private const int StreamWindowThreshold = StreamWindowSize / 2;//8; + private const int StreamWindowThreshold = StreamWindowSize / WindowUpdateRatio; public Http2Stream(HttpRequestMessage request, Http2Connection connection) { diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/DelegatingHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/DelegatingHandlerTest.cs index 42225b47b160f3..2cea9dcee94f1a 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/DelegatingHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/DelegatingHandlerTest.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics.Tracing; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -225,92 +223,5 @@ protected override void Dispose(bool disposing) } } #endregion - - - [Fact] - public void Test() - //public async void Test() - { - /*var listener = new HttpEventListener(); - // Not needed in clear text scenario, just a remnant of the original test. - var handler = new HttpClientHandler() - { - ServerCertificateCustomValidationCallback = delegate { return true; } - }; - var client = new HttpClient(handler) - { - Timeout = TimeSpan.FromSeconds(100), - DefaultRequestVersion = HttpVersion.Version20, - DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact - }; - - var response = await client.GetAsync($"http://localhost:5001/sendBytes?length={BYTE_LENGTH}"); - - Console.WriteLine(response.StatusCode);*/ - - - - Console.WriteLine("Buffer size 8MB"); - - // var listener = new HttpEventListener(); - - var timer = Diagnostics.Stopwatch.StartNew(); - - for (int i = 0; i < 3; ++i) - { - timer.Restart(); - using (var handler = new SocketsHttpHandler()) - { - var result = TestHandler(handler, new Version(2, 0)); - result.Wait(); - timer.Stop(); - - Console.WriteLine($"SocketsHttpHandler (Success: {result.Result}) HTTP/2.0 in {timer.ElapsedMilliseconds}ms ({BYTE_LENGTH / timer.ElapsedMilliseconds / 1000:N3} MB/s)"); - } - } - } - - const uint BYTE_LENGTH = 26_214_400; // 25MB - - static HttpRequestMessage GenerateRequestMessage(Version httpVersion, uint bytes) - { - // Replace the URL below with the URL of server that can generate an arbitrary number of bytes - return new HttpRequestMessage(HttpMethod.Get, $"&length={bytes}") - { - Version = httpVersion - }; - } - - static async Task TestHandler(HttpMessageHandler handler, Version httpVersion) - { - using (var client = new HttpClient(handler, false)) - { - var message = GenerateRequestMessage(httpVersion, BYTE_LENGTH); - var response = await client.SendAsync(message); - - return response.IsSuccessStatusCode; - } - } - } - internal sealed class HttpEventListener : EventListener - { - - protected override void OnEventSourceCreated(EventSource eventSource) - { - if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http") - EnableEvents(eventSource, EventLevel.LogAlways); - } - - protected override void OnEventWritten(EventWrittenEventArgs eventData) - { - var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] "); - for (int i = 0; i < eventData.Payload?.Count; i++) - { - if (i > 0) - sb.Append(", "); - sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]); - } - Console.WriteLine(sb.ToString()); - } } } From 0767cc4c1cefa53e8f2623a810f57d9f0c3fef4d Mon Sep 17 00:00:00 2001 From: Natalia Kondratyeva Date: Tue, 23 Mar 2021 21:52:47 +0000 Subject: [PATCH 003/101] Clean up --- .../System/Net/Http/SocketsHttpHandler/Http2Connection.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 00bcde212b85ff..73958229bb7fdc 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -178,17 +178,13 @@ public async ValueTask SetupAsync() s_http2ConnectionPreface.AsSpan().CopyTo(_outgoingBuffer.AvailableSpan); _outgoingBuffer.Commit(s_http2ConnectionPreface.Length); - // Send SETTINGS frame. Disable push promise. + // 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); - - // Send SETTINGS frame. Set initial window size. - //FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.SettingLength, FrameType.Settings, FrameFlags.None, streamId: 0); - //_outgoingBuffer.Commit(FrameHeader.Size); BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.InitialWindowSize); _outgoingBuffer.Commit(2); BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, DefaultInitialWindowSize); From 9a3668c802dfb2bddf9d6b73e86fefe5498dd405 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 27 Apr 2021 15:31:50 +0200 Subject: [PATCH 004/101] LargeFileBenchmark --- .../tests/FunctionalTests/HttpClientTest.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs index 883bb8cf725834..d4659f2d3f8280 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs @@ -19,6 +19,53 @@ namespace System.Net.Http.Functional.Tests { + public class LargeFileBenchmark + { + private const string HostName = "10.194.114.94"; + + private readonly ITestOutputHelper _output; + + public LargeFileBenchmark(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public Task Download11() => TestHandler("SocketsHttpHandler HTTP 1.1", false, 5); + + [Fact] + public Task Download20() => TestHandler("SocketsHttpHandler HTTP 2.0", true, 5); + + private async Task TestHandler(string info, bool http2, int lengthMb) + { + using var client = new HttpClient(); + var message = GenerateRequestMessage(HostName, http2, lengthMb); + _output.WriteLine($"{info} / {lengthMb} MB from {HostName}"); + Stopwatch sw = Stopwatch.StartNew(); + var response = await client.SendAsync(message); + long elapsedMs = sw.ElapsedMilliseconds; + + _output.WriteLine($"{info}: {response.StatusCode} in {elapsedMs} ms"); + } + + static HttpRequestMessage GenerateRequestMessage(string hostName, bool http2, int lengthMb = 25) + { + string url = $"http://{hostName}:{(http2 ? "5001" : "5000")}?lengthMb={lengthMb}"; + var msg = new HttpRequestMessage(HttpMethod.Get, url) + { + Version = new Version(1, 1) + }; + + if (http2) + { + msg.Version = new Version(2, 0); + msg.VersionPolicy = HttpVersionPolicy.RequestVersionExact; + } + + return msg; + } + } + public sealed class HttpClientTest : HttpClientHandlerTestBase { public HttpClientTest(ITestOutputHelper output) : base(output) { } From de6274f8238d62b1cfa29507decb731df62c9cfe Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 27 Apr 2021 15:48:23 +0200 Subject: [PATCH 005/101] use original DefaultInitialWindowSize --- .../src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 9d459f2469951e..a97f82a7c2543a 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -80,7 +80,7 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable private const int InitialConnectionBufferSize = 4096; #endif - private const int DefaultInitialWindowSize = 8 * 1024 * 1024; + private const int DefaultInitialWindowSize = 65535; private const int WindowUpdateRatio = 8; // We don't really care about limiting control flow at the connection level. From 791ad962b07966a0a810763cfd5143b5ad8edc7e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 4 May 2021 18:42:51 +0200 Subject: [PATCH 006/101] ability to disable initial window + trace in tests --- .../SocketsHttpHandler/Http2Connection.cs | 11 ++- .../tests/FunctionalTests/HttpClientTest.cs | 81 ++++++++++++++++++- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index bd31bbb64c0cd4..0a068c9f306e37 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -80,7 +80,7 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable private const int InitialConnectionBufferSize = 4096; #endif - private const int DefaultInitialWindowSize = 65535; + private const int DefaultInitialWindowSize = 2 * 65535; private const int WindowUpdateRatio = 8; // We don't really care about limiting control flow at the connection level. @@ -181,6 +181,7 @@ public async ValueTask SetupAsync() _outgoingBuffer.Commit(s_http2ConnectionPreface.Length); // Send SETTINGS frame. Disable push promise & set initial window size. +#if false FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, 2*FrameHeader.SettingLength, FrameType.Settings, FrameFlags.None, streamId: 0); _outgoingBuffer.Commit(FrameHeader.Size); BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.EnablePush); @@ -191,6 +192,14 @@ public async ValueTask SetupAsync() _outgoingBuffer.Commit(2); BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, DefaultInitialWindowSize); _outgoingBuffer.Commit(4); +#else + FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, 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); +#endif // Send initial connection-level WINDOW_UPDATE FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.WindowUpdateLength, FrameType.WindowUpdate, FrameFlags.None, streamId: 0); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs index d4659f2d3f8280..9b67c6f6cde600 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs @@ -4,6 +4,7 @@ using Microsoft.DotNet.RemoteExecutor; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.Tracing; using System.IO; using System.Linq; using System.Net.Quic; @@ -12,6 +13,7 @@ using System.Net.Test.Common; using System.Text; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -19,24 +21,28 @@ namespace System.Net.Http.Functional.Tests { - public class LargeFileBenchmark + public class LargeFileBenchmark : IDisposable { private const string HostName = "10.194.114.94"; private readonly ITestOutputHelper _output; + private LogHttpEventListener _listener; public LargeFileBenchmark(ITestOutputHelper output) { _output = output; + _listener = new LogHttpEventListener(output); } + public void Dispose() => _listener.Dispose(); + [Fact] public Task Download11() => TestHandler("SocketsHttpHandler HTTP 1.1", false, 5); [Fact] - public Task Download20() => TestHandler("SocketsHttpHandler HTTP 2.0", true, 5); + public Task Download20() => TestHandler("SocketsHttpHandler HTTP 2.0", true, 0.1); - private async Task TestHandler(string info, bool http2, int lengthMb) + private async Task TestHandler(string info, bool http2, double lengthMb) { using var client = new HttpClient(); var message = GenerateRequestMessage(HostName, http2, lengthMb); @@ -48,7 +54,7 @@ private async Task TestHandler(string info, bool http2, int lengthMb) _output.WriteLine($"{info}: {response.StatusCode} in {elapsedMs} ms"); } - static HttpRequestMessage GenerateRequestMessage(string hostName, bool http2, int lengthMb = 25) + static HttpRequestMessage GenerateRequestMessage(string hostName, bool http2, double lengthMb = 5) { string url = $"http://{hostName}:{(http2 ? "5001" : "5000")}?lengthMb={lengthMb}"; var msg = new HttpRequestMessage(HttpMethod.Get, url) @@ -1438,4 +1444,71 @@ public sealed class HttpClientSendTest_Sync : HttpClientTest.HttpClientSendTest public HttpClientSendTest_Sync(ITestOutputHelper output) : base(output) { } protected override bool TestAsync => false; } + + + public sealed class LogHttpEventListener : EventListener + { + private Channel _messagesChannel = Channel.CreateUnbounded(); + private Task _processMessages; + private CancellationTokenSource _stopProcessing; + private ITestOutputHelper _log; + + public LogHttpEventListener(ITestOutputHelper log) + { + _log = log; + _messagesChannel = Channel.CreateUnbounded(); + _processMessages = ProcessMessagesAsync(); + _stopProcessing = new CancellationTokenSource(); + } + + protected override void OnEventSourceCreated(EventSource eventSource) + { + if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http") + { + EnableEvents(eventSource, EventLevel.LogAlways); + } + } + + private async Task ProcessMessagesAsync() + { + await Task.Yield(); + + try + { + await foreach (string message in _messagesChannel.Reader.ReadAllAsync(_stopProcessing.Token)) + { + _log.WriteLine(message); + } + } + catch (OperationCanceledException) + { + return; + } + } + + protected override async void OnEventWritten(EventWrittenEventArgs eventData) + { + var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] "); + for (int i = 0; i < eventData.Payload?.Count; i++) + { + if (i > 0) + { + sb.Append(", "); + } + sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]); + } + await _messagesChannel.Writer.WriteAsync(sb.ToString()); + } + + public override void Dispose() + { + base.Dispose(); + + if (!_processMessages.Wait(TimeSpan.FromSeconds(10))) + { + _stopProcessing.Cancel(); + _processMessages.Wait(); + } + } + } } From 1a7927423bd787d250603943a08dc71fdebd2cbc Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 4 May 2021 18:43:49 +0200 Subject: [PATCH 007/101] choose remote peer --- .../tests/FunctionalTests/HttpClientTest.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs index d4659f2d3f8280..30d64adc3f95b0 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs @@ -21,8 +21,6 @@ namespace System.Net.Http.Functional.Tests { public class LargeFileBenchmark { - private const string HostName = "10.194.114.94"; - private readonly ITestOutputHelper _output; public LargeFileBenchmark(ITestOutputHelper output) @@ -30,17 +28,21 @@ public LargeFileBenchmark(ITestOutputHelper output) _output = output; } - [Fact] - public Task Download11() => TestHandler("SocketsHttpHandler HTTP 1.1", false, 5); + [Theory] + [InlineData("172.19.78.199")] + [InlineData("10.194.114.94")] + public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1", hostName, false, 1); - [Fact] - public Task Download20() => TestHandler("SocketsHttpHandler HTTP 2.0", true, 5); + [Theory] + [InlineData("172.19.78.199")] + [InlineData("10.194.114.94")] + public Task Download20(string hostName) => TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, 1); - private async Task TestHandler(string info, bool http2, int lengthMb) + private async Task TestHandler(string info, string hostName, bool http2, int lengthMb) { using var client = new HttpClient(); - var message = GenerateRequestMessage(HostName, http2, lengthMb); - _output.WriteLine($"{info} / {lengthMb} MB from {HostName}"); + var message = GenerateRequestMessage(hostName, http2, lengthMb); + _output.WriteLine($"{info} / {lengthMb} MB from {hostName}"); Stopwatch sw = Stopwatch.StartNew(); var response = await client.SendAsync(message); long elapsedMs = sw.ElapsedMilliseconds; From ff533a5c846bab58173cbbe5a74d4551583607ac Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 4 May 2021 18:48:44 +0200 Subject: [PATCH 008/101] fix build --- .../System.Net.Http/tests/FunctionalTests/HttpClientTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs index a79f034d30f57a..a5757b290064dd 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs @@ -32,6 +32,8 @@ public LargeFileBenchmark(ITestOutputHelper output) _listener = new LogHttpEventListener(output); } + public void Dispose() => _listener.Dispose(); + [Theory] [InlineData("172.19.78.199")] [InlineData("10.194.114.94")] From 81c38e08caaf59db0b679352557a8c05b9b16e10 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 4 May 2021 19:16:03 +0200 Subject: [PATCH 009/101] trap black magic --- .../System/Net/Http/SocketsHttpHandler/Http2Connection.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 0a068c9f306e37..75881da7cab96a 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -785,6 +785,12 @@ private void ProcessWindowUpdateFrame(FrameHeader frameHeader) if (frameHeader.StreamId == 0) { + if (amount > 2147000000) + { + amount++; + amount--; + } + _connectionWindow.AdjustCredit(amount); } else From 037484a2b57a65bcd02b6e2fe1f4963d6e4abdd0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 5 May 2021 17:10:04 +0200 Subject: [PATCH 010/101] size of initial connection window is always 65535 --- .../SocketsHttpHandler/Http2Connection.cs | 21 ++++++++++++------- .../Http/SocketsHttpHandler/Http2Stream.cs | 2 +- .../tests/FunctionalTests/HttpClientTest.cs | 4 ++-- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 75881da7cab96a..ea18c6c7557b04 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -79,8 +79,13 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable #else private const int InitialConnectionBufferSize = 4096; #endif - - private const int DefaultInitialWindowSize = 2 * 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 + // 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; + private const int DefaultInitialStreamWindowSize = 4 * 65535; private const int WindowUpdateRatio = 8; // We don't really care about limiting control flow at the connection level. @@ -130,13 +135,13 @@ public Http2Connection(HttpConnectionPool pool, Stream stream) _httpStreams = new Dictionary(); - _connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialWindowSize); + _connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialConnectionWindowSize); _concurrentStreams = new CreditManager(this, nameof(_concurrentStreams), InitialMaxConcurrentStreams); _writeChannel = Channel.CreateUnbounded(s_channelOptions); _nextStream = 1; - _initialWindowSize = DefaultInitialWindowSize; + _initialWindowSize = DefaultInitialStreamWindowSize; _maxConcurrentStreams = InitialMaxConcurrentStreams; _pendingWindowUpdate = 0; @@ -181,7 +186,7 @@ public async ValueTask SetupAsync() _outgoingBuffer.Commit(s_http2ConnectionPreface.Length); // Send SETTINGS frame. Disable push promise & set initial window size. -#if false +#if true FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, 2*FrameHeader.SettingLength, FrameType.Settings, FrameFlags.None, streamId: 0); _outgoingBuffer.Commit(FrameHeader.Size); BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.EnablePush); @@ -190,7 +195,7 @@ public async ValueTask SetupAsync() _outgoingBuffer.Commit(4); BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.InitialWindowSize); _outgoingBuffer.Commit(2); - BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, DefaultInitialWindowSize); + BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, DefaultInitialStreamWindowSize); _outgoingBuffer.Commit(4); #else FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.SettingLength, FrameType.Settings, FrameFlags.None, streamId: 0); @@ -202,9 +207,11 @@ public async ValueTask SetupAsync() #endif // Send initial connection-level WINDOW_UPDATE + uint windowUpdateAmount = ConnectionWindowSize - DefaultInitialStreamWindowSize; + 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); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index 2e1b0b918080a4..d5483fa9064f58 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -85,7 +85,7 @@ private sealed class Http2Stream : IValueTaskSource, IHttpHeadersHandler, IHttpT private int _headerBudgetRemaining; - private const int StreamWindowSize = DefaultInitialWindowSize; + private const int StreamWindowSize = DefaultInitialStreamWindowSize; // See comment on ConnectionWindowThreshold. private const int StreamWindowThreshold = StreamWindowSize / WindowUpdateRatio; diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs index a5757b290064dd..884515361f308d 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs @@ -37,12 +37,12 @@ public LargeFileBenchmark(ITestOutputHelper output) [Theory] [InlineData("172.19.78.199")] [InlineData("10.194.114.94")] - public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1", hostName, false, 1); + public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1", hostName, false, 5); [Theory] [InlineData("172.19.78.199")] [InlineData("10.194.114.94")] - public Task Download20(string hostName) => TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, 1); + public Task Download20(string hostName) => TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, 5); private async Task TestHandler(string info, string hostName, bool http2, int lengthMb) { From 8578ba704013bcb8bfe07e04b4e7ca843d829392 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 10 May 2021 15:31:14 +0200 Subject: [PATCH 011/101] move window management algorithm to separate class --- .../src/System.Net.Http.csproj | 1 + .../Http/SocketsHttpHandler/Http2Stream.cs | 38 +++---------- .../Http2StreamWindowManager.cs | 55 +++++++++++++++++++ 3 files changed, 64 insertions(+), 30 deletions(-) create mode 100644 src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index 8f495ebd792242..dabc27a7a8a226 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -151,6 +151,7 @@ + diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index d5483fa9064f58..b9ac166570683a 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -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 @@ -103,7 +103,7 @@ public Http2Stream(HttpRequestMessage request, Http2Connection connection) _responseBuffer = new MultiArrayBuffer(InitialStreamBufferSize); - _pendingWindowUpdate = 0; + _windowManager = new Http2StreamWindowManager(connection, this); _headerBudgetRemaining = connection._pool.Settings._maxResponseHeadersLength * 1024; if (_request.Content == null) @@ -150,6 +150,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. @@ -1014,30 +1016,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 buffer, bool partOfSyncRead = false) { Debug.Assert(buffer.Length > 0); @@ -1090,7 +1068,7 @@ public int ReadData(Span buffer, HttpResponseMessage responseMessage) if (bytesRead != 0) { - ExtendWindow(bytesRead); + _windowManager.AdjustWindow(bytesRead); } else { @@ -1119,7 +1097,7 @@ public async ValueTask ReadDataAsync(Memory buffer, HttpResponseMessa if (bytesRead != 0) { - ExtendWindow(bytesRead); + _windowManager.AdjustWindow(bytesRead); } else { @@ -1149,7 +1127,7 @@ public void CopyTo(HttpResponseMessage responseMessage, Stream destination, int if (bytesRead != 0) { - ExtendWindow(bytesRead); + _windowManager.AdjustWindow(bytesRead); destination.Write(new ReadOnlySpan(buffer, 0, bytesRead)); } else @@ -1185,7 +1163,7 @@ public async Task CopyToAsync(HttpResponseMessage responseMessage, Stream destin if (bytesRead != 0) { - ExtendWindow(bytesRead); + _windowManager.AdjustWindow(bytesRead); await destination.WriteAsync(new ReadOnlyMemory(buffer, 0, bytesRead), cancellationToken).ConfigureAwait(false); } else diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs new file mode 100644 index 00000000000000..7681742af9317e --- /dev/null +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -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.Diagnostics; +using System.Threading.Tasks; + +namespace System.Net.Http +{ + internal sealed partial class Http2Connection + { + private sealed class Http2StreamWindowManager + { + private const int StreamWindowSize = DefaultInitialStreamWindowSize; + + // See comment on ConnectionWindowThreshold. + private const int StreamWindowThreshold = StreamWindowSize / WindowUpdateRatio; + + private int _pendingWindowUpdate; + + private readonly Http2Connection _connection; + private readonly Http2Stream _stream; + + public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) + { + _connection = connection; + _stream = stream; + } + + public void AdjustWindow(int bytesConsumed) + { + Debug.Assert(bytesConsumed > 0); + Debug.Assert(_pendingWindowUpdate < StreamWindowThreshold); + + 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. + return; + } + + _pendingWindowUpdate += bytesConsumed; + if (_pendingWindowUpdate < StreamWindowThreshold) + { + return; + } + + int windowUpdateSize = _pendingWindowUpdate; + _pendingWindowUpdate = 0; + + Task sendWindowUpdateTask = _connection.SendWindowUpdateAsync(_stream.StreamId, windowUpdateSize); + _connection.LogExceptions(sendWindowUpdateTask); + } + } + } +} From 7ed17f7a0d238d39adc8c09c88e7d78531ab2e2b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 10 May 2021 18:23:04 +0200 Subject: [PATCH 012/101] FakeRtt --- .../System.Net.Http/ref/System.Net.Http.cs | 1 + .../BrowserHttpHandler/SocketsHttpHandler.cs | 6 +++++ .../SocketsHttpHandler/Http2Connection.cs | 2 ++ .../Http/SocketsHttpHandler/Http2Stream.cs | 5 +++- .../Http2StreamWindowManager.cs | 27 +++++++++++++++---- .../HttpConnectionSettings.cs | 2 ++ .../SocketsHttpHandler/SocketsHttpHandler.cs | 6 +++++ .../tests/FunctionalTests/HttpClientTest.cs | 9 ++++--- 8 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Net.Http/ref/System.Net.Http.cs b/src/libraries/System.Net.Http/ref/System.Net.Http.cs index c05f0e0e9aead8..ffb1063d62afe0 100644 --- a/src/libraries/System.Net.Http/ref/System.Net.Http.cs +++ b/src/libraries/System.Net.Http/ref/System.Net.Http.cs @@ -353,6 +353,7 @@ 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 bool AllowAutoRedirect { get { throw null; } set { } } public System.Net.DecompressionMethods AutomaticDecompression { get { throw null; } set { } } public System.TimeSpan ConnectTimeout { get { throw null; } set { } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs index 0fb0ca57b099a3..1f06eafc25e729 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs @@ -24,6 +24,12 @@ public bool UseCookies set => throw new PlatformNotSupportedException(); } + public TimeSpan? FakeRtt + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + [AllowNull] public CookieContainer CookieContainer { diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index ea18c6c7557b04..1694fb4b5f0440 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -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; @@ -137,6 +138,7 @@ public Http2Connection(HttpConnectionPool pool, Stream stream) _connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialConnectionWindowSize); _concurrentStreams = new CreditManager(this, nameof(_concurrentStreams), InitialMaxConcurrentStreams); + _rttEstimator = _pool.Settings.FakeRtt != null ? new RttEstimator(_pool.Settings.FakeRtt.Value) : null; _writeChannel = Channel.CreateUnbounded(s_channelOptions); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index b9ac166570683a..81f610c922ff02 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -103,7 +103,10 @@ public Http2Stream(HttpRequestMessage request, Http2Connection connection) _responseBuffer = new MultiArrayBuffer(InitialStreamBufferSize); - _windowManager = new Http2StreamWindowManager(connection, this); + _windowManager = connection._rttEstimator != null ? + new Http2StreamWindowManager(connection, this) : + new DynamicHttp2StreamWindowManager(connection, this); + _headerBudgetRemaining = connection._pool.Settings._maxResponseHeadersLength * 1024; if (_request.Content == null) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 7681742af9317e..a710c9cc40d46f 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -8,17 +8,17 @@ namespace System.Net.Http { internal sealed partial class Http2Connection { - private sealed class Http2StreamWindowManager + private class Http2StreamWindowManager { private const int StreamWindowSize = DefaultInitialStreamWindowSize; // See comment on ConnectionWindowThreshold. private const int StreamWindowThreshold = StreamWindowSize / WindowUpdateRatio; - private int _pendingWindowUpdate; + protected int _pendingWindowUpdate; - private readonly Http2Connection _connection; - private readonly Http2Stream _stream; + protected readonly Http2Connection _connection; + protected readonly Http2Stream _stream; public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) { @@ -26,7 +26,7 @@ public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) _stream = stream; } - public void AdjustWindow(int bytesConsumed) + public virtual void AdjustWindow(int bytesConsumed) { Debug.Assert(bytesConsumed > 0); Debug.Assert(_pendingWindowUpdate < StreamWindowThreshold); @@ -51,5 +51,22 @@ public void AdjustWindow(int bytesConsumed) _connection.LogExceptions(sendWindowUpdateTask); } } + + private class DynamicHttp2StreamWindowManager : Http2StreamWindowManager + { + public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream stream) + : base(connection, stream) + { + } + } + + private class RttEstimator + { + public TimeSpan Rtt { get; } + public RttEstimator(TimeSpan fakeRtt) + { + Rtt = fakeRtt; + } + } } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index cf81a5d5a2929a..3e798b3cffae17 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -193,5 +193,7 @@ private static bool AllowDraftHttp3 [SupportedOSPlatform("linux")] [SupportedOSPlatform("macos")] internal byte[] Http3SettingsFrame => _http3SettingsFrame ??= Http3Connection.BuildSettingsFrame(this); + + internal TimeSpan? FakeRtt { get; set; } } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index 8e0b508e6a4858..78b2d4855a36f9 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -54,6 +54,12 @@ public bool UseCookies } } + public TimeSpan? FakeRtt + { + get => _settings.FakeRtt; + set => _settings.FakeRtt = value; + } + [AllowNull] public CookieContainer CookieContainer { diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs index 884515361f308d..5ea4504bcbc201 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs @@ -29,18 +29,19 @@ public class LargeFileBenchmark : IDisposable public LargeFileBenchmark(ITestOutputHelper output) { _output = output; - _listener = new LogHttpEventListener(output); + //_listener = new LogHttpEventListener(output); + _listener = null; } - public void Dispose() => _listener.Dispose(); + public void Dispose() => _listener?.Dispose(); [Theory] - [InlineData("172.19.78.199")] + //[InlineData("172.19.78.199")] [InlineData("10.194.114.94")] public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1", hostName, false, 5); [Theory] - [InlineData("172.19.78.199")] + //[InlineData("172.19.78.199")] [InlineData("10.194.114.94")] public Task Download20(string hostName) => TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, 5); From 869577b4ef11e9546f76d98aeccb033fb64eff3f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 10 May 2021 19:11:15 +0200 Subject: [PATCH 013/101] move constants around --- .../Net/Http/SocketsHttpHandler/Http2Connection.cs | 8 +++----- .../System/Net/Http/SocketsHttpHandler/Http2Stream.cs | 8 +------- .../Http/SocketsHttpHandler/Http2StreamWindowManager.cs | 9 +++++++-- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 1694fb4b5f0440..6eccb2be7ce7d0 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -86,8 +86,6 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable // 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; - private const int DefaultInitialStreamWindowSize = 4 * 65535; - private const int WindowUpdateRatio = 8; // 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. @@ -143,7 +141,7 @@ public Http2Connection(HttpConnectionPool pool, Stream stream) _writeChannel = Channel.CreateUnbounded(s_channelOptions); _nextStream = 1; - _initialWindowSize = DefaultInitialStreamWindowSize; + _initialWindowSize = DefaultInitialConnectionWindowSize; _maxConcurrentStreams = InitialMaxConcurrentStreams; _pendingWindowUpdate = 0; @@ -197,7 +195,7 @@ public async ValueTask SetupAsync() _outgoingBuffer.Commit(4); BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.InitialWindowSize); _outgoingBuffer.Commit(2); - BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, DefaultInitialStreamWindowSize); + BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, InitialStreamWindowSize); _outgoingBuffer.Commit(4); #else FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.SettingLength, FrameType.Settings, FrameFlags.None, streamId: 0); @@ -209,7 +207,7 @@ public async ValueTask SetupAsync() #endif // Send initial connection-level WINDOW_UPDATE - uint windowUpdateAmount = ConnectionWindowSize - DefaultInitialStreamWindowSize; + uint windowUpdateAmount = ConnectionWindowSize - InitialStreamWindowSize; 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); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index 81f610c922ff02..44c89b61d8e5a0 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -85,16 +85,10 @@ private sealed class Http2Stream : IValueTaskSource, IHttpHeadersHandler, IHttpT private int _headerBudgetRemaining; - private const int StreamWindowSize = DefaultInitialStreamWindowSize; - - // See comment on ConnectionWindowThreshold. - private const int StreamWindowThreshold = StreamWindowSize / WindowUpdateRatio; - public Http2Stream(HttpRequestMessage request, Http2Connection connection) { _request = request; _connection = connection; - Trace($"StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); _requestCompletionState = StreamCompletionState.InProgress; _responseCompletionState = StreamCompletionState.InProgress; @@ -811,7 +805,7 @@ public void OnResponseData(ReadOnlySpan 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); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index a710c9cc40d46f..750c0283db66fe 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -8,12 +8,15 @@ namespace System.Net.Http { internal sealed partial class Http2Connection { + private const int StreamWindowUpdateRatio = 8; + private const int InitialStreamWindowSize = 65535; + private class Http2StreamWindowManager { - private const int StreamWindowSize = DefaultInitialStreamWindowSize; + internal int StreamWindowSize => InitialStreamWindowSize; // See comment on ConnectionWindowThreshold. - private const int StreamWindowThreshold = StreamWindowSize / WindowUpdateRatio; + internal int StreamWindowThreshold => StreamWindowSize / StreamWindowUpdateRatio; protected int _pendingWindowUpdate; @@ -24,6 +27,8 @@ public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) { _connection = connection; _stream = stream; + + _stream.Trace($"StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); } public virtual void AdjustWindow(int bytesConsumed) From 74df4450bbc5c0894e89fb7ba3632f737fce4ac2 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 10 May 2021 19:25:48 +0200 Subject: [PATCH 014/101] ping for fake RTT --- .../tests/FunctionalTests/HttpClientTest.cs | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs index 5ea4504bcbc201..2aca73c8d248b2 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs @@ -7,6 +7,7 @@ using System.Diagnostics.Tracing; using System.IO; using System.Linq; +using System.Net.NetworkInformation; using System.Net.Quic; using System.Net.Security; using System.Net.Sockets; @@ -45,9 +46,22 @@ public LargeFileBenchmark(ITestOutputHelper output) [InlineData("10.194.114.94")] public Task Download20(string hostName) => TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, 5); - private async Task TestHandler(string info, string hostName, bool http2, int lengthMb) + [Theory] + //[InlineData("172.19.78.199")] + [InlineData("10.194.114.94")] + public async Task Download20_Dynamic(string hostName) { - using var client = new HttpClient(); + SocketsHttpHandler handler = new SocketsHttpHandler() + { + FakeRtt = await EstimateRttAsync(hostName) + }; + await TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, 5, handler); + } + + private async Task TestHandler(string info, string hostName, bool http2, int lengthMb, HttpMessageHandler handler = null) + { + handler ??= new SocketsHttpHandler(); + using var client = new HttpClient(handler, true); var message = GenerateRequestMessage(hostName, http2, lengthMb); _output.WriteLine($"{info} / {lengthMb} MB from {hostName}"); Stopwatch sw = Stopwatch.StartNew(); @@ -57,6 +71,27 @@ private async Task TestHandler(string info, string hostName, bool http2, int len _output.WriteLine($"{info}: {response.StatusCode} in {elapsedMs} ms"); } + private async Task EstimateRttAsync(string hostName) + { + IPAddress addr; + if (!IPAddress.TryParse(hostName, out addr)) + { + addr = (await Dns.GetHostAddressesAsync(hostName)).FirstOrDefault(e => e.AddressFamily == AddressFamily.InterNetwork); + } + + Ping ping = new Ping(); + + // warmup: + await ping.SendPingAsync(addr); + + PingReply reply1 = await ping.SendPingAsync(addr); + PingReply reply2 = await ping.SendPingAsync(addr); + TimeSpan rtt = new TimeSpan(reply1.RoundtripTime + reply2.RoundtripTime) / 2; + _output.WriteLine($"Estimated RTT: {rtt.TotalMilliseconds} ms"); + return rtt; + } + + static HttpRequestMessage GenerateRequestMessage(string hostName, bool http2, double lengthMb = 5) { string url = $"http://{hostName}:{(http2 ? "5001" : "5000")}?lengthMb={lengthMb}"; From 6e931cd99176225ed3b16f2a15f0d9a4b3cf7d80 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 10 May 2021 20:46:34 +0200 Subject: [PATCH 015/101] initial attempt for dynamic window --- .../SocketsHttpHandler/Http2Connection.cs | 2 +- .../Http/SocketsHttpHandler/Http2Stream.cs | 6 +- .../Http2StreamWindowManager.cs | 60 ++++++++++++++++--- .../HttpConnectionSettings.cs | 7 ++- .../SocketsHttpHandler/SocketsHttpHandler.cs | 4 +- .../tests/FunctionalTests/HttpClientTest.cs | 23 ++++--- 6 files changed, 79 insertions(+), 23 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 6eccb2be7ce7d0..cb9f02e49d77d0 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -136,7 +136,7 @@ public Http2Connection(HttpConnectionPool pool, Stream stream) _connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialConnectionWindowSize); _concurrentStreams = new CreditManager(this, nameof(_concurrentStreams), InitialMaxConcurrentStreams); - _rttEstimator = _pool.Settings.FakeRtt != null ? new RttEstimator(_pool.Settings.FakeRtt.Value) : null; + _rttEstimator = _pool.Settings._fakeRtt != null ? new RttEstimator(_pool.Settings._fakeRtt.Value) : null; _writeChannel = Channel.CreateUnbounded(s_channelOptions); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index 44c89b61d8e5a0..b1b920e2a4595f 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -98,8 +98,10 @@ public Http2Stream(HttpRequestMessage request, Http2Connection connection) _responseBuffer = new MultiArrayBuffer(InitialStreamBufferSize); _windowManager = connection._rttEstimator != null ? - new Http2StreamWindowManager(connection, this) : - new DynamicHttp2StreamWindowManager(connection, this); + new DynamicHttp2StreamWindowManager(connection, this) : + new Http2StreamWindowManager(connection, this); + + Trace($"_windowManager: {_windowManager.GetType().Name}"); _headerBudgetRemaining = connection._pool.Settings._maxResponseHeadersLength * 1024; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 750c0283db66fe..32cbecdb43b4dd 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -13,12 +13,11 @@ internal sealed partial class Http2Connection private class Http2StreamWindowManager { - internal int StreamWindowSize => InitialStreamWindowSize; - // See comment on ConnectionWindowThreshold. internal int StreamWindowThreshold => StreamWindowSize / StreamWindowUpdateRatio; - protected int _pendingWindowUpdate; + protected int _delivered; + protected int _streamWindowSize; protected readonly Http2Connection _connection; protected readonly Http2Stream _stream; @@ -27,14 +26,17 @@ public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) { _connection = connection; _stream = stream; + _streamWindowSize = InitialStreamWindowSize; _stream.Trace($"StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); } + internal int StreamWindowSize => _streamWindowSize; + public virtual void AdjustWindow(int bytesConsumed) { Debug.Assert(bytesConsumed > 0); - Debug.Assert(_pendingWindowUpdate < StreamWindowThreshold); + Debug.Assert(_delivered < StreamWindowThreshold); if (!_stream.ExpectResponseData) { @@ -43,14 +45,14 @@ public virtual void AdjustWindow(int bytesConsumed) return; } - _pendingWindowUpdate += bytesConsumed; - if (_pendingWindowUpdate < StreamWindowThreshold) + _delivered += bytesConsumed; + if (_delivered < StreamWindowThreshold) { return; } - int windowUpdateSize = _pendingWindowUpdate; - _pendingWindowUpdate = 0; + int windowUpdateSize = _delivered; + _delivered = 0; Task sendWindowUpdateTask = _connection.SendWindowUpdateAsync(_stream.StreamId, windowUpdateSize); _connection.LogExceptions(sendWindowUpdateTask); @@ -59,10 +61,52 @@ public virtual void AdjustWindow(int bytesConsumed) private class DynamicHttp2StreamWindowManager : Http2StreamWindowManager { + private DateTime _lastWindowUpdate = DateTime.Now; + + private const long Magic = 10_000; + public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream stream) : base(connection, stream) { } + + public override void AdjustWindow(int bytesConsumed) + { + _delivered += bytesConsumed; + if (_delivered < StreamWindowThreshold) + { + return; + } + + TimeSpan rtt = _connection._rttEstimator!.Rtt; + TimeSpan dt = DateTime.Now - _lastWindowUpdate; + + int windowSizeIncrement = _delivered; + + if (Magic * _delivered * rtt.Ticks > StreamWindowThreshold * dt.Ticks) + { + int newWindowSize = _streamWindowSize * 2; + windowSizeIncrement += newWindowSize - _streamWindowSize; + + _streamWindowSize = newWindowSize; + + _stream.Trace($"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); + } + else + { + string msg = + $"No adjustment! | RTT={rtt.TotalMilliseconds} ms || dt={dt.TotalMilliseconds} ms || " + + //$"_delivered * rtt.Ticks = {_delivered * rtt.Ticks} || StreamWindowThreshold * dt.Ticks = {StreamWindowThreshold * dt.Ticks} ||" + + $"Magic*_delivered/dt = {Magic* _delivered / dt.TotalSeconds} bytes/sec || StreamWindowThreshold/RTT = {StreamWindowThreshold / rtt.TotalSeconds} bytes/sec"; + _stream.Trace(msg); + } + + Task sendWindowUpdateTask = _connection.SendWindowUpdateAsync(_stream.StreamId, windowSizeIncrement); + _connection.LogExceptions(sendWindowUpdateTask); + + _delivered = 0; + _lastWindowUpdate = DateTime.Now; + } } private class RttEstimator diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index 3e798b3cffae17..71d452f41d6a4b 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -67,6 +67,8 @@ internal sealed class HttpConnectionSettings internal IDictionary? _properties; + internal TimeSpan? _fakeRtt; + public HttpConnectionSettings() { bool allowHttp2 = AllowHttp2; @@ -119,7 +121,8 @@ public HttpConnectionSettings CloneAndNormalize() _responseHeaderEncodingSelector = _responseHeaderEncodingSelector, _enableMultipleHttp2Connections = _enableMultipleHttp2Connections, _connectCallback = _connectCallback, - _plaintextStreamFilter = _plaintextStreamFilter + _plaintextStreamFilter = _plaintextStreamFilter, + _fakeRtt = _fakeRtt }; // TODO: Replace with Platform-Guard Assertion Annotations once https://github.com/dotnet/runtime/issues/44922 is finished @@ -193,7 +196,5 @@ private static bool AllowDraftHttp3 [SupportedOSPlatform("linux")] [SupportedOSPlatform("macos")] internal byte[] Http3SettingsFrame => _http3SettingsFrame ??= Http3Connection.BuildSettingsFrame(this); - - internal TimeSpan? FakeRtt { get; set; } } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index 78b2d4855a36f9..b1c97322343535 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -56,8 +56,8 @@ public bool UseCookies public TimeSpan? FakeRtt { - get => _settings.FakeRtt; - set => _settings.FakeRtt = value; + get => _settings._fakeRtt; + set => _settings._fakeRtt = value; } [AllowNull] diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs index 2aca73c8d248b2..6d9b46352adf56 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs @@ -30,35 +30,39 @@ public class LargeFileBenchmark : IDisposable public LargeFileBenchmark(ITestOutputHelper output) { _output = output; - //_listener = new LogHttpEventListener(output); - _listener = null; + _listener = new LogHttpEventListener(output); } public void Dispose() => _listener?.Dispose(); + private const double LengthMb = 5; + [Theory] //[InlineData("172.19.78.199")] [InlineData("10.194.114.94")] - public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1", hostName, false, 5); + public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1", hostName, false, LengthMb); [Theory] //[InlineData("172.19.78.199")] [InlineData("10.194.114.94")] - public Task Download20(string hostName) => TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, 5); + public Task Download20(string hostName) => TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb); [Theory] //[InlineData("172.19.78.199")] [InlineData("10.194.114.94")] public async Task Download20_Dynamic(string hostName) { + _listener.Enabled = true; + _listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize"); + SocketsHttpHandler handler = new SocketsHttpHandler() { FakeRtt = await EstimateRttAsync(hostName) }; - await TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, 5, handler); + await TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb, handler); } - private async Task TestHandler(string info, string hostName, bool http2, int lengthMb, HttpMessageHandler handler = null) + private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, HttpMessageHandler handler = null) { handler ??= new SocketsHttpHandler(); using var client = new HttpClient(handler, true); @@ -1499,6 +1503,9 @@ public LogHttpEventListener(ITestOutputHelper log) _stopProcessing = new CancellationTokenSource(); } + public bool Enabled { get; set; } + public Predicate Filter { get; set; } = _ => true; + protected override void OnEventSourceCreated(EventSource eventSource) { if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http") @@ -1515,7 +1522,7 @@ private async Task ProcessMessagesAsync() { await foreach (string message in _messagesChannel.Reader.ReadAllAsync(_stopProcessing.Token)) { - _log.WriteLine(message); + if (Filter(message)) _log.WriteLine(message); } } catch (OperationCanceledException) @@ -1526,6 +1533,8 @@ private async Task ProcessMessagesAsync() protected override async void OnEventWritten(EventWrittenEventArgs eventData) { + if (!Enabled) return; + var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] "); for (int i = 0; i < eventData.Payload?.Count; i++) { From f0a299236006a44202f214fa8defb1c3a9537720 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 10 May 2021 21:46:53 +0200 Subject: [PATCH 016/101] disable magic for now --- .../Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs | 2 +- .../System.Net.Http/tests/FunctionalTests/HttpClientTest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 32cbecdb43b4dd..677d34beebbb53 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -63,7 +63,7 @@ private class DynamicHttp2StreamWindowManager : Http2StreamWindowManager { private DateTime _lastWindowUpdate = DateTime.Now; - private const long Magic = 10_000; + private const long Magic = 1; public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream stream) : base(connection, stream) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs index 6d9b46352adf56..c359b8f162ef17 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs @@ -53,7 +53,7 @@ public LargeFileBenchmark(ITestOutputHelper output) public async Task Download20_Dynamic(string hostName) { _listener.Enabled = true; - _listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize"); + _listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync"); SocketsHttpHandler handler = new SocketsHttpHandler() { From 623dfe9ba35c1e40b9782966d9417a0061da5e35 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 12 May 2021 16:44:47 +0200 Subject: [PATCH 017/101] InitialStreamWindowSize argument --- .../System.Net.Http/ref/System.Net.Http.cs | 1 + .../BrowserHttpHandler/SocketsHttpHandler.cs | 6 +++++ .../SocketsHttpHandler/Http2Connection.cs | 11 +++++---- .../Http2StreamWindowManager.cs | 24 +++++++++++++++---- .../SocketsHttpHandler/HttpConnectionPool.cs | 4 +++- .../HttpConnectionSettings.cs | 5 +++- .../SocketsHttpHandler/SocketsHttpHandler.cs | 6 +++++ 7 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Net.Http/ref/System.Net.Http.cs b/src/libraries/System.Net.Http/ref/System.Net.Http.cs index ffb1063d62afe0..8f2633a6068be9 100644 --- a/src/libraries/System.Net.Http/ref/System.Net.Http.cs +++ b/src/libraries/System.Net.Http/ref/System.Net.Http.cs @@ -354,6 +354,7 @@ 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 bool AllowAutoRedirect { get { throw null; } set { } } public System.Net.DecompressionMethods AutomaticDecompression { get { throw null; } set { } } public System.TimeSpan ConnectTimeout { get { throw null; } set { } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs index 1f06eafc25e729..45583d12f55357 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs @@ -30,6 +30,12 @@ public TimeSpan? FakeRtt set => throw new PlatformNotSupportedException(); } + public int InitialStreamWindowSize + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + [AllowNull] public CookieContainer CookieContainer { diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index cb9f02e49d77d0..9d31b4ab98acb5 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -123,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) { _pool = pool; _stream = stream; @@ -136,7 +136,10 @@ public Http2Connection(HttpConnectionPool pool, Stream stream) _connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialConnectionWindowSize); _concurrentStreams = new CreditManager(this, nameof(_concurrentStreams), InitialMaxConcurrentStreams); - _rttEstimator = _pool.Settings._fakeRtt != null ? new RttEstimator(_pool.Settings._fakeRtt.Value) : null; + InitialStreamWindowSize = pool.Settings._initialStreamWindowSize; + _rttEstimator = _pool.Settings._fakeRtt != null || socket != null ? + new RttEstimator(this, _pool.Settings._fakeRtt, socket) : + null; _writeChannel = Channel.CreateUnbounded(s_channelOptions); @@ -195,7 +198,7 @@ public async ValueTask SetupAsync() _outgoingBuffer.Commit(4); BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.InitialWindowSize); _outgoingBuffer.Commit(2); - BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, InitialStreamWindowSize); + BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, (uint)InitialStreamWindowSize); _outgoingBuffer.Commit(4); #else FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, FrameHeader.SettingLength, FrameType.Settings, FrameFlags.None, streamId: 0); @@ -207,7 +210,7 @@ public async ValueTask SetupAsync() #endif // Send initial connection-level WINDOW_UPDATE - uint windowUpdateAmount = ConnectionWindowSize - InitialStreamWindowSize; + uint windowUpdateAmount = (uint)(ConnectionWindowSize - InitialStreamWindowSize); 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); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 677d34beebbb53..0417604eae4530 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Net.Sockets; using System.Threading.Tasks; namespace System.Net.Http @@ -9,7 +10,7 @@ namespace System.Net.Http internal sealed partial class Http2Connection { private const int StreamWindowUpdateRatio = 8; - private const int InitialStreamWindowSize = 65535; + private int InitialStreamWindowSize { get; } = 65535; private class Http2StreamWindowManager { @@ -25,12 +26,16 @@ private class Http2StreamWindowManager public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) { _connection = connection; + _stream = stream; + InitialStreamWindowSize = connection.InitialStreamWindowSize; _streamWindowSize = InitialStreamWindowSize; _stream.Trace($"StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); } + internal int InitialStreamWindowSize { get; } + internal int StreamWindowSize => _streamWindowSize; public virtual void AdjustWindow(int bytesConsumed) @@ -111,10 +116,21 @@ public override void AdjustWindow(int bytesConsumed) private class RttEstimator { - public TimeSpan Rtt { get; } - public RttEstimator(TimeSpan fakeRtt) + private Http2Connection _connection; + private readonly Socket? _socket; + public TimeSpan Rtt { get; private set; } + public RttEstimator(Http2Connection connection, TimeSpan? fakeRtt, Socket? socket) + { + _connection = connection; + if (fakeRtt.HasValue) + Rtt = fakeRtt.Value; + _socket = socket; + UpdateRttFromSocket(); + } + + private void UpdateRttFromSocket() { - Rtt = fakeRtt; + if (_socket == null) return; } } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index ba3c8edb48a1b2..21b90dad699911 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -1436,9 +1436,11 @@ private async ValueTask ConstructHttp11ConnectionAsync(bool asyn private async ValueTask ConstructHttp2ConnectionAsync(Stream stream, HttpRequestMessage request, CancellationToken cancellationToken) { + Socket? socket = (stream as NetworkStream)?.Socket; + stream = await ApplyPlaintextFilterAsync(async: true, stream, HttpVersion.Version20, request, cancellationToken).ConfigureAwait(false); - Http2Connection http2Connection = new Http2Connection(this, stream); + Http2Connection http2Connection = new Http2Connection(this, stream, socket); try { await http2Connection.SetupAsync().ConfigureAwait(false); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index 71d452f41d6a4b..1595741659af51 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -69,6 +69,8 @@ internal sealed class HttpConnectionSettings internal TimeSpan? _fakeRtt; + internal int _initialStreamWindowSize = 65535; + public HttpConnectionSettings() { bool allowHttp2 = AllowHttp2; @@ -122,7 +124,8 @@ public HttpConnectionSettings CloneAndNormalize() _enableMultipleHttp2Connections = _enableMultipleHttp2Connections, _connectCallback = _connectCallback, _plaintextStreamFilter = _plaintextStreamFilter, - _fakeRtt = _fakeRtt + _fakeRtt = _fakeRtt, + _initialStreamWindowSize = _initialStreamWindowSize }; // TODO: Replace with Platform-Guard Assertion Annotations once https://github.com/dotnet/runtime/issues/44922 is finished diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index b1c97322343535..4162ac134f3781 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -60,6 +60,12 @@ public TimeSpan? FakeRtt set => _settings._fakeRtt = value; } + public int InitialStreamWindowSize + { + get => _settings._initialStreamWindowSize; + set => _settings._initialStreamWindowSize = value; + } + [AllowNull] public CookieContainer CookieContainer { From a9591fbf5c384eb30550b349cfc412d11a9f73a9 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 13 May 2021 14:58:16 +0200 Subject: [PATCH 018/101] move LargeFileBenchmark --- .../tests/FunctionalTests/HttpClientTest.cs | 168 ---------------- .../System.Net.Http.Functional.Tests.csproj | 3 +- .../FunctionalTests/_LargeFileBenchmark.cs | 189 ++++++++++++++++++ 3 files changed, 191 insertions(+), 169 deletions(-) create mode 100644 src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs index c359b8f162ef17..70f3009457ef80 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs @@ -4,17 +4,13 @@ using Microsoft.DotNet.RemoteExecutor; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.Tracing; using System.IO; using System.Linq; -using System.Net.NetworkInformation; -using System.Net.Quic; using System.Net.Security; using System.Net.Sockets; using System.Net.Test.Common; using System.Text; using System.Threading; -using System.Threading.Channels; using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -22,98 +18,6 @@ namespace System.Net.Http.Functional.Tests { - public class LargeFileBenchmark : IDisposable - { - private readonly ITestOutputHelper _output; - private LogHttpEventListener _listener; - - public LargeFileBenchmark(ITestOutputHelper output) - { - _output = output; - _listener = new LogHttpEventListener(output); - } - - public void Dispose() => _listener?.Dispose(); - - private const double LengthMb = 5; - - [Theory] - //[InlineData("172.19.78.199")] - [InlineData("10.194.114.94")] - public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1", hostName, false, LengthMb); - - [Theory] - //[InlineData("172.19.78.199")] - [InlineData("10.194.114.94")] - public Task Download20(string hostName) => TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb); - - [Theory] - //[InlineData("172.19.78.199")] - [InlineData("10.194.114.94")] - public async Task Download20_Dynamic(string hostName) - { - _listener.Enabled = true; - _listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync"); - - SocketsHttpHandler handler = new SocketsHttpHandler() - { - FakeRtt = await EstimateRttAsync(hostName) - }; - await TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb, handler); - } - - private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, HttpMessageHandler handler = null) - { - handler ??= new SocketsHttpHandler(); - using var client = new HttpClient(handler, true); - var message = GenerateRequestMessage(hostName, http2, lengthMb); - _output.WriteLine($"{info} / {lengthMb} MB from {hostName}"); - Stopwatch sw = Stopwatch.StartNew(); - var response = await client.SendAsync(message); - long elapsedMs = sw.ElapsedMilliseconds; - - _output.WriteLine($"{info}: {response.StatusCode} in {elapsedMs} ms"); - } - - private async Task EstimateRttAsync(string hostName) - { - IPAddress addr; - if (!IPAddress.TryParse(hostName, out addr)) - { - addr = (await Dns.GetHostAddressesAsync(hostName)).FirstOrDefault(e => e.AddressFamily == AddressFamily.InterNetwork); - } - - Ping ping = new Ping(); - - // warmup: - await ping.SendPingAsync(addr); - - PingReply reply1 = await ping.SendPingAsync(addr); - PingReply reply2 = await ping.SendPingAsync(addr); - TimeSpan rtt = new TimeSpan(reply1.RoundtripTime + reply2.RoundtripTime) / 2; - _output.WriteLine($"Estimated RTT: {rtt.TotalMilliseconds} ms"); - return rtt; - } - - - static HttpRequestMessage GenerateRequestMessage(string hostName, bool http2, double lengthMb = 5) - { - string url = $"http://{hostName}:{(http2 ? "5001" : "5000")}?lengthMb={lengthMb}"; - var msg = new HttpRequestMessage(HttpMethod.Get, url) - { - Version = new Version(1, 1) - }; - - if (http2) - { - msg.Version = new Version(2, 0); - msg.VersionPolicy = HttpVersionPolicy.RequestVersionExact; - } - - return msg; - } - } - public sealed class HttpClientTest : HttpClientHandlerTestBase { public HttpClientTest(ITestOutputHelper output) : base(output) { } @@ -1486,76 +1390,4 @@ public sealed class HttpClientSendTest_Sync : HttpClientTest.HttpClientSendTest public HttpClientSendTest_Sync(ITestOutputHelper output) : base(output) { } protected override bool TestAsync => false; } - - - public sealed class LogHttpEventListener : EventListener - { - private Channel _messagesChannel = Channel.CreateUnbounded(); - private Task _processMessages; - private CancellationTokenSource _stopProcessing; - private ITestOutputHelper _log; - - public LogHttpEventListener(ITestOutputHelper log) - { - _log = log; - _messagesChannel = Channel.CreateUnbounded(); - _processMessages = ProcessMessagesAsync(); - _stopProcessing = new CancellationTokenSource(); - } - - public bool Enabled { get; set; } - public Predicate Filter { get; set; } = _ => true; - - protected override void OnEventSourceCreated(EventSource eventSource) - { - if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http") - { - EnableEvents(eventSource, EventLevel.LogAlways); - } - } - - private async Task ProcessMessagesAsync() - { - await Task.Yield(); - - try - { - await foreach (string message in _messagesChannel.Reader.ReadAllAsync(_stopProcessing.Token)) - { - if (Filter(message)) _log.WriteLine(message); - } - } - catch (OperationCanceledException) - { - return; - } - } - - protected override async void OnEventWritten(EventWrittenEventArgs eventData) - { - if (!Enabled) return; - - var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] "); - for (int i = 0; i < eventData.Payload?.Count; i++) - { - if (i > 0) - { - sb.Append(", "); - } - sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]); - } - await _messagesChannel.Writer.WriteAsync(sb.ToString()); - } - - public override void Dispose() - { - base.Dispose(); - - if (!_processMessages.Wait(TimeSpan.FromSeconds(10))) - { - _stopProcessing.Cancel(); - _processMessages.Wait(); - } - } - } } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj index 88d979aff2cfe0..44fe8b28336870 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj @@ -1,4 +1,4 @@ - + ../../src/Resources/Strings.resx $(DefineConstants);TargetsWindows @@ -9,6 +9,7 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Linux;$(NetCoreAppCurrent)-Browser;$(NetCoreAppCurrent)-OSX + _listener?.Dispose(); + + private const double LengthMb = 5; + + [Theory] + [InlineData("10.194.114.94")] + public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1", hostName, false, LengthMb); + + [Theory] + [InlineData("10.194.114.94")] + public Task Download20(string hostName) => TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb); + + [Theory] + [InlineData("10.194.114.94", 256)] + [InlineData("10.194.114.94", 2 * 1024)] + public Task Download20_LargeWindow(string hostName, int initialWindowKbytes) + { + SocketsHttpHandler handler = new SocketsHttpHandler() + { + InitialStreamWindowSize = initialWindowKbytes * 1024 + }; + return TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb, handler); + } + + + [Theory] + [InlineData("10.194.114.94")] + public async Task Download20_Dynamic(string hostName) + { + _listener.Enabled = true; + _listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync"); + + SocketsHttpHandler handler = new SocketsHttpHandler() + { + FakeRtt = await EstimateRttAsync(hostName) + }; + await TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb, handler); + } + + private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, HttpMessageHandler handler = null) + { + handler ??= new SocketsHttpHandler(); + using var client = new HttpClient(handler, true); + var message = GenerateRequestMessage(hostName, http2, lengthMb); + _output.WriteLine($"{info} / {lengthMb} MB from {hostName}"); + Stopwatch sw = Stopwatch.StartNew(); + var response = await client.SendAsync(message); + long elapsedMs = sw.ElapsedMilliseconds; + + _output.WriteLine($"{info}: {response.StatusCode} in {elapsedMs} ms"); + } + + private async Task EstimateRttAsync(string hostName) + { + IPAddress addr; + if (!IPAddress.TryParse(hostName, out addr)) + { + addr = (await Dns.GetHostAddressesAsync(hostName)).FirstOrDefault(e => e.AddressFamily == AddressFamily.InterNetwork); + } + + Ping ping = new Ping(); + + // warmup: + await ping.SendPingAsync(addr); + + PingReply reply1 = await ping.SendPingAsync(addr); + PingReply reply2 = await ping.SendPingAsync(addr); + TimeSpan rtt = new TimeSpan(reply1.RoundtripTime + reply2.RoundtripTime) / 2; + _output.WriteLine($"Estimated RTT: {rtt.TotalMilliseconds} ms"); + return rtt; + } + + + static HttpRequestMessage GenerateRequestMessage(string hostName, bool http2, double lengthMb = 5) + { + string url = $"http://{hostName}:{(http2 ? "5001" : "5000")}?lengthMb={lengthMb}"; + var msg = new HttpRequestMessage(HttpMethod.Get, url) + { + Version = new Version(1, 1) + }; + + if (http2) + { + msg.Version = new Version(2, 0); + msg.VersionPolicy = HttpVersionPolicy.RequestVersionExact; + } + + return msg; + } + } + + public sealed class LogHttpEventListener : EventListener + { + private Channel _messagesChannel = Channel.CreateUnbounded(); + private Task _processMessages; + private CancellationTokenSource _stopProcessing; + private ITestOutputHelper _log; + + public LogHttpEventListener(ITestOutputHelper log) + { + _log = log; + _messagesChannel = Channel.CreateUnbounded(); + _processMessages = ProcessMessagesAsync(); + _stopProcessing = new CancellationTokenSource(); + } + + public bool Enabled { get; set; } + public Predicate Filter { get; set; } = _ => true; + + protected override void OnEventSourceCreated(EventSource eventSource) + { + if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http") + { + EnableEvents(eventSource, EventLevel.LogAlways); + } + } + + private async Task ProcessMessagesAsync() + { + await Task.Yield(); + + try + { + await foreach (string message in _messagesChannel.Reader.ReadAllAsync(_stopProcessing.Token)) + { + if (Filter(message)) _log.WriteLine(message); + } + } + catch (OperationCanceledException) + { + return; + } + } + + protected override async void OnEventWritten(EventWrittenEventArgs eventData) + { + if (!Enabled) return; + + var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] "); + for (int i = 0; i < eventData.Payload?.Count; i++) + { + if (i > 0) + { + sb.Append(", "); + } + sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]); + } + await _messagesChannel.Writer.WriteAsync(sb.ToString()); + } + + public override void Dispose() + { + base.Dispose(); + + if (!_processMessages.Wait(TimeSpan.FromSeconds(10))) + { + _stopProcessing.Cancel(); + _processMessages.Wait(); + } + } + } +} From acc64c5f0ede418e414c84458f48db6c5ba70b78 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 13 May 2021 16:44:01 +0200 Subject: [PATCH 019/101] Winsock RTT estimation --- .../WinSock/Interop.WSAIoctl.TcpInfo.cs | 55 +++++++++++++++++++ .../src/System.Net.Http.csproj | 2 + .../SocketsHttpHandler/Http2Connection.cs | 1 + .../Http2StreamWindowManager.cs | 21 ++++++- 4 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Windows/WinSock/Interop.WSAIoctl.TcpInfo.cs diff --git a/src/libraries/Common/src/Interop/Windows/WinSock/Interop.WSAIoctl.TcpInfo.cs b/src/libraries/Common/src/Interop/Windows/WinSock/Interop.WSAIoctl.TcpInfo.cs new file mode 100644 index 00000000000000..a921a4f0ce204e --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/WinSock/Interop.WSAIoctl.TcpInfo.cs @@ -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; + } + } +} diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index dabc27a7a8a226..e275bd01afbcbf 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -412,6 +412,8 @@ Link="Common\Interop\Windows\WinHttp\Interop.winhttp_types.cs" /> + 0) { + _rttEstimator?.UpdateEstimation(); ExtendWindow(frameData.Length); } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 0417604eae4530..9a138ece93dc3b 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -116,6 +116,7 @@ public override void AdjustWindow(int bytesConsumed) private class RttEstimator { + private readonly TimeSpan _initialRtt; private Http2Connection _connection; private readonly Socket? _socket; public TimeSpan Rtt { get; private set; } @@ -123,15 +124,31 @@ public RttEstimator(Http2Connection connection, TimeSpan? fakeRtt, Socket? socke { _connection = connection; if (fakeRtt.HasValue) + { Rtt = fakeRtt.Value; + _initialRtt = Rtt; + } _socket = socket; - UpdateRttFromSocket(); + UpdateEstimation(); } - private void UpdateRttFromSocket() +#if WINDOWS + internal void UpdateEstimation() { if (_socket == null) return; + + if (Interop.Winsock.GetTcpInfoV0(_socket.SafeHandle, out Interop.Winsock._TCP_INFO_v0 tcpInfo) == SocketError.Success) + { + Rtt = TimeSpan.FromTicks(10 * tcpInfo.RttUs); + _connection.Trace($"Rtt estimation updated: Rtt={Rtt} || (initial fake:{_initialRtt} difference:{Rtt - _initialRtt}"); + } + } + +#else + internal void UpdateEstimation() + { } +#endif } } } From 1c655c30fe32037ba20579b41dfb23d1d70c340e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 13 May 2021 17:26:13 +0200 Subject: [PATCH 020/101] timeout --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index f385795e5a7420..36256935e15558 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -55,7 +55,7 @@ public Task Download20_LargeWindow(string hostName, int initialWindowKbytes) public async Task Download20_Dynamic(string hostName) { _listener.Enabled = true; - _listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync"); + _listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync") || m.Contains("Rtt estimation updated"); SocketsHttpHandler handler = new SocketsHttpHandler() { @@ -178,11 +178,12 @@ protected override async void OnEventWritten(EventWrittenEventArgs eventData) public override void Dispose() { base.Dispose(); + var timeout = TimeSpan.FromSeconds(2); - if (!_processMessages.Wait(TimeSpan.FromSeconds(10))) + if (!_processMessages.Wait(timeout)) { _stopProcessing.Cancel(); - _processMessages.Wait(); + _processMessages.Wait(timeout); } } } From 7d46cdb9b062d9ca2e6bfdc2b82d1cc1368f136a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 17 May 2021 16:08:57 +0200 Subject: [PATCH 021/101] benchmarks --- .../FunctionalTests/_LargeFileBenchmark.cs | 64 +++++++++++++++---- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 36256935e15558..4307936e821819 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -14,6 +14,10 @@ namespace System.Net.Http.Functional.Tests { + [CollectionDefinition("NoParallelTests", DisableParallelization = true)] + public class LargeFileBenchmark_ShouldNotBeParallell { } + + [Collection(nameof(LargeFileBenchmark_ShouldNotBeParallell))] public class LargeFileBenchmark : IDisposable { private readonly ITestOutputHelper _output; @@ -27,26 +31,63 @@ public LargeFileBenchmark(ITestOutputHelper output) public void Dispose() => _listener?.Dispose(); - private const double LengthMb = 5; + private const double LengthMb = 50; + private const string BenchmarkServer = "10.194.114.94"; [Theory] - [InlineData("10.194.114.94")] - public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1", hostName, false, LengthMb); + [InlineData(BenchmarkServer)] + public Task Download11_Run1(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run1", hostName, false, LengthMb); [Theory] - [InlineData("10.194.114.94")] - public Task Download20(string hostName) => TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb); + [InlineData(BenchmarkServer)] + public Task Download11_Run2(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run2", hostName, false, LengthMb); + + // [Theory] + // [InlineData("10.194.114.94")] + // public Task Download20_DefaultWindow(string hostName) => TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb); + + [Theory] + [InlineData(BenchmarkServer, 64)] + public Task Download20_SpecificWindow_Run0(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + + [Theory] + [InlineData(BenchmarkServer, 256)] + [InlineData(BenchmarkServer, 384)] + public Task Download20_SpecificWindow_Run1(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + + [Theory] + [InlineData(BenchmarkServer, 512)] + [InlineData(BenchmarkServer, 768)] + public Task Download20_SpecificWindow_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + + [Theory] + [InlineData(BenchmarkServer, 1024)] + [InlineData(BenchmarkServer, 1280)] + [InlineData(BenchmarkServer, 1536)] + public Task Download20_SpecificWindow_Run3(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); [Theory] - [InlineData("10.194.114.94", 256)] - [InlineData("10.194.114.94", 2 * 1024)] - public Task Download20_LargeWindow(string hostName, int initialWindowKbytes) + [InlineData(BenchmarkServer, 1792)] + [InlineData(BenchmarkServer, 2048)] + [InlineData(BenchmarkServer, 2560)] + [InlineData(BenchmarkServer, 3072)] + [InlineData(BenchmarkServer, 4096)] + [InlineData(BenchmarkServer, 5120)] + [InlineData(BenchmarkServer, 6144)] + [InlineData(BenchmarkServer, 8192)] + [InlineData(BenchmarkServer, 10240)] + [InlineData(BenchmarkServer, 12288)] + [InlineData(BenchmarkServer, 14336)] + [InlineData(BenchmarkServer, 16384)] + public Task Download20_SpecificWindow_Run4(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + + private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) { SocketsHttpHandler handler = new SocketsHttpHandler() { InitialStreamWindowSize = initialWindowKbytes * 1024 }; - return TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb, handler); + return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler); } @@ -54,8 +95,8 @@ public Task Download20_LargeWindow(string hostName, int initialWindowKbytes) [InlineData("10.194.114.94")] public async Task Download20_Dynamic(string hostName) { - _listener.Enabled = true; - _listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync") || m.Contains("Rtt estimation updated"); + //_listener.Enabled = true; + //_listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync"); SocketsHttpHandler handler = new SocketsHttpHandler() { @@ -68,6 +109,7 @@ private async Task TestHandler(string info, string hostName, bool http2, double { handler ??= new SocketsHttpHandler(); using var client = new HttpClient(handler, true); + client.Timeout = TimeSpan.FromMinutes(2); var message = GenerateRequestMessage(hostName, http2, lengthMb); _output.WriteLine($"{info} / {lengthMb} MB from {hostName}"); Stopwatch sw = Stopwatch.StartNew(); From 0e4040b1589b24dfb52a82f9aa9113bf8c760e00 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 17 May 2021 18:10:40 +0200 Subject: [PATCH 022/101] confugrable StreamWindowUpdateRatio & StreamWindowMagicMultiplier --- .../System.Net.Http/ref/System.Net.Http.cs | 2 ++ .../BrowserHttpHandler/SocketsHttpHandler.cs | 12 +++++++++ .../Http2StreamWindowManager.cs | 23 ++++++++-------- .../HttpConnectionSettings.cs | 6 ++++- .../SocketsHttpHandler/SocketsHttpHandler.cs | 12 +++++++++ .../FunctionalTests/_LargeFileBenchmark.cs | 27 ++++++++++++++++--- 6 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Net.Http/ref/System.Net.Http.cs b/src/libraries/System.Net.Http/ref/System.Net.Http.cs index 8f2633a6068be9..699bf7e4f94043 100644 --- a/src/libraries/System.Net.Http/ref/System.Net.Http.cs +++ b/src/libraries/System.Net.Http/ref/System.Net.Http.cs @@ -355,6 +355,8 @@ 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 { } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs index 45583d12f55357..24f88afa546318 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs @@ -36,6 +36,18 @@ public int InitialStreamWindowSize 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 { diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 9a138ece93dc3b..4d1b2957074760 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -9,14 +9,12 @@ namespace System.Net.Http { internal sealed partial class Http2Connection { - private const int StreamWindowUpdateRatio = 8; private int InitialStreamWindowSize { get; } = 65535; private class Http2StreamWindowManager { // See comment on ConnectionWindowThreshold. - internal int StreamWindowThreshold => StreamWindowSize / StreamWindowUpdateRatio; - + protected int _streamWindowUpdateRatio; protected int _delivered; protected int _streamWindowSize; @@ -28,16 +26,15 @@ public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) _connection = connection; _stream = stream; - InitialStreamWindowSize = connection.InitialStreamWindowSize; - _streamWindowSize = InitialStreamWindowSize; - - _stream.Trace($"StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); + _streamWindowSize = connection.InitialStreamWindowSize; + _streamWindowUpdateRatio = _connection._pool.Settings._streamWindowUpdateRatio; + _stream.Trace($"StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}, streamWindowUpdateRatio: {_streamWindowUpdateRatio}"); } - internal int InitialStreamWindowSize { get; } - internal int StreamWindowSize => _streamWindowSize; + internal int StreamWindowThreshold => _streamWindowSize / _streamWindowUpdateRatio; + public virtual void AdjustWindow(int bytesConsumed) { Debug.Assert(bytesConsumed > 0); @@ -68,11 +65,13 @@ private class DynamicHttp2StreamWindowManager : Http2StreamWindowManager { private DateTime _lastWindowUpdate = DateTime.Now; - private const long Magic = 1; + private long _magic = 1; public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream stream) : base(connection, stream) { + _magic = connection._pool.Settings._streamWindowMagicMultiplier; + _stream.Trace($"magic:{_magic}"); } public override void AdjustWindow(int bytesConsumed) @@ -88,7 +87,7 @@ public override void AdjustWindow(int bytesConsumed) int windowSizeIncrement = _delivered; - if (Magic * _delivered * rtt.Ticks > StreamWindowThreshold * dt.Ticks) + if (_magic * _delivered * rtt.Ticks > StreamWindowThreshold * dt.Ticks) { int newWindowSize = _streamWindowSize * 2; windowSizeIncrement += newWindowSize - _streamWindowSize; @@ -102,7 +101,7 @@ public override void AdjustWindow(int bytesConsumed) string msg = $"No adjustment! | RTT={rtt.TotalMilliseconds} ms || dt={dt.TotalMilliseconds} ms || " + //$"_delivered * rtt.Ticks = {_delivered * rtt.Ticks} || StreamWindowThreshold * dt.Ticks = {StreamWindowThreshold * dt.Ticks} ||" + - $"Magic*_delivered/dt = {Magic* _delivered / dt.TotalSeconds} bytes/sec || StreamWindowThreshold/RTT = {StreamWindowThreshold / rtt.TotalSeconds} bytes/sec"; + $"Magic*_delivered/dt = {_magic* _delivered / dt.TotalSeconds} bytes/sec || StreamWindowThreshold/RTT = {StreamWindowThreshold / rtt.TotalSeconds} bytes/sec"; _stream.Trace(msg); } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index 1595741659af51..161ca28b9c030d 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -70,6 +70,8 @@ internal sealed class HttpConnectionSettings internal TimeSpan? _fakeRtt; internal int _initialStreamWindowSize = 65535; + internal int _streamWindowUpdateRatio = 8; + internal int _streamWindowMagicMultiplier = 1; public HttpConnectionSettings() { @@ -125,7 +127,9 @@ public HttpConnectionSettings CloneAndNormalize() _connectCallback = _connectCallback, _plaintextStreamFilter = _plaintextStreamFilter, _fakeRtt = _fakeRtt, - _initialStreamWindowSize = _initialStreamWindowSize + _initialStreamWindowSize = _initialStreamWindowSize, + _streamWindowUpdateRatio = _streamWindowUpdateRatio, + _streamWindowMagicMultiplier = _streamWindowMagicMultiplier }; // TODO: Replace with Platform-Guard Assertion Annotations once https://github.com/dotnet/runtime/issues/44922 is finished diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index 4162ac134f3781..3b2a4595824db5 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -66,6 +66,18 @@ public int InitialStreamWindowSize set => _settings._initialStreamWindowSize = value; } + public int StreamWindowUpdateRatio + { + get => _settings._streamWindowUpdateRatio; + set => _settings._streamWindowUpdateRatio = value; + } + + public int StreamWindowMagicMultiplier + { + get => _settings._streamWindowMagicMultiplier; + set => _settings._streamWindowMagicMultiplier = value; + } + [AllowNull] public CookieContainer CookieContainer { diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 4307936e821819..02e1ae7ac77754 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -92,10 +92,10 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) [Theory] - [InlineData("10.194.114.94")] - public async Task Download20_Dynamic(string hostName) + [InlineData(BenchmarkServer)] + public async Task Download20_Dynamic_Default(string hostName) { - //_listener.Enabled = true; + _listener.Enabled = true; //_listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync"); SocketsHttpHandler handler = new SocketsHttpHandler() @@ -105,6 +105,27 @@ public async Task Download20_Dynamic(string hostName) await TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb, handler); } + [Theory] + [InlineData(BenchmarkServer, 8, 1)] + [InlineData(BenchmarkServer, 4, 1)] + [InlineData(BenchmarkServer, 2, 1)] + [InlineData(BenchmarkServer, 8, 10)] + [InlineData(BenchmarkServer, 4, 10)] + [InlineData(BenchmarkServer, 2, 10)] + public async Task Download20_Dynamic_Custom(string hostName, int ratio, int magic) + { + _listener.Enabled = true; + //_listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync"); + + SocketsHttpHandler handler = new SocketsHttpHandler() + { + FakeRtt = await EstimateRttAsync(hostName), + StreamWindowUpdateRatio = ratio, + StreamWindowMagicMultiplier = magic + }; + await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic | ratio={ratio} | magic={magic}", hostName, true, LengthMb, handler); + } + private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, HttpMessageHandler handler = null) { handler ??= new SocketsHttpHandler(); From e2d002f081c9859d7e2949873b27cd1baeb7376b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 17 May 2021 20:04:28 +0200 Subject: [PATCH 023/101] separate update ratios --- .../System.Net.Http/ref/System.Net.Http.cs | 3 +- .../BrowserHttpHandler/SocketsHttpHandler.cs | 8 ++- .../Http2StreamWindowManager.cs | 63 +++++++++++-------- .../HttpConnectionSettings.cs | 6 +- .../SocketsHttpHandler/SocketsHttpHandler.cs | 12 +++- .../FunctionalTests/_LargeFileBenchmark.cs | 18 +++--- 6 files changed, 67 insertions(+), 43 deletions(-) diff --git a/src/libraries/System.Net.Http/ref/System.Net.Http.cs b/src/libraries/System.Net.Http/ref/System.Net.Http.cs index 699bf7e4f94043..767638630e6b3f 100644 --- a/src/libraries/System.Net.Http/ref/System.Net.Http.cs +++ b/src/libraries/System.Net.Http/ref/System.Net.Http.cs @@ -355,7 +355,8 @@ 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 StreamWindowUpdateSendRatio { get { throw null; } set { } } + public int StreamWindowExtensionRatio { 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 { } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs index 24f88afa546318..dcdc42ac3658d0 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs @@ -36,7 +36,13 @@ public int InitialStreamWindowSize set => throw new PlatformNotSupportedException(); } - public int StreamWindowUpdateRatio + public int StreamWindowUpdateSendRatio + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + + public int StreamWindowExtensionRatio { get => throw new PlatformNotSupportedException(); set => throw new PlatformNotSupportedException(); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 4d1b2957074760..466841e3fc1c9f 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -14,7 +14,7 @@ internal sealed partial class Http2Connection private class Http2StreamWindowManager { // See comment on ConnectionWindowThreshold. - protected int _streamWindowUpdateRatio; + protected int _streamWindowUpdateSendRatio; protected int _delivered; protected int _streamWindowSize; @@ -27,18 +27,18 @@ public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) _stream = stream; _streamWindowSize = connection.InitialStreamWindowSize; - _streamWindowUpdateRatio = _connection._pool.Settings._streamWindowUpdateRatio; - _stream.Trace($"StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}, streamWindowUpdateRatio: {_streamWindowUpdateRatio}"); + _streamWindowUpdateSendRatio = _connection._pool.Settings._streamWindowUpdateSendRatio; + _stream.Trace($"StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowUpdateSendThreshold}, streamWindowUpdateRatio: {_streamWindowUpdateSendRatio}"); } internal int StreamWindowSize => _streamWindowSize; - internal int StreamWindowThreshold => _streamWindowSize / _streamWindowUpdateRatio; + internal int StreamWindowUpdateSendThreshold => _streamWindowSize / _streamWindowUpdateSendRatio; public virtual void AdjustWindow(int bytesConsumed) { Debug.Assert(bytesConsumed > 0); - Debug.Assert(_delivered < StreamWindowThreshold); + Debug.Assert(_delivered < StreamWindowUpdateSendThreshold); if (!_stream.ExpectResponseData) { @@ -48,7 +48,7 @@ public virtual void AdjustWindow(int bytesConsumed) } _delivered += bytesConsumed; - if (_delivered < StreamWindowThreshold) + if (_delivered < StreamWindowUpdateSendThreshold) { return; } @@ -63,53 +63,64 @@ public virtual void AdjustWindow(int bytesConsumed) private class DynamicHttp2StreamWindowManager : Http2StreamWindowManager { - private DateTime _lastWindowUpdate = DateTime.Now; + private DateTime _lastWindowExtensionCheck = DateTime.Now; private long _magic = 1; + private int _streamWindowExtensionRatio; + private int _pendingWindowExtension; + + private int StreamWindowExtensionThreshold => _streamWindowSize / _streamWindowExtensionRatio; public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream stream) : base(connection, stream) { + _streamWindowExtensionRatio = connection._pool.Settings._streamWindowExtensionRatio; _magic = connection._pool.Settings._streamWindowMagicMultiplier; - _stream.Trace($"magic:{_magic}"); + _stream.Trace($"_streamWindowExtensionRatio:{_streamWindowExtensionRatio}, StreamWindowExtensionThreshold:{StreamWindowExtensionThreshold}, magic:{_magic}"); } public override void AdjustWindow(int bytesConsumed) { _delivered += bytesConsumed; - if (_delivered < StreamWindowThreshold) + _pendingWindowExtension += bytesConsumed; + if (_delivered < StreamWindowUpdateSendThreshold) { return; } TimeSpan rtt = _connection._rttEstimator!.Rtt; - TimeSpan dt = DateTime.Now - _lastWindowUpdate; + TimeSpan dt = DateTime.Now - _lastWindowExtensionCheck; int windowSizeIncrement = _delivered; - if (_magic * _delivered * rtt.Ticks > StreamWindowThreshold * dt.Ticks) - { - int newWindowSize = _streamWindowSize * 2; - windowSizeIncrement += newWindowSize - _streamWindowSize; - - _streamWindowSize = newWindowSize; - - _stream.Trace($"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); - } - else + if (_pendingWindowExtension >= StreamWindowExtensionThreshold) { - string msg = - $"No adjustment! | RTT={rtt.TotalMilliseconds} ms || dt={dt.TotalMilliseconds} ms || " + - //$"_delivered * rtt.Ticks = {_delivered * rtt.Ticks} || StreamWindowThreshold * dt.Ticks = {StreamWindowThreshold * dt.Ticks} ||" + - $"Magic*_delivered/dt = {_magic* _delivered / dt.TotalSeconds} bytes/sec || StreamWindowThreshold/RTT = {StreamWindowThreshold / rtt.TotalSeconds} bytes/sec"; - _stream.Trace(msg); + if (_magic * _pendingWindowExtension * rtt.Ticks > StreamWindowExtensionThreshold * dt.Ticks) + { + int newWindowSize = _streamWindowSize * 2; + windowSizeIncrement += newWindowSize - _streamWindowSize; + + _streamWindowSize = newWindowSize; + + _stream.Trace($"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowExtensionThreshold: {StreamWindowExtensionThreshold}"); + } + else + { + string msg = + $"No adjustment! | RTT={rtt.TotalMilliseconds} ms || dt={dt.TotalMilliseconds} ms || " + + //$"_delivered * rtt.Ticks = {_delivered * rtt.Ticks} || StreamWindowThreshold * dt.Ticks = {StreamWindowThreshold * dt.Ticks} ||" + + $"Magic*_pendingWindowExtension/dt = {_magic * _pendingWindowExtension / dt.TotalSeconds} bytes/sec || StreamWindowExtensionThreshold/RTT = {StreamWindowExtensionThreshold / rtt.TotalSeconds} bytes/sec"; + _stream.Trace(msg); + } + + _pendingWindowExtension = 0; + _lastWindowExtensionCheck = DateTime.Now; } Task sendWindowUpdateTask = _connection.SendWindowUpdateAsync(_stream.StreamId, windowSizeIncrement); _connection.LogExceptions(sendWindowUpdateTask); _delivered = 0; - _lastWindowUpdate = DateTime.Now; } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index 161ca28b9c030d..d373d97e1c1509 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -70,7 +70,8 @@ internal sealed class HttpConnectionSettings internal TimeSpan? _fakeRtt; internal int _initialStreamWindowSize = 65535; - internal int _streamWindowUpdateRatio = 8; + internal int _streamWindowUpdateSendRatio = 8; + internal int _streamWindowExtensionRatio = 8; internal int _streamWindowMagicMultiplier = 1; public HttpConnectionSettings() @@ -128,7 +129,8 @@ public HttpConnectionSettings CloneAndNormalize() _plaintextStreamFilter = _plaintextStreamFilter, _fakeRtt = _fakeRtt, _initialStreamWindowSize = _initialStreamWindowSize, - _streamWindowUpdateRatio = _streamWindowUpdateRatio, + _streamWindowUpdateSendRatio = _streamWindowUpdateSendRatio, + _streamWindowExtensionRatio = _streamWindowExtensionRatio, _streamWindowMagicMultiplier = _streamWindowMagicMultiplier }; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index 3b2a4595824db5..ed557ba45541dc 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -66,10 +66,16 @@ public int InitialStreamWindowSize set => _settings._initialStreamWindowSize = value; } - public int StreamWindowUpdateRatio + public int StreamWindowUpdateSendRatio { - get => _settings._streamWindowUpdateRatio; - set => _settings._streamWindowUpdateRatio = value; + get => _settings._streamWindowUpdateSendRatio; + set => _settings._streamWindowUpdateSendRatio = value; + } + + public int StreamWindowExtensionRatio + { + get => _settings._streamWindowExtensionRatio; + set => _settings._streamWindowExtensionRatio = value; } public int StreamWindowMagicMultiplier diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 02e1ae7ac77754..2235e1f76929ab 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -31,7 +31,7 @@ public LargeFileBenchmark(ITestOutputHelper output) public void Dispose() => _listener?.Dispose(); - private const double LengthMb = 50; + private const double LengthMb = 1; private const string BenchmarkServer = "10.194.114.94"; [Theory] @@ -106,13 +106,10 @@ public async Task Download20_Dynamic_Default(string hostName) } [Theory] - [InlineData(BenchmarkServer, 8, 1)] - [InlineData(BenchmarkServer, 4, 1)] - [InlineData(BenchmarkServer, 2, 1)] - [InlineData(BenchmarkServer, 8, 10)] - [InlineData(BenchmarkServer, 4, 10)] - [InlineData(BenchmarkServer, 2, 10)] - public async Task Download20_Dynamic_Custom(string hostName, int ratio, int magic) + [InlineData(BenchmarkServer, 16, 4, 1)] + [InlineData(BenchmarkServer, 16, 8, 1)] + [InlineData(BenchmarkServer, 8, 4, 1)] + public async Task Download20_Dynamic_Custom(string hostName, int updateRatio, int extensionCheckRatio, int magic) { _listener.Enabled = true; //_listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync"); @@ -120,10 +117,11 @@ public async Task Download20_Dynamic_Custom(string hostName, int ratio, int magi SocketsHttpHandler handler = new SocketsHttpHandler() { FakeRtt = await EstimateRttAsync(hostName), - StreamWindowUpdateRatio = ratio, + StreamWindowUpdateSendRatio = updateRatio, + StreamWindowExtensionRatio = extensionCheckRatio, StreamWindowMagicMultiplier = magic }; - await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic | ratio={ratio} | magic={magic}", hostName, true, LengthMb, handler); + await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic | ratio={updateRatio} | magic={magic}", hostName, true, LengthMb, handler); } private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, HttpMessageHandler handler = null) From 1409ad564ea31498558eb28fb71797cf148a955d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 17 May 2021 21:21:01 +0200 Subject: [PATCH 024/101] Revert "separate update ratios" This reverts commit e2d002f081c9859d7e2949873b27cd1baeb7376b. --- .../System.Net.Http/ref/System.Net.Http.cs | 3 +- .../BrowserHttpHandler/SocketsHttpHandler.cs | 8 +-- .../Http2StreamWindowManager.cs | 63 ++++++++----------- .../HttpConnectionSettings.cs | 6 +- .../SocketsHttpHandler/SocketsHttpHandler.cs | 12 +--- .../FunctionalTests/_LargeFileBenchmark.cs | 18 +++--- 6 files changed, 43 insertions(+), 67 deletions(-) diff --git a/src/libraries/System.Net.Http/ref/System.Net.Http.cs b/src/libraries/System.Net.Http/ref/System.Net.Http.cs index 767638630e6b3f..699bf7e4f94043 100644 --- a/src/libraries/System.Net.Http/ref/System.Net.Http.cs +++ b/src/libraries/System.Net.Http/ref/System.Net.Http.cs @@ -355,8 +355,7 @@ 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 StreamWindowUpdateSendRatio { get { throw null; } set { } } - public int StreamWindowExtensionRatio { 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 { } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs index dcdc42ac3658d0..24f88afa546318 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs @@ -36,13 +36,7 @@ public int InitialStreamWindowSize set => throw new PlatformNotSupportedException(); } - public int StreamWindowUpdateSendRatio - { - get => throw new PlatformNotSupportedException(); - set => throw new PlatformNotSupportedException(); - } - - public int StreamWindowExtensionRatio + public int StreamWindowUpdateRatio { get => throw new PlatformNotSupportedException(); set => throw new PlatformNotSupportedException(); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 466841e3fc1c9f..4d1b2957074760 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -14,7 +14,7 @@ internal sealed partial class Http2Connection private class Http2StreamWindowManager { // See comment on ConnectionWindowThreshold. - protected int _streamWindowUpdateSendRatio; + protected int _streamWindowUpdateRatio; protected int _delivered; protected int _streamWindowSize; @@ -27,18 +27,18 @@ public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) _stream = stream; _streamWindowSize = connection.InitialStreamWindowSize; - _streamWindowUpdateSendRatio = _connection._pool.Settings._streamWindowUpdateSendRatio; - _stream.Trace($"StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowUpdateSendThreshold}, streamWindowUpdateRatio: {_streamWindowUpdateSendRatio}"); + _streamWindowUpdateRatio = _connection._pool.Settings._streamWindowUpdateRatio; + _stream.Trace($"StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}, streamWindowUpdateRatio: {_streamWindowUpdateRatio}"); } internal int StreamWindowSize => _streamWindowSize; - internal int StreamWindowUpdateSendThreshold => _streamWindowSize / _streamWindowUpdateSendRatio; + internal int StreamWindowThreshold => _streamWindowSize / _streamWindowUpdateRatio; public virtual void AdjustWindow(int bytesConsumed) { Debug.Assert(bytesConsumed > 0); - Debug.Assert(_delivered < StreamWindowUpdateSendThreshold); + Debug.Assert(_delivered < StreamWindowThreshold); if (!_stream.ExpectResponseData) { @@ -48,7 +48,7 @@ public virtual void AdjustWindow(int bytesConsumed) } _delivered += bytesConsumed; - if (_delivered < StreamWindowUpdateSendThreshold) + if (_delivered < StreamWindowThreshold) { return; } @@ -63,64 +63,53 @@ public virtual void AdjustWindow(int bytesConsumed) private class DynamicHttp2StreamWindowManager : Http2StreamWindowManager { - private DateTime _lastWindowExtensionCheck = DateTime.Now; + private DateTime _lastWindowUpdate = DateTime.Now; private long _magic = 1; - private int _streamWindowExtensionRatio; - private int _pendingWindowExtension; - - private int StreamWindowExtensionThreshold => _streamWindowSize / _streamWindowExtensionRatio; public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream stream) : base(connection, stream) { - _streamWindowExtensionRatio = connection._pool.Settings._streamWindowExtensionRatio; _magic = connection._pool.Settings._streamWindowMagicMultiplier; - _stream.Trace($"_streamWindowExtensionRatio:{_streamWindowExtensionRatio}, StreamWindowExtensionThreshold:{StreamWindowExtensionThreshold}, magic:{_magic}"); + _stream.Trace($"magic:{_magic}"); } public override void AdjustWindow(int bytesConsumed) { _delivered += bytesConsumed; - _pendingWindowExtension += bytesConsumed; - if (_delivered < StreamWindowUpdateSendThreshold) + if (_delivered < StreamWindowThreshold) { return; } TimeSpan rtt = _connection._rttEstimator!.Rtt; - TimeSpan dt = DateTime.Now - _lastWindowExtensionCheck; + TimeSpan dt = DateTime.Now - _lastWindowUpdate; int windowSizeIncrement = _delivered; - if (_pendingWindowExtension >= StreamWindowExtensionThreshold) + if (_magic * _delivered * rtt.Ticks > StreamWindowThreshold * dt.Ticks) + { + int newWindowSize = _streamWindowSize * 2; + windowSizeIncrement += newWindowSize - _streamWindowSize; + + _streamWindowSize = newWindowSize; + + _stream.Trace($"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); + } + else { - if (_magic * _pendingWindowExtension * rtt.Ticks > StreamWindowExtensionThreshold * dt.Ticks) - { - int newWindowSize = _streamWindowSize * 2; - windowSizeIncrement += newWindowSize - _streamWindowSize; - - _streamWindowSize = newWindowSize; - - _stream.Trace($"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowExtensionThreshold: {StreamWindowExtensionThreshold}"); - } - else - { - string msg = - $"No adjustment! | RTT={rtt.TotalMilliseconds} ms || dt={dt.TotalMilliseconds} ms || " + - //$"_delivered * rtt.Ticks = {_delivered * rtt.Ticks} || StreamWindowThreshold * dt.Ticks = {StreamWindowThreshold * dt.Ticks} ||" + - $"Magic*_pendingWindowExtension/dt = {_magic * _pendingWindowExtension / dt.TotalSeconds} bytes/sec || StreamWindowExtensionThreshold/RTT = {StreamWindowExtensionThreshold / rtt.TotalSeconds} bytes/sec"; - _stream.Trace(msg); - } - - _pendingWindowExtension = 0; - _lastWindowExtensionCheck = DateTime.Now; + string msg = + $"No adjustment! | RTT={rtt.TotalMilliseconds} ms || dt={dt.TotalMilliseconds} ms || " + + //$"_delivered * rtt.Ticks = {_delivered * rtt.Ticks} || StreamWindowThreshold * dt.Ticks = {StreamWindowThreshold * dt.Ticks} ||" + + $"Magic*_delivered/dt = {_magic* _delivered / dt.TotalSeconds} bytes/sec || StreamWindowThreshold/RTT = {StreamWindowThreshold / rtt.TotalSeconds} bytes/sec"; + _stream.Trace(msg); } Task sendWindowUpdateTask = _connection.SendWindowUpdateAsync(_stream.StreamId, windowSizeIncrement); _connection.LogExceptions(sendWindowUpdateTask); _delivered = 0; + _lastWindowUpdate = DateTime.Now; } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index d373d97e1c1509..161ca28b9c030d 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -70,8 +70,7 @@ internal sealed class HttpConnectionSettings internal TimeSpan? _fakeRtt; internal int _initialStreamWindowSize = 65535; - internal int _streamWindowUpdateSendRatio = 8; - internal int _streamWindowExtensionRatio = 8; + internal int _streamWindowUpdateRatio = 8; internal int _streamWindowMagicMultiplier = 1; public HttpConnectionSettings() @@ -129,8 +128,7 @@ public HttpConnectionSettings CloneAndNormalize() _plaintextStreamFilter = _plaintextStreamFilter, _fakeRtt = _fakeRtt, _initialStreamWindowSize = _initialStreamWindowSize, - _streamWindowUpdateSendRatio = _streamWindowUpdateSendRatio, - _streamWindowExtensionRatio = _streamWindowExtensionRatio, + _streamWindowUpdateRatio = _streamWindowUpdateRatio, _streamWindowMagicMultiplier = _streamWindowMagicMultiplier }; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index ed557ba45541dc..3b2a4595824db5 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -66,16 +66,10 @@ public int InitialStreamWindowSize set => _settings._initialStreamWindowSize = value; } - public int StreamWindowUpdateSendRatio + public int StreamWindowUpdateRatio { - get => _settings._streamWindowUpdateSendRatio; - set => _settings._streamWindowUpdateSendRatio = value; - } - - public int StreamWindowExtensionRatio - { - get => _settings._streamWindowExtensionRatio; - set => _settings._streamWindowExtensionRatio = value; + get => _settings._streamWindowUpdateRatio; + set => _settings._streamWindowUpdateRatio = value; } public int StreamWindowMagicMultiplier diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 2235e1f76929ab..02e1ae7ac77754 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -31,7 +31,7 @@ public LargeFileBenchmark(ITestOutputHelper output) public void Dispose() => _listener?.Dispose(); - private const double LengthMb = 1; + private const double LengthMb = 50; private const string BenchmarkServer = "10.194.114.94"; [Theory] @@ -106,10 +106,13 @@ public async Task Download20_Dynamic_Default(string hostName) } [Theory] - [InlineData(BenchmarkServer, 16, 4, 1)] - [InlineData(BenchmarkServer, 16, 8, 1)] - [InlineData(BenchmarkServer, 8, 4, 1)] - public async Task Download20_Dynamic_Custom(string hostName, int updateRatio, int extensionCheckRatio, int magic) + [InlineData(BenchmarkServer, 8, 1)] + [InlineData(BenchmarkServer, 4, 1)] + [InlineData(BenchmarkServer, 2, 1)] + [InlineData(BenchmarkServer, 8, 10)] + [InlineData(BenchmarkServer, 4, 10)] + [InlineData(BenchmarkServer, 2, 10)] + public async Task Download20_Dynamic_Custom(string hostName, int ratio, int magic) { _listener.Enabled = true; //_listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync"); @@ -117,11 +120,10 @@ public async Task Download20_Dynamic_Custom(string hostName, int updateRatio, in SocketsHttpHandler handler = new SocketsHttpHandler() { FakeRtt = await EstimateRttAsync(hostName), - StreamWindowUpdateSendRatio = updateRatio, - StreamWindowExtensionRatio = extensionCheckRatio, + StreamWindowUpdateRatio = ratio, StreamWindowMagicMultiplier = magic }; - await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic | ratio={updateRatio} | magic={magic}", hostName, true, LengthMb, handler); + await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic | ratio={ratio} | magic={magic}", hostName, true, LengthMb, handler); } private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, HttpMessageHandler handler = null) From da86afa5516a826f483393f0b48e9f939ac4d953 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 17 May 2021 21:23:25 +0200 Subject: [PATCH 025/101] comment out _LargeFileBenchmark --- .../FunctionalTests/_LargeFileBenchmark.cs | 506 +++++++++--------- 1 file changed, 253 insertions(+), 253 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 02e1ae7ac77754..be67c9cf1ec753 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -1,253 +1,253 @@ -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Diagnostics.Tracing; -using System.Linq; -using System.Net.NetworkInformation; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; -using Xunit; -using Xunit.Abstractions; - -namespace System.Net.Http.Functional.Tests -{ - [CollectionDefinition("NoParallelTests", DisableParallelization = true)] - public class LargeFileBenchmark_ShouldNotBeParallell { } - - [Collection(nameof(LargeFileBenchmark_ShouldNotBeParallell))] - public class LargeFileBenchmark : IDisposable - { - private readonly ITestOutputHelper _output; - private LogHttpEventListener _listener; - - public LargeFileBenchmark(ITestOutputHelper output) - { - _output = output; - _listener = new LogHttpEventListener(output); - } - - public void Dispose() => _listener?.Dispose(); - - private const double LengthMb = 50; - private const string BenchmarkServer = "10.194.114.94"; - - [Theory] - [InlineData(BenchmarkServer)] - public Task Download11_Run1(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run1", hostName, false, LengthMb); - - [Theory] - [InlineData(BenchmarkServer)] - public Task Download11_Run2(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run2", hostName, false, LengthMb); - - // [Theory] - // [InlineData("10.194.114.94")] - // public Task Download20_DefaultWindow(string hostName) => TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb); - - [Theory] - [InlineData(BenchmarkServer, 64)] - public Task Download20_SpecificWindow_Run0(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - [Theory] - [InlineData(BenchmarkServer, 256)] - [InlineData(BenchmarkServer, 384)] - public Task Download20_SpecificWindow_Run1(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - [Theory] - [InlineData(BenchmarkServer, 512)] - [InlineData(BenchmarkServer, 768)] - public Task Download20_SpecificWindow_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - [Theory] - [InlineData(BenchmarkServer, 1024)] - [InlineData(BenchmarkServer, 1280)] - [InlineData(BenchmarkServer, 1536)] - public Task Download20_SpecificWindow_Run3(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - [Theory] - [InlineData(BenchmarkServer, 1792)] - [InlineData(BenchmarkServer, 2048)] - [InlineData(BenchmarkServer, 2560)] - [InlineData(BenchmarkServer, 3072)] - [InlineData(BenchmarkServer, 4096)] - [InlineData(BenchmarkServer, 5120)] - [InlineData(BenchmarkServer, 6144)] - [InlineData(BenchmarkServer, 8192)] - [InlineData(BenchmarkServer, 10240)] - [InlineData(BenchmarkServer, 12288)] - [InlineData(BenchmarkServer, 14336)] - [InlineData(BenchmarkServer, 16384)] - public Task Download20_SpecificWindow_Run4(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) - { - SocketsHttpHandler handler = new SocketsHttpHandler() - { - InitialStreamWindowSize = initialWindowKbytes * 1024 - }; - return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler); - } - - - [Theory] - [InlineData(BenchmarkServer)] - public async Task Download20_Dynamic_Default(string hostName) - { - _listener.Enabled = true; - //_listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync"); - - SocketsHttpHandler handler = new SocketsHttpHandler() - { - FakeRtt = await EstimateRttAsync(hostName) - }; - await TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb, handler); - } - - [Theory] - [InlineData(BenchmarkServer, 8, 1)] - [InlineData(BenchmarkServer, 4, 1)] - [InlineData(BenchmarkServer, 2, 1)] - [InlineData(BenchmarkServer, 8, 10)] - [InlineData(BenchmarkServer, 4, 10)] - [InlineData(BenchmarkServer, 2, 10)] - public async Task Download20_Dynamic_Custom(string hostName, int ratio, int magic) - { - _listener.Enabled = true; - //_listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync"); - - SocketsHttpHandler handler = new SocketsHttpHandler() - { - FakeRtt = await EstimateRttAsync(hostName), - StreamWindowUpdateRatio = ratio, - StreamWindowMagicMultiplier = magic - }; - await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic | ratio={ratio} | magic={magic}", hostName, true, LengthMb, handler); - } - - private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, HttpMessageHandler handler = null) - { - handler ??= new SocketsHttpHandler(); - using var client = new HttpClient(handler, true); - client.Timeout = TimeSpan.FromMinutes(2); - var message = GenerateRequestMessage(hostName, http2, lengthMb); - _output.WriteLine($"{info} / {lengthMb} MB from {hostName}"); - Stopwatch sw = Stopwatch.StartNew(); - var response = await client.SendAsync(message); - long elapsedMs = sw.ElapsedMilliseconds; - - _output.WriteLine($"{info}: {response.StatusCode} in {elapsedMs} ms"); - } - - private async Task EstimateRttAsync(string hostName) - { - IPAddress addr; - if (!IPAddress.TryParse(hostName, out addr)) - { - addr = (await Dns.GetHostAddressesAsync(hostName)).FirstOrDefault(e => e.AddressFamily == AddressFamily.InterNetwork); - } - - Ping ping = new Ping(); - - // warmup: - await ping.SendPingAsync(addr); - - PingReply reply1 = await ping.SendPingAsync(addr); - PingReply reply2 = await ping.SendPingAsync(addr); - TimeSpan rtt = new TimeSpan(reply1.RoundtripTime + reply2.RoundtripTime) / 2; - _output.WriteLine($"Estimated RTT: {rtt.TotalMilliseconds} ms"); - return rtt; - } - - - static HttpRequestMessage GenerateRequestMessage(string hostName, bool http2, double lengthMb = 5) - { - string url = $"http://{hostName}:{(http2 ? "5001" : "5000")}?lengthMb={lengthMb}"; - var msg = new HttpRequestMessage(HttpMethod.Get, url) - { - Version = new Version(1, 1) - }; - - if (http2) - { - msg.Version = new Version(2, 0); - msg.VersionPolicy = HttpVersionPolicy.RequestVersionExact; - } - - return msg; - } - } - - public sealed class LogHttpEventListener : EventListener - { - private Channel _messagesChannel = Channel.CreateUnbounded(); - private Task _processMessages; - private CancellationTokenSource _stopProcessing; - private ITestOutputHelper _log; - - public LogHttpEventListener(ITestOutputHelper log) - { - _log = log; - _messagesChannel = Channel.CreateUnbounded(); - _processMessages = ProcessMessagesAsync(); - _stopProcessing = new CancellationTokenSource(); - } - - public bool Enabled { get; set; } - public Predicate Filter { get; set; } = _ => true; - - protected override void OnEventSourceCreated(EventSource eventSource) - { - if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http") - { - EnableEvents(eventSource, EventLevel.LogAlways); - } - } - - private async Task ProcessMessagesAsync() - { - await Task.Yield(); - - try - { - await foreach (string message in _messagesChannel.Reader.ReadAllAsync(_stopProcessing.Token)) - { - if (Filter(message)) _log.WriteLine(message); - } - } - catch (OperationCanceledException) - { - return; - } - } - - protected override async void OnEventWritten(EventWrittenEventArgs eventData) - { - if (!Enabled) return; - - var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] "); - for (int i = 0; i < eventData.Payload?.Count; i++) - { - if (i > 0) - { - sb.Append(", "); - } - sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]); - } - await _messagesChannel.Writer.WriteAsync(sb.ToString()); - } - - public override void Dispose() - { - base.Dispose(); - var timeout = TimeSpan.FromSeconds(2); - - if (!_processMessages.Wait(timeout)) - { - _stopProcessing.Cancel(); - _processMessages.Wait(timeout); - } - } - } -} +//// The .NET Foundation licenses this file to you under the MIT license. + +//using System.Diagnostics; +//using System.Diagnostics.Tracing; +//using System.Linq; +//using System.Net.NetworkInformation; +//using System.Net.Sockets; +//using System.Text; +//using System.Threading; +//using System.Threading.Channels; +//using System.Threading.Tasks; +//using Xunit; +//using Xunit.Abstractions; + +//namespace System.Net.Http.Functional.Tests +//{ +// [CollectionDefinition("NoParallelTests", DisableParallelization = true)] +// public class LargeFileBenchmark_ShouldNotBeParallell { } + +// [Collection(nameof(LargeFileBenchmark_ShouldNotBeParallell))] +// public class LargeFileBenchmark : IDisposable +// { +// private readonly ITestOutputHelper _output; +// private LogHttpEventListener _listener; + +// public LargeFileBenchmark(ITestOutputHelper output) +// { +// _output = output; +// _listener = new LogHttpEventListener(output); +// } + +// public void Dispose() => _listener?.Dispose(); + +// private const double LengthMb = 50; +// private const string BenchmarkServer = "10.194.114.94"; + +// [Theory] +// [InlineData(BenchmarkServer)] +// public Task Download11_Run1(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run1", hostName, false, LengthMb); + +// [Theory] +// [InlineData(BenchmarkServer)] +// public Task Download11_Run2(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run2", hostName, false, LengthMb); + +// // [Theory] +// // [InlineData("10.194.114.94")] +// // public Task Download20_DefaultWindow(string hostName) => TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb); + +// [Theory] +// [InlineData(BenchmarkServer, 64)] +// public Task Download20_SpecificWindow_Run0(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + +// [Theory] +// [InlineData(BenchmarkServer, 256)] +// [InlineData(BenchmarkServer, 384)] +// public Task Download20_SpecificWindow_Run1(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + +// [Theory] +// [InlineData(BenchmarkServer, 512)] +// [InlineData(BenchmarkServer, 768)] +// public Task Download20_SpecificWindow_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + +// [Theory] +// [InlineData(BenchmarkServer, 1024)] +// [InlineData(BenchmarkServer, 1280)] +// [InlineData(BenchmarkServer, 1536)] +// public Task Download20_SpecificWindow_Run3(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + +// [Theory] +// [InlineData(BenchmarkServer, 1792)] +// [InlineData(BenchmarkServer, 2048)] +// [InlineData(BenchmarkServer, 2560)] +// [InlineData(BenchmarkServer, 3072)] +// [InlineData(BenchmarkServer, 4096)] +// [InlineData(BenchmarkServer, 5120)] +// [InlineData(BenchmarkServer, 6144)] +// [InlineData(BenchmarkServer, 8192)] +// [InlineData(BenchmarkServer, 10240)] +// [InlineData(BenchmarkServer, 12288)] +// [InlineData(BenchmarkServer, 14336)] +// [InlineData(BenchmarkServer, 16384)] +// public Task Download20_SpecificWindow_Run4(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + +// private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) +// { +// SocketsHttpHandler handler = new SocketsHttpHandler() +// { +// InitialStreamWindowSize = initialWindowKbytes * 1024 +// }; +// return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler); +// } + + +// [Theory] +// [InlineData(BenchmarkServer)] +// public async Task Download20_Dynamic_Default(string hostName) +// { +// _listener.Enabled = true; +// //_listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync"); + +// SocketsHttpHandler handler = new SocketsHttpHandler() +// { +// FakeRtt = await EstimateRttAsync(hostName) +// }; +// await TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb, handler); +// } + +// [Theory] +// [InlineData(BenchmarkServer, 8, 1)] +// [InlineData(BenchmarkServer, 4, 1)] +// [InlineData(BenchmarkServer, 2, 1)] +// [InlineData(BenchmarkServer, 8, 10)] +// [InlineData(BenchmarkServer, 4, 10)] +// [InlineData(BenchmarkServer, 2, 10)] +// public async Task Download20_Dynamic_Custom(string hostName, int ratio, int magic) +// { +// _listener.Enabled = true; +// //_listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync"); + +// SocketsHttpHandler handler = new SocketsHttpHandler() +// { +// FakeRtt = await EstimateRttAsync(hostName), +// StreamWindowUpdateRatio = ratio, +// StreamWindowMagicMultiplier = magic +// }; +// await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic | ratio={ratio} | magic={magic}", hostName, true, LengthMb, handler); +// } + +// private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, HttpMessageHandler handler = null) +// { +// handler ??= new SocketsHttpHandler(); +// using var client = new HttpClient(handler, true); +// client.Timeout = TimeSpan.FromMinutes(2); +// var message = GenerateRequestMessage(hostName, http2, lengthMb); +// _output.WriteLine($"{info} / {lengthMb} MB from {hostName}"); +// Stopwatch sw = Stopwatch.StartNew(); +// var response = await client.SendAsync(message); +// long elapsedMs = sw.ElapsedMilliseconds; + +// _output.WriteLine($"{info}: {response.StatusCode} in {elapsedMs} ms"); +// } + +// private async Task EstimateRttAsync(string hostName) +// { +// IPAddress addr; +// if (!IPAddress.TryParse(hostName, out addr)) +// { +// addr = (await Dns.GetHostAddressesAsync(hostName)).FirstOrDefault(e => e.AddressFamily == AddressFamily.InterNetwork); +// } + +// Ping ping = new Ping(); + +// // warmup: +// await ping.SendPingAsync(addr); + +// PingReply reply1 = await ping.SendPingAsync(addr); +// PingReply reply2 = await ping.SendPingAsync(addr); +// TimeSpan rtt = new TimeSpan(reply1.RoundtripTime + reply2.RoundtripTime) / 2; +// _output.WriteLine($"Estimated RTT: {rtt.TotalMilliseconds} ms"); +// return rtt; +// } + + +// static HttpRequestMessage GenerateRequestMessage(string hostName, bool http2, double lengthMb = 5) +// { +// string url = $"http://{hostName}:{(http2 ? "5001" : "5000")}?lengthMb={lengthMb}"; +// var msg = new HttpRequestMessage(HttpMethod.Get, url) +// { +// Version = new Version(1, 1) +// }; + +// if (http2) +// { +// msg.Version = new Version(2, 0); +// msg.VersionPolicy = HttpVersionPolicy.RequestVersionExact; +// } + +// return msg; +// } +// } + +// public sealed class LogHttpEventListener : EventListener +// { +// private Channel _messagesChannel = Channel.CreateUnbounded(); +// private Task _processMessages; +// private CancellationTokenSource _stopProcessing; +// private ITestOutputHelper _log; + +// public LogHttpEventListener(ITestOutputHelper log) +// { +// _log = log; +// _messagesChannel = Channel.CreateUnbounded(); +// _processMessages = ProcessMessagesAsync(); +// _stopProcessing = new CancellationTokenSource(); +// } + +// public bool Enabled { get; set; } +// public Predicate Filter { get; set; } = _ => true; + +// protected override void OnEventSourceCreated(EventSource eventSource) +// { +// if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http") +// { +// EnableEvents(eventSource, EventLevel.LogAlways); +// } +// } + +// private async Task ProcessMessagesAsync() +// { +// await Task.Yield(); + +// try +// { +// await foreach (string message in _messagesChannel.Reader.ReadAllAsync(_stopProcessing.Token)) +// { +// if (Filter(message)) _log.WriteLine(message); +// } +// } +// catch (OperationCanceledException) +// { +// return; +// } +// } + +// protected override async void OnEventWritten(EventWrittenEventArgs eventData) +// { +// if (!Enabled) return; + +// var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] "); +// for (int i = 0; i < eventData.Payload?.Count; i++) +// { +// if (i > 0) +// { +// sb.Append(", "); +// } +// sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]); +// } +// await _messagesChannel.Writer.WriteAsync(sb.ToString()); +// } + +// public override void Dispose() +// { +// base.Dispose(); +// var timeout = TimeSpan.FromSeconds(2); + +// if (!_processMessages.Wait(timeout)) +// { +// _stopProcessing.Cancel(); +// _processMessages.Wait(timeout); +// } +// } +// } +//} From 8739547b82df47e77f1766002ff94fb69ba1bf55 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 17 May 2021 21:55:23 +0200 Subject: [PATCH 026/101] remove some junk code --- .../Http/SocketsHttpHandler/Http2Connection.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 8d6872eaa9d0f4..2580efbbc15d30 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -189,7 +189,6 @@ public async ValueTask SetupAsync() _outgoingBuffer.Commit(s_http2ConnectionPreface.Length); // Send SETTINGS frame. Disable push promise & set initial window size. -#if true FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, 2*FrameHeader.SettingLength, FrameType.Settings, FrameFlags.None, streamId: 0); _outgoingBuffer.Commit(FrameHeader.Size); BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.EnablePush); @@ -200,14 +199,6 @@ public async ValueTask SetupAsync() _outgoingBuffer.Commit(2); BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, (uint)InitialStreamWindowSize); _outgoingBuffer.Commit(4); -#else - FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, 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); -#endif // Send initial connection-level WINDOW_UPDATE uint windowUpdateAmount = (uint)(ConnectionWindowSize - InitialStreamWindowSize); @@ -796,12 +787,6 @@ private void ProcessWindowUpdateFrame(FrameHeader frameHeader) if (frameHeader.StreamId == 0) { - if (amount > 2147000000) - { - amount++; - amount--; - } - _connectionWindow.AdjustCredit(amount); } else From 57ade9416166d0aff7f1849f8cc4c448b32c19ab Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 18 May 2021 12:11:12 +0200 Subject: [PATCH 027/101] remove some junk code --- .../Http/SocketsHttpHandler/Http2Connection.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 8d6872eaa9d0f4..2580efbbc15d30 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -189,7 +189,6 @@ public async ValueTask SetupAsync() _outgoingBuffer.Commit(s_http2ConnectionPreface.Length); // Send SETTINGS frame. Disable push promise & set initial window size. -#if true FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, 2*FrameHeader.SettingLength, FrameType.Settings, FrameFlags.None, streamId: 0); _outgoingBuffer.Commit(FrameHeader.Size); BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.EnablePush); @@ -200,14 +199,6 @@ public async ValueTask SetupAsync() _outgoingBuffer.Commit(2); BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, (uint)InitialStreamWindowSize); _outgoingBuffer.Commit(4); -#else - FrameHeader.WriteTo(_outgoingBuffer.AvailableSpan, 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); -#endif // Send initial connection-level WINDOW_UPDATE uint windowUpdateAmount = (uint)(ConnectionWindowSize - InitialStreamWindowSize); @@ -796,12 +787,6 @@ private void ProcessWindowUpdateFrame(FrameHeader frameHeader) if (frameHeader.StreamId == 0) { - if (amount > 2147000000) - { - amount++; - amount--; - } - _connectionWindow.AdjustCredit(amount); } else From dd2e98202fa2b147bcc25adc8a9e881ef2574540 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 18 May 2021 16:56:42 +0200 Subject: [PATCH 028/101] Simplify logic around windowSizeIncrement --- .../Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 4d1b2957074760..852262ea7d4c65 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -89,10 +89,8 @@ public override void AdjustWindow(int bytesConsumed) if (_magic * _delivered * rtt.Ticks > StreamWindowThreshold * dt.Ticks) { - int newWindowSize = _streamWindowSize * 2; - windowSizeIncrement += newWindowSize - _streamWindowSize; - - _streamWindowSize = newWindowSize; + windowSizeIncrement += _streamWindowSize; + _streamWindowSize *= 2; _stream.Trace($"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); } From 90dce09f6d5ae833ee6497eaf2bc355a03ca7557 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 18 May 2021 18:16:20 +0200 Subject: [PATCH 029/101] Simplify logic around windowSizeIncrement --- .../Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 4d1b2957074760..852262ea7d4c65 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -89,10 +89,8 @@ public override void AdjustWindow(int bytesConsumed) if (_magic * _delivered * rtt.Ticks > StreamWindowThreshold * dt.Ticks) { - int newWindowSize = _streamWindowSize * 2; - windowSizeIncrement += newWindowSize - _streamWindowSize; - - _streamWindowSize = newWindowSize; + windowSizeIncrement += _streamWindowSize; + _streamWindowSize *= 2; _stream.Trace($"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); } From a8bd26a145e4365f1731324ad2f55c76883a32ab Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 18 May 2021 19:50:09 +0200 Subject: [PATCH 030/101] use content stream in benchmark --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 02e1ae7ac77754..19ac2d1c9b4bb6 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Diagnostics.Tracing; +using System.IO; using System.Linq; using System.Net.NetworkInformation; using System.Net.Sockets; @@ -135,6 +136,10 @@ private async Task TestHandler(string info, string hostName, bool http2, double _output.WriteLine($"{info} / {lengthMb} MB from {hostName}"); Stopwatch sw = Stopwatch.StartNew(); var response = await client.SendAsync(message); + + using Stream responseStream = await response.Content.ReadAsStreamAsync(); + await responseStream.CopyToAsync(Stream.Null); + long elapsedMs = sw.ElapsedMilliseconds; _output.WriteLine($"{info}: {response.StatusCode} in {elapsedMs} ms"); From 4e521e5d67a04b5337655aa4c545aa546707e3ca Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 19 May 2021 13:28:06 +0200 Subject: [PATCH 031/101] use Stopwatch instead of DateTime.Now --- .../SocketsHttpHandler/Http2Connection.cs | 3 ++ .../Http/SocketsHttpHandler/Http2Stream.cs | 3 ++ .../Http2StreamWindowManager.cs | 30 +++++++++++-------- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 2580efbbc15d30..7af24a0ad5a4cc 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -2035,6 +2035,9 @@ internal void Trace(int streamId, string message, [CallerMemberName] string? mem memberName, // method name message); // message + public void TraceFlowControl(string message, [CallerMemberName] string? memberName = null) => + Trace("[FlowControl] " + message, memberName); + [DoesNotReturn] private static void ThrowRetry(string message, Exception innerException) => throw new HttpRequestException(message, innerException, allowRetry: RequestRetryType.RetryOnConnectionFailure); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index b1b920e2a4595f..6e1fb8615f230c 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -1335,6 +1335,9 @@ private ValueTask WaitForDataAsync(CancellationToken cancellationToken) public void Trace(string message, [CallerMemberName] string? memberName = null) => _connection.Trace(StreamId, message, memberName); + public void TraceFlowControl(string message, [CallerMemberName] string? memberName = null) => + Trace("[FlowControl] " + message, memberName); + private enum ResponseProtocolState : byte { ExpectingStatus, diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 852262ea7d4c65..e3ae392160cb84 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -28,7 +28,7 @@ public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) _stream = stream; _streamWindowSize = connection.InitialStreamWindowSize; _streamWindowUpdateRatio = _connection._pool.Settings._streamWindowUpdateRatio; - _stream.Trace($"StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}, streamWindowUpdateRatio: {_streamWindowUpdateRatio}"); + _stream.TraceFlowControl($"StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}, streamWindowUpdateRatio: {_streamWindowUpdateRatio}"); } internal int StreamWindowSize => _streamWindowSize; @@ -63,7 +63,8 @@ public virtual void AdjustWindow(int bytesConsumed) private class DynamicHttp2StreamWindowManager : Http2StreamWindowManager { - private DateTime _lastWindowUpdate = DateTime.Now; + private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); + private TimeSpan _lastWindowUpdate; private long _magic = 1; @@ -71,7 +72,8 @@ public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream s : base(connection, stream) { _magic = connection._pool.Settings._streamWindowMagicMultiplier; - _stream.Trace($"magic:{_magic}"); + _stream.TraceFlowControl($" magic:{_magic} | Stopwatch: IsHighResolution={Stopwatch.IsHighResolution}, Frequency={Stopwatch.Frequency}"); + _lastWindowUpdate = _stopwatch.Elapsed; } public override void AdjustWindow(int bytesConsumed) @@ -83,7 +85,8 @@ public override void AdjustWindow(int bytesConsumed) } TimeSpan rtt = _connection._rttEstimator!.Rtt; - TimeSpan dt = DateTime.Now - _lastWindowUpdate; + TimeSpan currentTime = _stopwatch.Elapsed; + TimeSpan dt = currentTime - _lastWindowUpdate; int windowSizeIncrement = _delivered; @@ -92,22 +95,25 @@ public override void AdjustWindow(int bytesConsumed) windowSizeIncrement += _streamWindowSize; _streamWindowSize *= 2; - _stream.Trace($"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); + _stream.TraceFlowControl($"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold} \n | {GetDiagnostics()}"); } else { - string msg = - $"No adjustment! | RTT={rtt.TotalMilliseconds} ms || dt={dt.TotalMilliseconds} ms || " + - //$"_delivered * rtt.Ticks = {_delivered * rtt.Ticks} || StreamWindowThreshold * dt.Ticks = {StreamWindowThreshold * dt.Ticks} ||" + - $"Magic*_delivered/dt = {_magic* _delivered / dt.TotalSeconds} bytes/sec || StreamWindowThreshold/RTT = {StreamWindowThreshold / rtt.TotalSeconds} bytes/sec"; - _stream.Trace(msg); + string msg = "No adjustment! |" + GetDiagnostics(); + _stream.TraceFlowControl(msg); } Task sendWindowUpdateTask = _connection.SendWindowUpdateAsync(_stream.StreamId, windowSizeIncrement); _connection.LogExceptions(sendWindowUpdateTask); _delivered = 0; - _lastWindowUpdate = DateTime.Now; + _lastWindowUpdate = currentTime; + + string GetDiagnostics() + { + return "RTT={rtt.TotalMilliseconds} ms || dt={dt.TotalMilliseconds} ms || " + + $"Magic*_delivered/dt = {_magic * _delivered / dt.TotalSeconds} bytes/sec || StreamWindowThreshold/RTT = {StreamWindowThreshold / rtt.TotalSeconds} bytes/sec"; + } } } @@ -137,7 +143,7 @@ internal void UpdateEstimation() if (Interop.Winsock.GetTcpInfoV0(_socket.SafeHandle, out Interop.Winsock._TCP_INFO_v0 tcpInfo) == SocketError.Success) { Rtt = TimeSpan.FromTicks(10 * tcpInfo.RttUs); - _connection.Trace($"Rtt estimation updated: Rtt={Rtt} || (initial fake:{_initialRtt} difference:{Rtt - _initialRtt}"); + _connection.TraceFlowControl($"Rtt estimation updated: Rtt={Rtt} || (initial fake:{_initialRtt} difference:{Rtt - _initialRtt}"); } } From 9bef28cfb775874c71a710805ed5a6128a02888a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 19 May 2021 13:35:07 +0200 Subject: [PATCH 032/101] fix tracing --- .../Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index e3ae392160cb84..79488f34d7bef9 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -111,7 +111,7 @@ public override void AdjustWindow(int bytesConsumed) string GetDiagnostics() { - return "RTT={rtt.TotalMilliseconds} ms || dt={dt.TotalMilliseconds} ms || " + + return $"RTT={rtt.TotalMilliseconds} ms || dt={dt.TotalMilliseconds} ms || " + $"Magic*_delivered/dt = {_magic * _delivered / dt.TotalSeconds} bytes/sec || StreamWindowThreshold/RTT = {StreamWindowThreshold / rtt.TotalSeconds} bytes/sec"; } } From aa256aab9fde74aef5dd087746a5b67e5a3ec94c Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 19 May 2021 13:41:34 +0200 Subject: [PATCH 033/101] better tests --- .../FunctionalTests/_LargeFileBenchmark.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 19ac2d1c9b4bb6..3211ae6ff9ee93 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -97,26 +97,23 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) public async Task Download20_Dynamic_Default(string hostName) { _listener.Enabled = true; - //_listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync"); + _listener.Filter = m => m.Contains("[FlowControl]"); SocketsHttpHandler handler = new SocketsHttpHandler() { FakeRtt = await EstimateRttAsync(hostName) }; - await TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb, handler); + await TestHandler("SocketsHttpHandler HTTP 2.0 Dynamic default", hostName, true, LengthMb, handler); } [Theory] [InlineData(BenchmarkServer, 8, 1)] - [InlineData(BenchmarkServer, 4, 1)] - [InlineData(BenchmarkServer, 2, 1)] - [InlineData(BenchmarkServer, 8, 10)] - [InlineData(BenchmarkServer, 4, 10)] - [InlineData(BenchmarkServer, 2, 10)] + [InlineData(BenchmarkServer, 8, 50)] + [InlineData(BenchmarkServer, 32, 1)] public async Task Download20_Dynamic_Custom(string hostName, int ratio, int magic) { _listener.Enabled = true; - //_listener.Filter = m => m.Contains("No adjustment") || m.Contains("Updated StreamWindowSize") || m.Contains("SendWindowUpdateAsync"); + _listener.Filter = m => m.Contains("[FlowControl]"); SocketsHttpHandler handler = new SocketsHttpHandler() { @@ -136,10 +133,6 @@ private async Task TestHandler(string info, string hostName, bool http2, double _output.WriteLine($"{info} / {lengthMb} MB from {hostName}"); Stopwatch sw = Stopwatch.StartNew(); var response = await client.SendAsync(message); - - using Stream responseStream = await response.Content.ReadAsStreamAsync(); - await responseStream.CopyToAsync(Stream.Null); - long elapsedMs = sw.ElapsedMilliseconds; _output.WriteLine($"{info}: {response.StatusCode} in {elapsedMs} ms"); From 48cf3d23a0d4495ae9295ccc77a20dcdf436b26d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 19 May 2021 14:24:41 +0200 Subject: [PATCH 034/101] fix EstimateRttAsync in tests --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 3211ae6ff9ee93..f9a18798728902 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -153,7 +153,7 @@ private async Task EstimateRttAsync(string hostName) PingReply reply1 = await ping.SendPingAsync(addr); PingReply reply2 = await ping.SendPingAsync(addr); - TimeSpan rtt = new TimeSpan(reply1.RoundtripTime + reply2.RoundtripTime) / 2; + TimeSpan rtt = TimeSpan.FromMilliseconds(reply1.RoundtripTime + reply2.RoundtripTime) / 2; _output.WriteLine($"Estimated RTT: {rtt.TotalMilliseconds} ms"); return rtt; } @@ -172,7 +172,7 @@ static HttpRequestMessage GenerateRequestMessage(string hostName, bool http2, do msg.Version = new Version(2, 0); msg.VersionPolicy = HttpVersionPolicy.RequestVersionExact; } - + return msg; } } From cd733951e241a46bf21af07da9bd64fe9563f13a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 29 May 2021 00:28:43 +0200 Subject: [PATCH 035/101] use PING for RTT estimation --- .../SocketsHttpHandler/Http2Connection.cs | 29 +++-- .../Http2StreamWindowManager.cs | 103 +++++++++++------- .../SocketsHttpHandler/HttpConnectionPool.cs | 2 +- 3 files changed, 82 insertions(+), 52 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index b7bac3fe365869..b54c07d157f110 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -38,7 +38,7 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable private readonly CreditManager _connectionWindow; private readonly CreditManager _concurrentStreams; - private readonly RttEstimator? _rttEstimator; + private readonly RttEstimator _rttEstimator; private int _nextStream; private bool _expectingSettingsAck; @@ -123,7 +123,7 @@ internal enum KeepAliveState private long _keepAlivePingTimeoutTimestamp; private volatile KeepAliveState _keepAliveState; - public Http2Connection(HttpConnectionPool pool, Stream stream, System.Net.Sockets.Socket? socket) + public Http2Connection(HttpConnectionPool pool, Stream stream) { _pool = pool; _stream = stream; @@ -137,9 +137,7 @@ public Http2Connection(HttpConnectionPool pool, Stream stream, System.Net.Socket _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; + _rttEstimator = new RttEstimator(this); _writeChannel = Channel.CreateUnbounded(s_channelOptions); @@ -489,6 +487,7 @@ private async ValueTask ProcessHeadersFrame(FrameHeader frameHeader) _hpackDecoder.CompleteDecode(); http2Stream?.OnHeadersComplete(endStream); + //_rttEstimator.Update(); } /// Nop implementation of used by . @@ -590,7 +589,7 @@ private void ProcessDataFrame(FrameHeader frameHeader) if (frameData.Length > 0) { - _rttEstimator?.UpdateEstimation(); + _rttEstimator.Update(); ExtendWindow(frameData.Length); } @@ -1992,11 +1991,19 @@ private void RefreshPingTimestamp() private void ProcessPingAck(long payload) { - if (_keepAliveState != KeepAliveState.PingSent) - ThrowProtocolError(); - if (Interlocked.Read(ref _keepAlivePingPayload) != payload) - ThrowProtocolError(); - _keepAliveState = KeepAliveState.None; + if (payload < 0) // RTT ping + { + _rttEstimator.OnPingAck(payload); + return; + } + else // Keepalive ping + { + if (_keepAliveState != KeepAliveState.PingSent) + ThrowProtocolError(); + if (Interlocked.Read(ref _keepAlivePingPayload) != payload) + ThrowProtocolError(); + _keepAliveState = KeepAliveState.None; + } } private void VerifyKeepAlive() diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 79488f34d7bef9..4d68f27ebc38f3 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Net.Sockets; +using System.Threading; using System.Threading.Tasks; namespace System.Net.Http @@ -84,23 +85,32 @@ public override void AdjustWindow(int bytesConsumed) return; } - TimeSpan rtt = _connection._rttEstimator!.Rtt; - TimeSpan currentTime = _stopwatch.Elapsed; - TimeSpan dt = currentTime - _lastWindowUpdate; - int windowSizeIncrement = _delivered; + TimeSpan currentTime = _stopwatch.Elapsed; - if (_magic * _delivered * rtt.Ticks > StreamWindowThreshold * dt.Ticks) - { - windowSizeIncrement += _streamWindowSize; - _streamWindowSize *= 2; - - _stream.TraceFlowControl($"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold} \n | {GetDiagnostics()}"); - } - else + if (_connection._rttEstimator.Rtt > TimeSpan.Zero) { - string msg = "No adjustment! |" + GetDiagnostics(); - _stream.TraceFlowControl(msg); + TimeSpan rtt = _connection._rttEstimator.Rtt; + TimeSpan dt = currentTime - _lastWindowUpdate; + + if (_magic * _delivered * rtt.Ticks > StreamWindowThreshold * dt.Ticks) + { + windowSizeIncrement += _streamWindowSize; + _streamWindowSize *= 2; + + _stream.TraceFlowControl($"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold} \n | {GetDiagnostics()}"); + } + else + { + string msg = "No adjustment! |" + GetDiagnostics(); + _stream.TraceFlowControl(msg); + } + + string GetDiagnostics() + { + return $"RTT={rtt.TotalMilliseconds} ms || dt={dt.TotalMilliseconds} ms || " + + $"Magic*_delivered/dt = {_magic * _delivered / dt.TotalSeconds} bytes/sec || StreamWindowThreshold/RTT = {StreamWindowThreshold / rtt.TotalSeconds} bytes/sec"; + } } Task sendWindowUpdateTask = _connection.SendWindowUpdateAsync(_stream.StreamId, windowSizeIncrement); @@ -108,50 +118,63 @@ public override void AdjustWindow(int bytesConsumed) _delivered = 0; _lastWindowUpdate = currentTime; - - string GetDiagnostics() - { - return $"RTT={rtt.TotalMilliseconds} ms || dt={dt.TotalMilliseconds} ms || " + - $"Magic*_delivered/dt = {_magic * _delivered / dt.TotalSeconds} bytes/sec || StreamWindowThreshold/RTT = {StreamWindowThreshold / rtt.TotalSeconds} bytes/sec"; - } } } private class RttEstimator { - private readonly TimeSpan _initialRtt; + private enum Status + { + NotReady, + Waiting, + PingSent + } + + private const long PingIntervalInSeconds = 5; + private static readonly long PingIntervalInTicks = PingIntervalInSeconds * Stopwatch.Frequency; + private Http2Connection _connection; - private readonly Socket? _socket; + + private Status _status; + private long _pingSentTimestamp; + private long _pingCounter = -1; + public TimeSpan Rtt { get; private set; } - public RttEstimator(Http2Connection connection, TimeSpan? fakeRtt, Socket? socket) + + public RttEstimator(Http2Connection connection) { _connection = connection; - if (fakeRtt.HasValue) - { - Rtt = fakeRtt.Value; - _initialRtt = Rtt; - } - _socket = socket; - UpdateEstimation(); } -#if WINDOWS - internal void UpdateEstimation() + internal void Update() { - if (_socket == null) return; - - if (Interop.Winsock.GetTcpInfoV0(_socket.SafeHandle, out Interop.Winsock._TCP_INFO_v0 tcpInfo) == SocketError.Success) + if (_status == Status.NotReady || _status == Status.Waiting) { - Rtt = TimeSpan.FromTicks(10 * tcpInfo.RttUs); - _connection.TraceFlowControl($"Rtt estimation updated: Rtt={Rtt} || (initial fake:{_initialRtt} difference:{Rtt - _initialRtt}"); + long now = Stopwatch.GetTimestamp(); + + if (now - _pingSentTimestamp > PingIntervalInTicks) // also true if _status == NotReady + { + // Send a PING + long payload = Interlocked.Decrement(ref _pingCounter); + _connection.LogExceptions(_connection.SendPingAsync(payload, isAck: false)); + _pingSentTimestamp = now; + _status = Status.PingSent; + } } } -#else - internal void UpdateEstimation() + internal void OnPingAck(long payload) { + Debug.Assert(payload < 0); + + if (Interlocked.Read(ref _pingCounter) != payload) + ThrowProtocolError(); + + long elapsedTicks = Stopwatch.GetTimestamp() - _pingSentTimestamp; + Rtt = TimeSpan.FromSeconds(elapsedTicks / (double)Stopwatch.Frequency); + _connection.TraceFlowControl($"Updated RTT: {Rtt.TotalMilliseconds} ms"); + _status = Status.Waiting; } -#endif } } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index 634fad0eb33f70..dbc70479431ebd 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -1446,7 +1446,7 @@ private async ValueTask ConstructHttp2ConnectionAsync(Stream st stream = await ApplyPlaintextFilterAsync(async: true, stream, HttpVersion.Version20, request, cancellationToken).ConfigureAwait(false); - Http2Connection http2Connection = new Http2Connection(this, stream, socket); + Http2Connection http2Connection = new Http2Connection(this, stream); try { await http2Connection.SetupAsync().ConfigureAwait(false); From b34df9fe6eb2dd236a54a3103035ba28e80a2482 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 1 Jun 2021 18:28:47 +0200 Subject: [PATCH 036/101] _LargeFileBenchmark custom port --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index f9a18798728902..d27b055b25766d 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -130,7 +130,7 @@ private async Task TestHandler(string info, string hostName, bool http2, double using var client = new HttpClient(handler, true); client.Timeout = TimeSpan.FromMinutes(2); var message = GenerateRequestMessage(hostName, http2, lengthMb); - _output.WriteLine($"{info} / {lengthMb} MB from {hostName}"); + _output.WriteLine($"{info} / {lengthMb} MB from {message.RequestUri}"); Stopwatch sw = Stopwatch.StartNew(); var response = await client.SendAsync(message); long elapsedMs = sw.ElapsedMilliseconds; @@ -161,7 +161,16 @@ private async Task EstimateRttAsync(string hostName) static HttpRequestMessage GenerateRequestMessage(string hostName, bool http2, double lengthMb = 5) { - string url = $"http://{hostName}:{(http2 ? "5001" : "5000")}?lengthMb={lengthMb}"; + int port = http2 ? 5001 : 5000; + int sep = hostName.IndexOf(':'); + if (sep > 0) + { + string portStr = hostName.Substring(sep + 1, hostName.Length - sep - 1); + int.TryParse(portStr, out port); + hostName = hostName.Substring(0, sep); + } + + string url = $"http://{hostName}:{port}?lengthMb={lengthMb}"; var msg = new HttpRequestMessage(HttpMethod.Get, url) { Version = new Version(1, 1) From 1645b39ad81e686f6f54f63ee0936f2ee3f018cc Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 1 Jun 2021 16:51:57 +0000 Subject: [PATCH 037/101] test both kestrel & go --- .../FunctionalTests/_LargeFileBenchmark.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index d27b055b25766d..a8903c05526280 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -28,11 +28,12 @@ public LargeFileBenchmark(ITestOutputHelper output) { _output = output; _listener = new LogHttpEventListener(output); + _listener.Filter = m => m.Contains("[FlowControl]"); } public void Dispose() => _listener?.Dispose(); - private const double LengthMb = 50; + private const double LengthMb = 100; private const string BenchmarkServer = "10.194.114.94"; [Theory] @@ -93,17 +94,21 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) [Theory] - [InlineData(BenchmarkServer)] - public async Task Download20_Dynamic_Default(string hostName) + [InlineData("10.194.114.94:5001")] + [InlineData("10.194.114.94:5002")] + public async Task Download20_Dynamic_Default_Run1(string hostName) { _listener.Enabled = true; - _listener.Filter = m => m.Contains("[FlowControl]"); + await TestHandler("SocketsHttpHandler HTTP 2.0 Dynamic default", hostName, true, LengthMb); + } - SocketsHttpHandler handler = new SocketsHttpHandler() - { - FakeRtt = await EstimateRttAsync(hostName) - }; - await TestHandler("SocketsHttpHandler HTTP 2.0 Dynamic default", hostName, true, LengthMb, handler); + [Theory] + [InlineData("10.194.114.94:5001")] + [InlineData("10.194.114.94:5002")] + public async Task Download20_Dynamic_Default_Run2(string hostName) + { + _listener.Enabled = true; + await TestHandler("SocketsHttpHandler HTTP 2.0 Dynamic default", hostName, true, LengthMb); } [Theory] From 8355c7518d1a46378f2b71f1a5290dba17beed29 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 1 Jun 2021 19:08:06 +0200 Subject: [PATCH 038/101] try more regular pings --- .../Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 4d68f27ebc38f3..11178a26cec1b6 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -130,8 +130,8 @@ private enum Status PingSent } - private const long PingIntervalInSeconds = 5; - private static readonly long PingIntervalInTicks = PingIntervalInSeconds * Stopwatch.Frequency; + private const double PingIntervalInSeconds = .5; + private static readonly long PingIntervalInTicks =(long)(PingIntervalInSeconds * Stopwatch.Frequency); private Http2Connection _connection; From e503e25ae8570142c5b404e2542261688bca1685 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 2 Jun 2021 15:14:08 +0200 Subject: [PATCH 039/101] multi stream test + opt-in static RTT --- .../SocketsHttpHandler/Http2Connection.cs | 2 +- .../Http2StreamWindowManager.cs | 13 ++++- .../FunctionalTests/_LargeFileBenchmark.cs | 57 ++++++++++++++----- 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index b54c07d157f110..f572a8784ab50f 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -137,7 +137,7 @@ public Http2Connection(HttpConnectionPool pool, Stream stream) _connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialConnectionWindowSize); _concurrentStreams = new CreditManager(this, nameof(_concurrentStreams), InitialMaxConcurrentStreams); InitialStreamWindowSize = pool.Settings._initialStreamWindowSize; - _rttEstimator = new RttEstimator(this); + _rttEstimator = new RttEstimator(this, pool.Settings._fakeRtt); _writeChannel = Channel.CreateUnbounded(s_channelOptions); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 11178a26cec1b6..ec16cdc5a44c52 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -141,13 +141,23 @@ private enum Status public TimeSpan Rtt { get; private set; } - public RttEstimator(Http2Connection connection) + private readonly TimeSpan? _staticRtt; + + public RttEstimator(Http2Connection connection, TimeSpan? staticRtt) { _connection = connection; + _staticRtt = staticRtt; + if (_staticRtt.HasValue) + { + Rtt = _staticRtt.Value; + _connection.TraceFlowControl($"Using static RTT: {Rtt.TotalMilliseconds} ms"); + } } internal void Update() { + if (_staticRtt.HasValue) return; + if (_status == Status.NotReady || _status == Status.Waiting) { long now = Stopwatch.GetTimestamp(); @@ -166,6 +176,7 @@ internal void Update() internal void OnPingAck(long payload) { Debug.Assert(payload < 0); + if (_staticRtt.HasValue) return; if (Interlocked.Read(ref _pingCounter) != payload) ThrowProtocolError(); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index a8903c05526280..10c52742eb33a6 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -1,5 +1,6 @@ // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Tracing; using System.IO; @@ -96,39 +97,65 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) [Theory] [InlineData("10.194.114.94:5001")] [InlineData("10.194.114.94:5002")] - public async Task Download20_Dynamic_Default_Run1(string hostName) + public async Task Download20_Dynamic_SingleStream_Run1(string hostName) { _listener.Enabled = true; - await TestHandler("SocketsHttpHandler HTTP 2.0 Dynamic default", hostName, true, LengthMb); + await TestHandler("SocketsHttpHandler HTTP 2.0 Dynamic single stream Run1", hostName, true, LengthMb); } [Theory] [InlineData("10.194.114.94:5001")] [InlineData("10.194.114.94:5002")] - public async Task Download20_Dynamic_Default_Run2(string hostName) + public async Task Download20_Dynamic_SingleStream_Run2(string hostName) { _listener.Enabled = true; - await TestHandler("SocketsHttpHandler HTTP 2.0 Dynamic default", hostName, true, LengthMb); + await TestHandler("SocketsHttpHandler HTTP 2.0 Dynamic single stream Run2", hostName, true, LengthMb); } [Theory] - [InlineData(BenchmarkServer, 8, 1)] - [InlineData(BenchmarkServer, 8, 50)] - [InlineData(BenchmarkServer, 32, 1)] - public async Task Download20_Dynamic_Custom(string hostName, int ratio, int magic) + [InlineData("10.194.114.94:5001")] + [InlineData("10.194.114.94:5002")] + public async Task Download20_StaticRtt(string hostName) { _listener.Enabled = true; - _listener.Filter = m => m.Contains("[FlowControl]"); - - SocketsHttpHandler handler = new SocketsHttpHandler() + var handler = new SocketsHttpHandler { - FakeRtt = await EstimateRttAsync(hostName), - StreamWindowUpdateRatio = ratio, - StreamWindowMagicMultiplier = magic + FakeRtt = await EstimateRttAsync(hostName) }; - await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic | ratio={ratio} | magic={magic}", hostName, true, LengthMb, handler); + + await TestHandler("SocketsHttpHandler HTTP 2.0 Dynamic single stream Run2", hostName, true, LengthMb, handler); } + [Theory] + [InlineData("10.194.114.94:5001")] + [InlineData("10.194.114.94:5002")] + public async Task Download20_Dynamic_MultiStream(string hostName) + { + _listener.Enabled = true; + var handler = new SocketsHttpHandler(); + using var client = new HttpClient(handler, true); + client.Timeout = TimeSpan.FromMinutes(3); + const int NStreams = 4; + string info = $"SocketsHttpHandler HTTP 2.0 Dynamic {NStreams} concurrent streams"; + + var message = GenerateRequestMessage(hostName, true, LengthMb); + _output.WriteLine($"{info} / {LengthMb} MB from {message.RequestUri}"); + + Stopwatch sw = Stopwatch.StartNew(); + List tasks = new List(); + for (int i = 0; i < NStreams; i++) + { + var task = client.SendAsync(message); + tasks.Add(task); + } + + await Task.WhenAll(tasks); + + long elapsedMs = sw.ElapsedMilliseconds; + _output.WriteLine($"{info}: completed in {elapsedMs} ms"); + } + + private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, HttpMessageHandler handler = null) { handler ??= new SocketsHttpHandler(); From 461291c7bfc16a817331f86a5561b277fbaf0213 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 2 Jun 2021 15:42:16 +0200 Subject: [PATCH 040/101] Task.Run --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 10c52742eb33a6..035a39e1c77c23 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -145,7 +145,7 @@ public async Task Download20_Dynamic_MultiStream(string hostName) List tasks = new List(); for (int i = 0; i < NStreams; i++) { - var task = client.SendAsync(message); + var task = Task.Run(() => client.SendAsync(message)); tasks.Add(task); } From 0e173495f4206091420c2b89ed696bd1b2123c4f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 2 Jun 2021 16:13:12 +0200 Subject: [PATCH 041/101] recreate request --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 035a39e1c77c23..c9dadf67bf1414 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -145,6 +145,12 @@ public async Task Download20_Dynamic_MultiStream(string hostName) List tasks = new List(); for (int i = 0; i < NStreams; i++) { + message = new HttpRequestMessage + { + RequestUri = message.RequestUri, + Version = message.Version, + VersionPolicy = message.VersionPolicy + }; var task = Task.Run(() => client.SendAsync(message)); tasks.Add(task); } From 869c377e33ddce6aa928ff52ab2ec95a738b1792 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 2 Jun 2021 16:02:43 +0000 Subject: [PATCH 042/101] more specific benchmarks --- .../FunctionalTests/_LargeFileBenchmark.cs | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index c9dadf67bf1414..23970ccb659a04 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -84,6 +84,23 @@ public LargeFileBenchmark(ITestOutputHelper output) [InlineData(BenchmarkServer, 16384)] public Task Download20_SpecificWindow_Run4(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + [Theory] + [InlineData(BenchmarkServer, 4096)] + public Task Download20_SpecificWindow_4M_Run1(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + + [Theory] + [InlineData(BenchmarkServer, 4096)] + public Task Download20_SpecificWindow_4M_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + + [Theory] + [InlineData(BenchmarkServer, 4096)] + public Task Download20_SpecificWindow_16M_Run1(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + + [Theory] + [InlineData(BenchmarkServer, 4096)] + public Task Download20_SpecificWindow_16M_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + + private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) { SocketsHttpHandler handler = new SocketsHttpHandler() @@ -138,6 +155,7 @@ public async Task Download20_Dynamic_MultiStream(string hostName) const int NStreams = 4; string info = $"SocketsHttpHandler HTTP 2.0 Dynamic {NStreams} concurrent streams"; + var message = GenerateRequestMessage(hostName, true, LengthMb); _output.WriteLine($"{info} / {LengthMb} MB from {message.RequestUri}"); @@ -145,13 +163,7 @@ public async Task Download20_Dynamic_MultiStream(string hostName) List tasks = new List(); for (int i = 0; i < NStreams; i++) { - message = new HttpRequestMessage - { - RequestUri = message.RequestUri, - Version = message.Version, - VersionPolicy = message.VersionPolicy - }; - var task = Task.Run(() => client.SendAsync(message)); + var task = Task.Run(() => client.SendAsync(GenerateRequestMessage(hostName, true, LengthMb))); tasks.Add(task); } @@ -178,6 +190,12 @@ private async Task TestHandler(string info, string hostName, bool http2, double private async Task EstimateRttAsync(string hostName) { + int sep = hostName.IndexOf(':'); + if (sep > 0) + { + hostName = hostName.Substring(0, sep); + } + IPAddress addr; if (!IPAddress.TryParse(hostName, out addr)) { From 3ddd7882f670684895cbc8a714deff356e680d5c Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 2 Jun 2021 18:36:57 +0200 Subject: [PATCH 043/101] EnableDynamicHttp2StreamWindowSizing --- src/libraries/System.Net.Http/ref/System.Net.Http.cs | 1 + .../Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs | 6 ++++++ .../Net/Http/SocketsHttpHandler/Http2Connection.cs | 9 +++++---- .../System/Net/Http/SocketsHttpHandler/Http2Stream.cs | 2 +- .../Http/SocketsHttpHandler/Http2StreamWindowManager.cs | 2 +- .../Http/SocketsHttpHandler/HttpConnectionSettings.cs | 4 +++- .../Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs | 6 ++++++ .../tests/FunctionalTests/_LargeFileBenchmark.cs | 1 + 8 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Net.Http/ref/System.Net.Http.cs b/src/libraries/System.Net.Http/ref/System.Net.Http.cs index 37b8656429c156..dff3e7ea5a31f2 100644 --- a/src/libraries/System.Net.Http/ref/System.Net.Http.cs +++ b/src/libraries/System.Net.Http/ref/System.Net.Http.cs @@ -356,6 +356,7 @@ public SocketsHttpHandler() { } public static bool IsSupported { get { throw null; } } public TimeSpan? FakeRtt { get { throw null; } set { } } public int InitialStreamWindowSize { get { throw null; } set { } } + public bool EnableDynamicHttp2StreamWindowSizing { 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 { } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs index c459d4bdd48313..27e1ec48701608 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs @@ -31,6 +31,12 @@ public TimeSpan? FakeRtt set => throw new PlatformNotSupportedException(); } + public bool EnableDynamicHttp2StreamWindowSizing + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + public int InitialStreamWindowSize { get => throw new PlatformNotSupportedException(); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index f572a8784ab50f..a5f4adbc1bdf3b 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -38,7 +38,7 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable private readonly CreditManager _connectionWindow; private readonly CreditManager _concurrentStreams; - private readonly RttEstimator _rttEstimator; + private readonly RttEstimator? _rttEstimator; private int _nextStream; private bool _expectingSettingsAck; @@ -137,7 +137,8 @@ public Http2Connection(HttpConnectionPool pool, Stream stream) _connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialConnectionWindowSize); _concurrentStreams = new CreditManager(this, nameof(_concurrentStreams), InitialMaxConcurrentStreams); InitialStreamWindowSize = pool.Settings._initialStreamWindowSize; - _rttEstimator = new RttEstimator(this, pool.Settings._fakeRtt); + _rttEstimator = pool.Settings._enableDynamicHttp2StreamWindowSizing ? + new RttEstimator(this, pool.Settings._fakeRtt) : null; _writeChannel = Channel.CreateUnbounded(s_channelOptions); @@ -589,7 +590,7 @@ private void ProcessDataFrame(FrameHeader frameHeader) if (frameData.Length > 0) { - _rttEstimator.Update(); + _rttEstimator?.Update(); ExtendWindow(frameData.Length); } @@ -1993,7 +1994,7 @@ private void ProcessPingAck(long payload) { if (payload < 0) // RTT ping { - _rttEstimator.OnPingAck(payload); + _rttEstimator?.OnPingAck(payload); return; } else // Keepalive ping diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index 95e17907e39da6..89bcee034f20c1 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -97,7 +97,7 @@ public Http2Stream(HttpRequestMessage request, Http2Connection connection) _responseBuffer = new MultiArrayBuffer(InitialStreamBufferSize); - _windowManager = connection._rttEstimator != null ? + _windowManager = connection._pool.Settings._enableDynamicHttp2StreamWindowSizing ? new DynamicHttp2StreamWindowManager(connection, this) : new Http2StreamWindowManager(connection, this); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index ec16cdc5a44c52..c62d5cc9f5e82e 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -88,7 +88,7 @@ public override void AdjustWindow(int bytesConsumed) int windowSizeIncrement = _delivered; TimeSpan currentTime = _stopwatch.Elapsed; - if (_connection._rttEstimator.Rtt > TimeSpan.Zero) + if (_connection._rttEstimator!.Rtt > TimeSpan.Zero) { TimeSpan rtt = _connection._rttEstimator.Rtt; TimeSpan dt = currentTime - _lastWindowUpdate; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index 7f846e215ed4b3..c3950f22839083 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -68,6 +68,7 @@ internal sealed class HttpConnectionSettings internal IDictionary? _properties; internal TimeSpan? _fakeRtt; + internal bool _enableDynamicHttp2StreamWindowSizing = true; internal int _initialStreamWindowSize = 65535; internal int _streamWindowUpdateRatio = 8; @@ -129,7 +130,8 @@ public HttpConnectionSettings CloneAndNormalize() _fakeRtt = _fakeRtt, _initialStreamWindowSize = _initialStreamWindowSize, _streamWindowUpdateRatio = _streamWindowUpdateRatio, - _streamWindowMagicMultiplier = _streamWindowMagicMultiplier + _streamWindowMagicMultiplier = _streamWindowMagicMultiplier, + _enableDynamicHttp2StreamWindowSizing = _enableDynamicHttp2StreamWindowSizing }; // TODO: Remove if/when QuicImplementationProvider is removed from System.Net.Quic. diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index a17ce8a04e1df4..270fc2284b798d 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -55,6 +55,12 @@ public bool UseCookies } } + public bool EnableDynamicHttp2StreamWindowSizing + { + get => _settings._enableDynamicHttp2StreamWindowSizing; + set => _settings._enableDynamicHttp2StreamWindowSizing = value; + } + public TimeSpan? FakeRtt { get => _settings._fakeRtt; diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 23970ccb659a04..639911deae34ab 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -105,6 +105,7 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) { SocketsHttpHandler handler = new SocketsHttpHandler() { + EnableDynamicHttp2StreamWindowSizing = false, InitialStreamWindowSize = initialWindowKbytes * 1024 }; return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler); From d1cd6d89bc94ca9e8c43fb76de29529b40139c91 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 3 Jun 2021 17:12:09 +0200 Subject: [PATCH 044/101] benchmark stuff --- .../FunctionalTests/_LargeFileBenchmark.cs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 639911deae34ab..d4ad946184311b 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -34,8 +34,9 @@ public LargeFileBenchmark(ITestOutputHelper output) public void Dispose() => _listener?.Dispose(); - private const double LengthMb = 100; - private const string BenchmarkServer = "10.194.114.94"; + private const double LengthMb = 500; + //private const string BenchmarkServer = "10.194.114.94"; + private const string BenchmarkServer = "192.168.0.152"; [Theory] [InlineData(BenchmarkServer)] @@ -93,11 +94,11 @@ public LargeFileBenchmark(ITestOutputHelper output) public Task Download20_SpecificWindow_4M_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); [Theory] - [InlineData(BenchmarkServer, 4096)] + [InlineData(BenchmarkServer, 16384)] public Task Download20_SpecificWindow_16M_Run1(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); [Theory] - [InlineData(BenchmarkServer, 4096)] + [InlineData(BenchmarkServer, 16384)] public Task Download20_SpecificWindow_16M_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); @@ -113,8 +114,9 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) [Theory] - [InlineData("10.194.114.94:5001")] - [InlineData("10.194.114.94:5002")] + [InlineData(BenchmarkServer)] + //[InlineData("10.194.114.94:5001")] + //[InlineData("10.194.114.94:5002")] public async Task Download20_Dynamic_SingleStream_Run1(string hostName) { _listener.Enabled = true; @@ -122,8 +124,9 @@ public async Task Download20_Dynamic_SingleStream_Run1(string hostName) } [Theory] - [InlineData("10.194.114.94:5001")] - [InlineData("10.194.114.94:5002")] + [InlineData(BenchmarkServer)] + //[InlineData("10.194.114.94:5001")] + //[InlineData("10.194.114.94:5002")] public async Task Download20_Dynamic_SingleStream_Run2(string hostName) { _listener.Enabled = true; @@ -131,8 +134,9 @@ public async Task Download20_Dynamic_SingleStream_Run2(string hostName) } [Theory] - [InlineData("10.194.114.94:5001")] - [InlineData("10.194.114.94:5002")] + [InlineData(BenchmarkServer)] + //[InlineData("10.194.114.94:5001")] + //[InlineData("10.194.114.94:5002")] public async Task Download20_StaticRtt(string hostName) { _listener.Enabled = true; @@ -141,12 +145,13 @@ public async Task Download20_StaticRtt(string hostName) FakeRtt = await EstimateRttAsync(hostName) }; - await TestHandler("SocketsHttpHandler HTTP 2.0 Dynamic single stream Run2", hostName, true, LengthMb, handler); + await TestHandler("SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT", hostName, true, LengthMb, handler); } [Theory] - [InlineData("10.194.114.94:5001")] - [InlineData("10.194.114.94:5002")] + [InlineData(BenchmarkServer)] + //[InlineData("10.194.114.94:5001")] + //[InlineData("10.194.114.94:5002")] public async Task Download20_Dynamic_MultiStream(string hostName) { _listener.Enabled = true; @@ -211,7 +216,11 @@ private async Task EstimateRttAsync(string hostName) PingReply reply1 = await ping.SendPingAsync(addr); PingReply reply2 = await ping.SendPingAsync(addr); TimeSpan rtt = TimeSpan.FromMilliseconds(reply1.RoundtripTime + reply2.RoundtripTime) / 2; - _output.WriteLine($"Estimated RTT: {rtt.TotalMilliseconds} ms"); + _output.WriteLine($"Estimated RTT: {rtt}"); + if (rtt == TimeSpan.Zero) + { + _output.WriteLine("RTT is indeed zero!"); + } return rtt; } From 197d9623db81eaeecf8f0e0778ec312bde49543d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 3 Jun 2021 18:45:52 +0200 Subject: [PATCH 045/101] more benchmark tweaking --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index d4ad946184311b..a339cdcfbb2433 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -36,7 +36,8 @@ public LargeFileBenchmark(ITestOutputHelper output) private const double LengthMb = 500; //private const string BenchmarkServer = "10.194.114.94"; - private const string BenchmarkServer = "192.168.0.152"; + //private const string BenchmarkServer = "192.168.0.152"; + private const string BenchmarkServer = "127.0.0.1:5000"; [Theory] [InlineData(BenchmarkServer)] @@ -142,7 +143,8 @@ public async Task Download20_StaticRtt(string hostName) _listener.Enabled = true; var handler = new SocketsHttpHandler { - FakeRtt = await EstimateRttAsync(hostName) + FakeRtt = await EstimateRttAsync(hostName), + StreamWindowUpdateRatio = 4 }; await TestHandler("SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT", hostName, true, LengthMb, handler); @@ -217,9 +219,10 @@ private async Task EstimateRttAsync(string hostName) PingReply reply2 = await ping.SendPingAsync(addr); TimeSpan rtt = TimeSpan.FromMilliseconds(reply1.RoundtripTime + reply2.RoundtripTime) / 2; _output.WriteLine($"Estimated RTT: {rtt}"); - if (rtt == TimeSpan.Zero) + if (rtt < TimeSpan.FromMilliseconds(1)) { - _output.WriteLine("RTT is indeed zero!"); + _output.WriteLine("RTT < 1 ms, changing to 1 ms!"); + rtt = TimeSpan.FromMilliseconds(1); } return rtt; } From 85e70ca5e262f7f1c6b3337928b334e6a8049552 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 3 Jun 2021 19:10:44 +0200 Subject: [PATCH 046/101] utilize SETTINGS frame, calc min RTT , initial burst --- .../SocketsHttpHandler/Http2Connection.cs | 4 +- .../Http2StreamWindowManager.cs | 52 +++++++++++++------ 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index a5f4adbc1bdf3b..07a7552ba13a14 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -208,6 +208,7 @@ public async ValueTask SetupAsync() _outgoingBuffer.Commit(4); await _stream.WriteAsync(_outgoingBuffer.ActiveMemory).ConfigureAwait(false); + _rttEstimator?.OnInitialSettingsSent(); _outgoingBuffer.Discard(_outgoingBuffer.ActiveLength); _expectingSettingsAck = true; @@ -590,7 +591,7 @@ private void ProcessDataFrame(FrameHeader frameHeader) if (frameData.Length > 0) { - _rttEstimator?.Update(); + _rttEstimator?.OnDataReceived(); ExtendWindow(frameData.Length); } @@ -621,6 +622,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(); } else { diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index c62d5cc9f5e82e..1efb87a84655f3 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -88,9 +88,9 @@ public override void AdjustWindow(int bytesConsumed) int windowSizeIncrement = _delivered; TimeSpan currentTime = _stopwatch.Elapsed; - if (_connection._rttEstimator!.Rtt > TimeSpan.Zero) + if (_connection._rttEstimator!.MinRtt > TimeSpan.Zero) { - TimeSpan rtt = _connection._rttEstimator.Rtt; + TimeSpan rtt = _connection._rttEstimator.MinRtt; TimeSpan dt = currentTime - _lastWindowUpdate; if (_magic * _delivered * rtt.Ticks > StreamWindowThreshold * dt.Ticks) @@ -125,12 +125,12 @@ private class RttEstimator { private enum Status { - NotReady, + Init, Waiting, - PingSent + PingSent, } - private const double PingIntervalInSeconds = .5; + private const double PingIntervalInSeconds = 1; private static readonly long PingIntervalInTicks =(long)(PingIntervalInSeconds * Stopwatch.Frequency); private Http2Connection _connection; @@ -138,8 +138,9 @@ private enum Status private Status _status; private long _pingSentTimestamp; private long _pingCounter = -1; + private int _initialBurst = 4; - public TimeSpan Rtt { get; private set; } + public TimeSpan MinRtt { get; private set; } private readonly TimeSpan? _staticRtt; @@ -149,21 +150,36 @@ public RttEstimator(Http2Connection connection, TimeSpan? staticRtt) _staticRtt = staticRtt; if (_staticRtt.HasValue) { - Rtt = _staticRtt.Value; - _connection.TraceFlowControl($"Using static RTT: {Rtt.TotalMilliseconds} ms"); + MinRtt = _staticRtt.Value; + _connection.TraceFlowControl($"Using static RTT: {MinRtt.TotalMilliseconds} ms"); } } - internal void Update() + internal void OnInitialSettingsSent() + { + _connection.TraceFlowControl("Initial SETTINGS sent"); + _pingSentTimestamp = Stopwatch.GetTimestamp(); + } + + internal void OnInitialSettingsAckReceived() + { + _connection.TraceFlowControl("Initial SETTINGS ACK received"); + RefreshRtt(); + _status = Status.Waiting; + } + + internal void OnDataReceived() { if (_staticRtt.HasValue) return; - if (_status == Status.NotReady || _status == Status.Waiting) + if (_status == Status.Waiting) { long now = Stopwatch.GetTimestamp(); - - if (now - _pingSentTimestamp > PingIntervalInTicks) // also true if _status == NotReady + bool initial = Interlocked.Decrement(ref _initialBurst) >= 0; + if (initial || now - _pingSentTimestamp > PingIntervalInTicks) { + if (_initialBurst > 0) Interlocked.Decrement(ref _initialBurst); + // Send a PING long payload = Interlocked.Decrement(ref _pingCounter); _connection.LogExceptions(_connection.SendPingAsync(payload, isAck: false)); @@ -180,11 +196,17 @@ internal void OnPingAck(long payload) if (Interlocked.Read(ref _pingCounter) != payload) ThrowProtocolError(); + RefreshRtt(); + _status = Status.Waiting; + } + private void RefreshRtt() + { long elapsedTicks = Stopwatch.GetTimestamp() - _pingSentTimestamp; - Rtt = TimeSpan.FromSeconds(elapsedTicks / (double)Stopwatch.Frequency); - _connection.TraceFlowControl($"Updated RTT: {Rtt.TotalMilliseconds} ms"); - _status = Status.Waiting; + TimeSpan prevRtt = MinRtt == TimeSpan.Zero ? TimeSpan.MaxValue : MinRtt; + TimeSpan currentRtt = TimeSpan.FromSeconds(elapsedTicks / (double)Stopwatch.Frequency); + MinRtt = new TimeSpan(Math.Min(prevRtt.Ticks, currentRtt.Ticks)); + _connection.TraceFlowControl($"Updated MinRtt: {MinRtt.TotalMilliseconds} ms || prevRtt:{prevRtt.TotalMilliseconds} ms, currentRtt:{currentRtt.TotalMilliseconds} ms)"); } } } From 3b957979133c78558c95798dbd945604fd22dca0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 3 Jun 2021 18:06:56 +0000 Subject: [PATCH 047/101] benchmark tweak --- .../FunctionalTests/_LargeFileBenchmark.cs | 96 ++++++++++++++----- 1 file changed, 70 insertions(+), 26 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index a339cdcfbb2433..19e5b73a7eed08 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -34,10 +34,10 @@ public LargeFileBenchmark(ITestOutputHelper output) public void Dispose() => _listener?.Dispose(); - private const double LengthMb = 500; - //private const string BenchmarkServer = "10.194.114.94"; + private const double LengthMb = 100; + private const string BenchmarkServer = "10.194.114.94"; //private const string BenchmarkServer = "192.168.0.152"; - private const string BenchmarkServer = "127.0.0.1:5000"; + // private const string BenchmarkServer = "127.0.0.1:5000"; [Theory] [InlineData(BenchmarkServer)] @@ -86,6 +86,23 @@ public LargeFileBenchmark(ITestOutputHelper output) [InlineData(BenchmarkServer, 16384)] public Task Download20_SpecificWindow_Run4(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + + [Theory] + [InlineData(BenchmarkServer, 1024)] + [InlineData(BenchmarkServer, 2048)] + [InlineData(BenchmarkServer, 4096)] + [InlineData(BenchmarkServer, 8192)] + [InlineData(BenchmarkServer, 16384)] + public Task Download20_SpecificWindow_MegaBytes_Run1(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + + [Theory] + [InlineData(BenchmarkServer, 1024)] + [InlineData(BenchmarkServer, 2048)] + [InlineData(BenchmarkServer, 4096)] + [InlineData(BenchmarkServer, 8192)] + [InlineData(BenchmarkServer, 16384)] + public Task Download20_SpecificWindow_MegaBytes_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + [Theory] [InlineData(BenchmarkServer, 4096)] public Task Download20_SpecificWindow_4M_Run1(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); @@ -115,39 +132,66 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) [Theory] - [InlineData(BenchmarkServer)] - //[InlineData("10.194.114.94:5001")] - //[InlineData("10.194.114.94:5002")] - public async Task Download20_Dynamic_SingleStream_Run1(string hostName) - { - _listener.Enabled = true; - await TestHandler("SocketsHttpHandler HTTP 2.0 Dynamic single stream Run1", hostName, true, LengthMb); - } + //[InlineData(BenchmarkServer)] + [InlineData("10.194.114.94:5001", 8)] + [InlineData("10.194.114.94:5001", 4)] + [InlineData("10.194.114.94:5001", 2)] + [InlineData("10.194.114.94:5002", 8)] + [InlineData("10.194.114.94:5002", 4)] + [InlineData("10.194.114.94:5002", 2)] + public Task Download20_Dynamic_SingleStream_Run1(string hostName, int ratio) => Download20_Dynamic_SingleStream(hostName, ratio); [Theory] - [InlineData(BenchmarkServer)] - //[InlineData("10.194.114.94:5001")] - //[InlineData("10.194.114.94:5002")] - public async Task Download20_Dynamic_SingleStream_Run2(string hostName) + //[InlineData(BenchmarkServer)] + [InlineData("10.194.114.94:5001", 8)] + [InlineData("10.194.114.94:5001", 4)] + [InlineData("10.194.114.94:5001", 2)] + [InlineData("10.194.114.94:5002", 8)] + [InlineData("10.194.114.94:5002", 4)] + [InlineData("10.194.114.94:5002", 2)] + public Task Download20_Dynamic_SingleStream_Run2(string hostName, int ratio) => Download20_Dynamic_SingleStream(hostName, ratio); + + private async Task Download20_Dynamic_SingleStream(string hostName, int ratio) { _listener.Enabled = true; - await TestHandler("SocketsHttpHandler HTTP 2.0 Dynamic single stream Run2", hostName, true, LengthMb); + _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); + var handler = new SocketsHttpHandler() + { + StreamWindowUpdateRatio = ratio + }; + await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio}", hostName, true, LengthMb, handler); } [Theory] - [InlineData(BenchmarkServer)] - //[InlineData("10.194.114.94:5001")] - //[InlineData("10.194.114.94:5002")] - public async Task Download20_StaticRtt(string hostName) + //[InlineData(BenchmarkServer)] + [InlineData("10.194.114.94:5001", 8)] + [InlineData("10.194.114.94:5001", 4)] + [InlineData("10.194.114.94:5001", 2)] + [InlineData("10.194.114.94:5002", 8)] + [InlineData("10.194.114.94:5002", 4)] + [InlineData("10.194.114.94:5002", 2)] + public Task Download20_StaticRtt_Run1(string hostName, int ratio) => Download20_StaticRtt(hostName, ratio); + + [Theory] + //[InlineData(BenchmarkServer)] + [InlineData("10.194.114.94:5001", 8)] + [InlineData("10.194.114.94:5001", 4)] + [InlineData("10.194.114.94:5001", 2)] + [InlineData("10.194.114.94:5002", 8)] + [InlineData("10.194.114.94:5002", 4)] + [InlineData("10.194.114.94:5002", 2)] + public Task Download20_StaticRtt_Run2(string hostName, int ratio) => Download20_StaticRtt(hostName, ratio); + + public async Task Download20_StaticRtt(string hostName, int ratio) { _listener.Enabled = true; var handler = new SocketsHttpHandler { FakeRtt = await EstimateRttAsync(hostName), - StreamWindowUpdateRatio = 4 + StreamWindowUpdateRatio = ratio }; - await TestHandler("SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT", hostName, true, LengthMb, handler); + await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT | host:{hostName} ratio={ratio}", hostName, true, LengthMb, handler); } [Theory] @@ -177,8 +221,8 @@ public async Task Download20_Dynamic_MultiStream(string hostName) await Task.WhenAll(tasks); - long elapsedMs = sw.ElapsedMilliseconds; - _output.WriteLine($"{info}: completed in {elapsedMs} ms"); + double elapsedSec = sw.ElapsedMilliseconds * 0.001; + _output.WriteLine($"{info}: completed in {elapsedSec} sec"); } @@ -191,9 +235,9 @@ private async Task TestHandler(string info, string hostName, bool http2, double _output.WriteLine($"{info} / {lengthMb} MB from {message.RequestUri}"); Stopwatch sw = Stopwatch.StartNew(); var response = await client.SendAsync(message); - long elapsedMs = sw.ElapsedMilliseconds; - _output.WriteLine($"{info}: {response.StatusCode} in {elapsedMs} ms"); + double elapsedSec = sw.ElapsedMilliseconds * 0.001; + _output.WriteLine($"{info}: completed in {elapsedSec} sec"); } private async Task EstimateRttAsync(string hostName) From 9e2a68fe0ce3236f0cfd278515c5a75c668746f2 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 3 Jun 2021 20:07:19 +0200 Subject: [PATCH 048/101] if (_staticRtt.HasValue) return --- .../Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 1efb87a84655f3..42a2f1e6dc28cc 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -157,12 +157,14 @@ public RttEstimator(Http2Connection connection, TimeSpan? staticRtt) internal void OnInitialSettingsSent() { + if (_staticRtt.HasValue) return; _connection.TraceFlowControl("Initial SETTINGS sent"); _pingSentTimestamp = Stopwatch.GetTimestamp(); } internal void OnInitialSettingsAckReceived() { + if (_staticRtt.HasValue) return; _connection.TraceFlowControl("Initial SETTINGS ACK received"); RefreshRtt(); _status = Status.Waiting; From 03e154f1ccccb1f0cc31ff50aa22ba61a62c04f5 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 7 Jun 2021 12:09:33 +0000 Subject: [PATCH 049/101] better filter --- .../System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 19e5b73a7eed08..09db806a90ac0d 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -185,6 +185,7 @@ private async Task Download20_Dynamic_SingleStream(string hostName, int ratio) public async Task Download20_StaticRtt(string hostName, int ratio) { _listener.Enabled = true; + _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); var handler = new SocketsHttpHandler { FakeRtt = await EstimateRttAsync(hostName), From c01860b266991762de5b120e498d0e3649f9f2cd Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 7 Jun 2021 14:21:11 +0200 Subject: [PATCH 050/101] trace time to reach 8M window --- .../SocketsHttpHandler/Http2StreamWindowManager.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 42a2f1e6dc28cc..5870f18f875972 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Net.Sockets; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -68,6 +69,8 @@ private class DynamicHttp2StreamWindowManager : Http2StreamWindowManager private TimeSpan _lastWindowUpdate; private long _magic = 1; + private readonly TimeSpan _start; + private readonly StringBuilder _msgBuilder = new StringBuilder(); public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream stream) : base(connection, stream) @@ -75,6 +78,7 @@ public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream s _magic = connection._pool.Settings._streamWindowMagicMultiplier; _stream.TraceFlowControl($" magic:{_magic} | Stopwatch: IsHighResolution={Stopwatch.IsHighResolution}, Frequency={Stopwatch.Frequency}"); _lastWindowUpdate = _stopwatch.Elapsed; + _start = _lastWindowUpdate; } public override void AdjustWindow(int bytesConsumed) @@ -98,7 +102,15 @@ public override void AdjustWindow(int bytesConsumed) windowSizeIncrement += _streamWindowSize; _streamWindowSize *= 2; - _stream.TraceFlowControl($"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold} \n | {GetDiagnostics()}"); + // Trace info: + _msgBuilder.Clear(); + _msgBuilder.Append($"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); + if (_streamWindowSize >= 8 * 1024 * 1024) + { + _msgBuilder.Append($"reached 8M window in {(currentTime - _start).TotalSeconds} sec"); + } + _msgBuilder.Append(Environment.NewLine); + _stream.TraceFlowControl(_msgBuilder.ToString()); } else { From 0683044cfc316ad6b14a30e4364592fadb44311e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 7 Jun 2021 14:29:08 +0200 Subject: [PATCH 051/101] do not print "reached 8M window" multiple times --- .../Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 5870f18f875972..397d681e48f959 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -69,7 +69,7 @@ private class DynamicHttp2StreamWindowManager : Http2StreamWindowManager private TimeSpan _lastWindowUpdate; private long _magic = 1; - private readonly TimeSpan _start; + private TimeSpan _start; private readonly StringBuilder _msgBuilder = new StringBuilder(); public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream stream) @@ -105,9 +105,10 @@ public override void AdjustWindow(int bytesConsumed) // Trace info: _msgBuilder.Clear(); _msgBuilder.Append($"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); - if (_streamWindowSize >= 8 * 1024 * 1024) + if (_streamWindowSize >= 8 * 1024 * 1024 && _start != TimeSpan.Zero) { _msgBuilder.Append($"reached 8M window in {(currentTime - _start).TotalSeconds} sec"); + _start = TimeSpan.Zero; } _msgBuilder.Append(Environment.NewLine); _stream.TraceFlowControl(_msgBuilder.ToString()); From ee375ca682cd2ff5f56f9c2c059dae642622bd55 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 7 Jun 2021 14:40:45 +0200 Subject: [PATCH 052/101] meh --- .../SocketsHttpHandler/Http2StreamWindowManager.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 397d681e48f959..7ead47fb7f695d 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -69,8 +69,7 @@ private class DynamicHttp2StreamWindowManager : Http2StreamWindowManager private TimeSpan _lastWindowUpdate; private long _magic = 1; - private TimeSpan _start; - private readonly StringBuilder _msgBuilder = new StringBuilder(); + private readonly TimeSpan _start; public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream stream) : base(connection, stream) @@ -103,15 +102,8 @@ public override void AdjustWindow(int bytesConsumed) _streamWindowSize *= 2; // Trace info: - _msgBuilder.Clear(); - _msgBuilder.Append($"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); - if (_streamWindowSize >= 8 * 1024 * 1024 && _start != TimeSpan.Zero) - { - _msgBuilder.Append($"reached 8M window in {(currentTime - _start).TotalSeconds} sec"); - _start = TimeSpan.Zero; - } - _msgBuilder.Append(Environment.NewLine); - _stream.TraceFlowControl(_msgBuilder.ToString()); + _stream.TraceFlowControl( + $"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold} | S-T={(currentTime - _start).TotalSeconds} sec {Environment.NewLine}"); } else { From ca19ab408b2872d2b2f0559196e62e85e899e26d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 7 Jun 2021 15:21:15 +0200 Subject: [PATCH 053/101] SpecificWindow_KiloBytes --- .../FunctionalTests/_LargeFileBenchmark.cs | 71 +++++-------------- 1 file changed, 16 insertions(+), 55 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 09db806a90ac0d..d1fafa8b4499a5 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -35,8 +35,8 @@ public LargeFileBenchmark(ITestOutputHelper output) public void Dispose() => _listener?.Dispose(); private const double LengthMb = 100; - private const string BenchmarkServer = "10.194.114.94"; - //private const string BenchmarkServer = "192.168.0.152"; + //private const string BenchmarkServer = "10.194.114.94"; + private const string BenchmarkServer = "192.168.0.152"; // private const string BenchmarkServer = "127.0.0.1:5000"; [Theory] @@ -47,46 +47,6 @@ public LargeFileBenchmark(ITestOutputHelper output) [InlineData(BenchmarkServer)] public Task Download11_Run2(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run2", hostName, false, LengthMb); - // [Theory] - // [InlineData("10.194.114.94")] - // public Task Download20_DefaultWindow(string hostName) => TestHandler("SocketsHttpHandler HTTP 2.0", hostName, true, LengthMb); - - [Theory] - [InlineData(BenchmarkServer, 64)] - public Task Download20_SpecificWindow_Run0(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - [Theory] - [InlineData(BenchmarkServer, 256)] - [InlineData(BenchmarkServer, 384)] - public Task Download20_SpecificWindow_Run1(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - [Theory] - [InlineData(BenchmarkServer, 512)] - [InlineData(BenchmarkServer, 768)] - public Task Download20_SpecificWindow_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - [Theory] - [InlineData(BenchmarkServer, 1024)] - [InlineData(BenchmarkServer, 1280)] - [InlineData(BenchmarkServer, 1536)] - public Task Download20_SpecificWindow_Run3(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - [Theory] - [InlineData(BenchmarkServer, 1792)] - [InlineData(BenchmarkServer, 2048)] - [InlineData(BenchmarkServer, 2560)] - [InlineData(BenchmarkServer, 3072)] - [InlineData(BenchmarkServer, 4096)] - [InlineData(BenchmarkServer, 5120)] - [InlineData(BenchmarkServer, 6144)] - [InlineData(BenchmarkServer, 8192)] - [InlineData(BenchmarkServer, 10240)] - [InlineData(BenchmarkServer, 12288)] - [InlineData(BenchmarkServer, 14336)] - [InlineData(BenchmarkServer, 16384)] - public Task Download20_SpecificWindow_Run4(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - [Theory] [InlineData(BenchmarkServer, 1024)] [InlineData(BenchmarkServer, 2048)] @@ -104,21 +64,22 @@ public LargeFileBenchmark(ITestOutputHelper output) public Task Download20_SpecificWindow_MegaBytes_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); [Theory] - [InlineData(BenchmarkServer, 4096)] - public Task Download20_SpecificWindow_4M_Run1(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - [Theory] - [InlineData(BenchmarkServer, 4096)] - public Task Download20_SpecificWindow_4M_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - [Theory] - [InlineData(BenchmarkServer, 16384)] - public Task Download20_SpecificWindow_16M_Run1(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + [InlineData(BenchmarkServer, 64)] + [InlineData(BenchmarkServer, 128)] + [InlineData(BenchmarkServer, 256)] + [InlineData(BenchmarkServer, 512)] + [InlineData(BenchmarkServer, 1024)] + [InlineData(BenchmarkServer, 2048)] + public Task Download20_SpecificWindow_KiloBytes_Run1(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); [Theory] - [InlineData(BenchmarkServer, 16384)] - public Task Download20_SpecificWindow_16M_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - + [InlineData(BenchmarkServer, 64)] + [InlineData(BenchmarkServer, 128)] + [InlineData(BenchmarkServer, 256)] + [InlineData(BenchmarkServer, 512)] + [InlineData(BenchmarkServer, 1024)] + [InlineData(BenchmarkServer, 2048)] + public Task Download20_SpecificWindow_KiloBytes_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) { From 56700ddec80e5df38adaec1be957048606db4729 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 7 Jun 2021 16:18:46 +0200 Subject: [PATCH 054/101] cleanup Download_Dynamic_* data --- .../FunctionalTests/_LargeFileBenchmark.cs | 71 ++++++++----------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index d1fafa8b4499a5..1297aedd5f52b6 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -34,9 +34,10 @@ public LargeFileBenchmark(ITestOutputHelper output) public void Dispose() => _listener?.Dispose(); - private const double LengthMb = 100; + private const double LengthMb = 400; //private const string BenchmarkServer = "10.194.114.94"; private const string BenchmarkServer = "192.168.0.152"; + private const string BenchmarkServerGo = "192.168.0.152:5002"; // private const string BenchmarkServer = "127.0.0.1:5000"; [Theory] @@ -64,21 +65,23 @@ public LargeFileBenchmark(ITestOutputHelper output) public Task Download20_SpecificWindow_MegaBytes_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); [Theory] - [InlineData(BenchmarkServer, 64)] - [InlineData(BenchmarkServer, 128)] - [InlineData(BenchmarkServer, 256)] - [InlineData(BenchmarkServer, 512)] - [InlineData(BenchmarkServer, 1024)] - [InlineData(BenchmarkServer, 2048)] + //[InlineData(BenchmarkServer, 64)] + //[InlineData(BenchmarkServer, 128)] + //[InlineData(BenchmarkServer, 256)] + //[InlineData(BenchmarkServer, 512)] + //[InlineData(BenchmarkServer, 1024)] + //[InlineData(BenchmarkServer, 2048)] + [InlineData(BenchmarkServer, 4096)] public Task Download20_SpecificWindow_KiloBytes_Run1(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); [Theory] - [InlineData(BenchmarkServer, 64)] - [InlineData(BenchmarkServer, 128)] - [InlineData(BenchmarkServer, 256)] - [InlineData(BenchmarkServer, 512)] - [InlineData(BenchmarkServer, 1024)] - [InlineData(BenchmarkServer, 2048)] + //[InlineData(BenchmarkServer, 64)] + //[InlineData(BenchmarkServer, 128)] + //[InlineData(BenchmarkServer, 256)] + //[InlineData(BenchmarkServer, 512)] + //[InlineData(BenchmarkServer, 1024)] + //[InlineData(BenchmarkServer, 2048)] + [InlineData(BenchmarkServer, 4096)] public Task Download20_SpecificWindow_KiloBytes_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) @@ -91,25 +94,23 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler); } + public static TheoryData Download20_ServerAndRatio = new TheoryData + { + { BenchmarkServer, 8 }, + { BenchmarkServer, 4 }, + { BenchmarkServer, 2 }, + { BenchmarkServerGo, 8 }, + { BenchmarkServerGo, 4 }, + { BenchmarkServerGo, 2 }, + }; + [Theory] - //[InlineData(BenchmarkServer)] - [InlineData("10.194.114.94:5001", 8)] - [InlineData("10.194.114.94:5001", 4)] - [InlineData("10.194.114.94:5001", 2)] - [InlineData("10.194.114.94:5002", 8)] - [InlineData("10.194.114.94:5002", 4)] - [InlineData("10.194.114.94:5002", 2)] + [MemberData(nameof(Download20_ServerAndRatio))] public Task Download20_Dynamic_SingleStream_Run1(string hostName, int ratio) => Download20_Dynamic_SingleStream(hostName, ratio); [Theory] - //[InlineData(BenchmarkServer)] - [InlineData("10.194.114.94:5001", 8)] - [InlineData("10.194.114.94:5001", 4)] - [InlineData("10.194.114.94:5001", 2)] - [InlineData("10.194.114.94:5002", 8)] - [InlineData("10.194.114.94:5002", 4)] - [InlineData("10.194.114.94:5002", 2)] + [MemberData(nameof(Download20_ServerAndRatio))] public Task Download20_Dynamic_SingleStream_Run2(string hostName, int ratio) => Download20_Dynamic_SingleStream(hostName, ratio); private async Task Download20_Dynamic_SingleStream(string hostName, int ratio) @@ -124,23 +125,11 @@ private async Task Download20_Dynamic_SingleStream(string hostName, int ratio) } [Theory] - //[InlineData(BenchmarkServer)] - [InlineData("10.194.114.94:5001", 8)] - [InlineData("10.194.114.94:5001", 4)] - [InlineData("10.194.114.94:5001", 2)] - [InlineData("10.194.114.94:5002", 8)] - [InlineData("10.194.114.94:5002", 4)] - [InlineData("10.194.114.94:5002", 2)] + [MemberData(nameof(Download20_ServerAndRatio))] public Task Download20_StaticRtt_Run1(string hostName, int ratio) => Download20_StaticRtt(hostName, ratio); [Theory] - //[InlineData(BenchmarkServer)] - [InlineData("10.194.114.94:5001", 8)] - [InlineData("10.194.114.94:5001", 4)] - [InlineData("10.194.114.94:5001", 2)] - [InlineData("10.194.114.94:5002", 8)] - [InlineData("10.194.114.94:5002", 4)] - [InlineData("10.194.114.94:5002", 2)] + [MemberData(nameof(Download20_ServerAndRatio))] public Task Download20_StaticRtt_Run2(string hostName, int ratio) => Download20_StaticRtt(hostName, ratio); public async Task Download20_StaticRtt(string hostName, int ratio) From 66914dbbb5afc6042876428f8c4fd1f04bb743cc Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 7 Jun 2021 18:07:16 +0200 Subject: [PATCH 055/101] repurpose magic factor --- .../System.Net.Http/ref/System.Net.Http.cs | 2 +- .../BrowserHttpHandler/SocketsHttpHandler.cs | 2 +- .../Http2StreamWindowManager.cs | 5 +-- .../HttpConnectionSettings.cs | 2 +- .../SocketsHttpHandler/SocketsHttpHandler.cs | 2 +- .../FunctionalTests/_LargeFileBenchmark.cs | 40 +++++++++++-------- 6 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/libraries/System.Net.Http/ref/System.Net.Http.cs b/src/libraries/System.Net.Http/ref/System.Net.Http.cs index dff3e7ea5a31f2..071dfc4765de19 100644 --- a/src/libraries/System.Net.Http/ref/System.Net.Http.cs +++ b/src/libraries/System.Net.Http/ref/System.Net.Http.cs @@ -358,7 +358,7 @@ public SocketsHttpHandler() { } public int InitialStreamWindowSize { get { throw null; } set { } } public bool EnableDynamicHttp2StreamWindowSizing { get { throw null; } set { } } public int StreamWindowUpdateRatio { get { throw null; } set { } } - public int StreamWindowMagicMultiplier { get { throw null; } set { } } + public double 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 { } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs index 27e1ec48701608..3f511d2f038b13 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs @@ -49,7 +49,7 @@ public int StreamWindowUpdateRatio set => throw new PlatformNotSupportedException(); } - public int StreamWindowMagicMultiplier + public double StreamWindowMagicMultiplier { get => throw new PlatformNotSupportedException(); set => throw new PlatformNotSupportedException(); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 7ead47fb7f695d..487007c4e94ba2 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -68,7 +68,7 @@ private class DynamicHttp2StreamWindowManager : Http2StreamWindowManager private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private TimeSpan _lastWindowUpdate; - private long _magic = 1; + private double _magic = 1; private readonly TimeSpan _start; public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream stream) @@ -96,12 +96,11 @@ public override void AdjustWindow(int bytesConsumed) TimeSpan rtt = _connection._rttEstimator.MinRtt; TimeSpan dt = currentTime - _lastWindowUpdate; - if (_magic * _delivered * rtt.Ticks > StreamWindowThreshold * dt.Ticks) + if (_magic * _delivered * rtt.Ticks > (double)StreamWindowThreshold * dt.Ticks) { windowSizeIncrement += _streamWindowSize; _streamWindowSize *= 2; - // Trace info: _stream.TraceFlowControl( $"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold} | S-T={(currentTime - _start).TotalSeconds} sec {Environment.NewLine}"); } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index c3950f22839083..bd0a251325bc5b 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -72,7 +72,7 @@ internal sealed class HttpConnectionSettings internal int _initialStreamWindowSize = 65535; internal int _streamWindowUpdateRatio = 8; - internal int _streamWindowMagicMultiplier = 1; + internal double _streamWindowMagicMultiplier = 1; public HttpConnectionSettings() { diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index 270fc2284b798d..ceb99c0cb3b8f5 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -79,7 +79,7 @@ public int StreamWindowUpdateRatio set => _settings._streamWindowUpdateRatio = value; } - public int StreamWindowMagicMultiplier + public double StreamWindowMagicMultiplier { get => _settings._streamWindowMagicMultiplier; set => _settings._streamWindowMagicMultiplier = value; diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 1297aedd5f52b6..9640cf15736863 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -94,55 +94,61 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler); } - public static TheoryData Download20_ServerAndRatio = new TheoryData + public static TheoryData Download20_ServerAndRatio = new TheoryData { - { BenchmarkServer, 8 }, - { BenchmarkServer, 4 }, - { BenchmarkServer, 2 }, - { BenchmarkServerGo, 8 }, - { BenchmarkServerGo, 4 }, - { BenchmarkServerGo, 2 }, + { BenchmarkServer, 8, 0.5 }, + { BenchmarkServer, 8, 0.25 }, + { BenchmarkServer, 8, 0.125 }, + { BenchmarkServer, 4, 0.5 }, + { BenchmarkServer, 4, 0.25 }, + { BenchmarkServerGo, 8, 0.5 }, + { BenchmarkServerGo, 8, 0.25 }, + { BenchmarkServerGo, 8, 0.125 }, + { BenchmarkServerGo, 4, 0.5 }, + { BenchmarkServerGo, 4, 0.25 }, }; [Theory] [MemberData(nameof(Download20_ServerAndRatio))] - public Task Download20_Dynamic_SingleStream_Run1(string hostName, int ratio) => Download20_Dynamic_SingleStream(hostName, ratio); + public Task Download20_Dynamic_SingleStream_Run1(string hostName, int ratio, double magic) => Download20_Dynamic_SingleStream(hostName, ratio, magic); [Theory] [MemberData(nameof(Download20_ServerAndRatio))] - public Task Download20_Dynamic_SingleStream_Run2(string hostName, int ratio) => Download20_Dynamic_SingleStream(hostName, ratio); + public Task Download20_Dynamic_SingleStream_Run2(string hostName, int ratio, double magic) => Download20_Dynamic_SingleStream(hostName, ratio, magic); - private async Task Download20_Dynamic_SingleStream(string hostName, int ratio) + private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, double magic) { _listener.Enabled = true; _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); var handler = new SocketsHttpHandler() { - StreamWindowUpdateRatio = ratio + StreamWindowUpdateRatio = ratio, + StreamWindowMagicMultiplier = magic }; - await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio}", hostName, true, LengthMb, handler); + await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio} magic={magic}", hostName, true, LengthMb, handler); } [Theory] [MemberData(nameof(Download20_ServerAndRatio))] - public Task Download20_StaticRtt_Run1(string hostName, int ratio) => Download20_StaticRtt(hostName, ratio); + public Task Download20_StaticRtt_Run1(string hostName, int ratio, double magic) => Download20_StaticRtt(hostName, ratio, magic); [Theory] [MemberData(nameof(Download20_ServerAndRatio))] - public Task Download20_StaticRtt_Run2(string hostName, int ratio) => Download20_StaticRtt(hostName, ratio); + public Task Download20_StaticRtt_Run2(string hostName, int ratio, double magic) => Download20_StaticRtt(hostName, ratio, magic); - public async Task Download20_StaticRtt(string hostName, int ratio) + public async Task Download20_StaticRtt(string hostName, int ratio, double magic) { _listener.Enabled = true; _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); var handler = new SocketsHttpHandler { FakeRtt = await EstimateRttAsync(hostName), - StreamWindowUpdateRatio = ratio + StreamWindowUpdateRatio = ratio, + StreamWindowMagicMultiplier = magic }; - await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT | host:{hostName} ratio={ratio}", hostName, true, LengthMb, handler); + await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT | host:{hostName} ratio={ratio} magic={magic}", hostName, true, LengthMb, handler); } [Theory] From 144c47fd778722d65d33d1778261654dd3782a91 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 10 Jun 2021 16:58:29 +0200 Subject: [PATCH 056/101] TestHandler: bind to custom local endpoint --- .../FunctionalTests/_LargeFileBenchmark.cs | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 9640cf15736863..a4c307712d2ca0 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -35,11 +35,14 @@ public LargeFileBenchmark(ITestOutputHelper output) public void Dispose() => _listener?.Dispose(); private const double LengthMb = 400; + //private const string BenchmarkServer = "10.194.114.94"; private const string BenchmarkServer = "192.168.0.152"; private const string BenchmarkServerGo = "192.168.0.152:5002"; // private const string BenchmarkServer = "127.0.0.1:5000"; + private static readonly IPEndPoint LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0); + [Theory] [InlineData(BenchmarkServer)] public Task Download11_Run1(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run1", hostName, false, LengthMb); @@ -89,7 +92,7 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) SocketsHttpHandler handler = new SocketsHttpHandler() { EnableDynamicHttp2StreamWindowSizing = false, - InitialStreamWindowSize = initialWindowKbytes * 1024 + InitialStreamWindowSize = initialWindowKbytes * 1024mó }; return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler); } @@ -101,11 +104,11 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) { BenchmarkServer, 8, 0.125 }, { BenchmarkServer, 4, 0.5 }, { BenchmarkServer, 4, 0.25 }, - { BenchmarkServerGo, 8, 0.5 }, - { BenchmarkServerGo, 8, 0.25 }, - { BenchmarkServerGo, 8, 0.125 }, - { BenchmarkServerGo, 4, 0.5 }, - { BenchmarkServerGo, 4, 0.25 }, + //{ BenchmarkServerGo, 8, 0.5 }, + //{ BenchmarkServerGo, 8, 0.25 }, + //{ BenchmarkServerGo, 8, 0.125 }, + //{ BenchmarkServerGo, 4, 0.5 }, + //{ BenchmarkServerGo, 4, 0.25 }, }; @@ -183,9 +186,11 @@ public async Task Download20_Dynamic_MultiStream(string hostName) } - private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, HttpMessageHandler handler = null) + private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler = null) { handler ??= new SocketsHttpHandler(); + handler.ConnectCallback = CustomConnect; + using var client = new HttpClient(handler, true); client.Timeout = TimeSpan.FromMinutes(2); var message = GenerateRequestMessage(hostName, http2, lengthMb); @@ -197,6 +202,26 @@ private async Task TestHandler(string info, string hostName, bool http2, double _output.WriteLine($"{info}: completed in {elapsedSec} sec"); } + private static async ValueTask CustomConnect(SocketsHttpConnectionContext ctx, CancellationToken cancellationToken) + { + Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) + { + NoDelay = true + }; + socket.Bind(LocalEndPoint); + + try + { + await socket.ConnectAsync(ctx.DnsEndPoint, cancellationToken).ConfigureAwait(false); + return new NetworkStream(socket, ownsSocket: true); + } + catch + { + socket.Dispose(); + throw; + } + } + private async Task EstimateRttAsync(string hostName) { int sep = hostName.IndexOf(':'); From d24d3324b7e706df5b1b5eb7d722a1db56272317 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 10 Jun 2021 17:03:31 +0200 Subject: [PATCH 057/101] duo --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index a4c307712d2ca0..176a03ddd93a0c 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -37,11 +37,13 @@ public LargeFileBenchmark(ITestOutputHelper output) private const double LengthMb = 400; //private const string BenchmarkServer = "10.194.114.94"; + //private const string BenchmarkServer = "169.254.132.170"; // duo1 private const string BenchmarkServer = "192.168.0.152"; private const string BenchmarkServerGo = "192.168.0.152:5002"; // private const string BenchmarkServer = "127.0.0.1:5000"; - private static readonly IPEndPoint LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0); + //private static readonly IPAddress LocalAddress = IPAddress.Parse("169.254.59.132"); // duo2 + private static readonly IPAddress LocalAddress = IPAddress.Loopback; [Theory] [InlineData(BenchmarkServer)] From 13a532280099795c9b84e193f7779b7e297e55a1 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 10 Jun 2021 17:06:42 +0200 Subject: [PATCH 058/101] meh --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 176a03ddd93a0c..1ae4bec3bd87e6 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -94,7 +94,7 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) SocketsHttpHandler handler = new SocketsHttpHandler() { EnableDynamicHttp2StreamWindowSizing = false, - InitialStreamWindowSize = initialWindowKbytes * 1024mó + InitialStreamWindowSize = initialWindowKbytes * 1024 }; return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler); } From fe053dfc72b6b1cc2a0eea85b9ccc4ed00298791 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 10 Jun 2021 17:15:49 +0200 Subject: [PATCH 059/101] rrrrr --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 1ae4bec3bd87e6..a88edc7a38c3df 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -210,7 +210,7 @@ private static async ValueTask CustomConnect(SocketsHttpConnectionContex { NoDelay = true }; - socket.Bind(LocalEndPoint); + socket.Bind(new IPEndPoint(LocalAddress, 0)); try { From 763f36887d55fb50b376d5e14d573bcbf3f005bc Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 10 Jun 2021 17:23:52 +0200 Subject: [PATCH 060/101] TestRunCount --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index a88edc7a38c3df..73f54bce121f93 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -35,6 +35,7 @@ public LargeFileBenchmark(ITestOutputHelper output) public void Dispose() => _listener?.Dispose(); private const double LengthMb = 400; + private const int TestRunCount = 1; //private const string BenchmarkServer = "10.194.114.94"; //private const string BenchmarkServer = "169.254.132.170"; // duo1 @@ -187,8 +188,16 @@ public async Task Download20_Dynamic_MultiStream(string hostName) _output.WriteLine($"{info}: completed in {elapsedSec} sec"); } - private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler = null) + { + for (int i = 0; i < TestRunCount; i++) + { + _output.WriteLine($"############ run {i} ############"); + await TestHandlerCore(info, hostName, http2, lengthMb, handler); + } + } + + private async Task TestHandlerCore(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler = null) { handler ??= new SocketsHttpHandler(); handler.ConnectCallback = CustomConnect; From fbdfc29d79563dad4e2426a47a5c51e4d61c7f3d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 10 Jun 2021 19:04:24 +0200 Subject: [PATCH 061/101] report to csv --- .../FunctionalTests/_LargeFileBenchmark.cs | 112 ++++++++++-------- 1 file changed, 60 insertions(+), 52 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 73f54bce121f93..347916853e3c7b 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -35,7 +35,7 @@ public LargeFileBenchmark(ITestOutputHelper output) public void Dispose() => _listener?.Dispose(); private const double LengthMb = 400; - private const int TestRunCount = 1; + private const int TestRunCount = 5; //private const string BenchmarkServer = "10.194.114.94"; //private const string BenchmarkServer = "169.254.132.170"; // duo1 @@ -44,15 +44,14 @@ public LargeFileBenchmark(ITestOutputHelper output) // private const string BenchmarkServer = "127.0.0.1:5000"; //private static readonly IPAddress LocalAddress = IPAddress.Parse("169.254.59.132"); // duo2 - private static readonly IPAddress LocalAddress = IPAddress.Loopback; + private static readonly IPAddress LocalAddress = null; - [Theory] - [InlineData(BenchmarkServer)] - public Task Download11_Run1(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run1", hostName, false, LengthMb); + //private const string ReportDir = @"c:\_dev\WindowBenchmark"; + private const string ReportDir = @"C:\Users\anfirszo\dev\dotnet\6.0\runtime\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; [Theory] [InlineData(BenchmarkServer)] - public Task Download11_Run2(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run2", hostName, false, LengthMb); + public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run1", hostName, false, LengthMb); [Theory] [InlineData(BenchmarkServer, 1024)] @@ -60,35 +59,17 @@ public LargeFileBenchmark(ITestOutputHelper output) [InlineData(BenchmarkServer, 4096)] [InlineData(BenchmarkServer, 8192)] [InlineData(BenchmarkServer, 16384)] - public Task Download20_SpecificWindow_MegaBytes_Run1(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + public Task Download20_SpecificWindow_MegaBytes(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); [Theory] + [InlineData(BenchmarkServer, 64)] + [InlineData(BenchmarkServer, 128)] + [InlineData(BenchmarkServer, 256)] + [InlineData(BenchmarkServer, 512)] [InlineData(BenchmarkServer, 1024)] [InlineData(BenchmarkServer, 2048)] [InlineData(BenchmarkServer, 4096)] - [InlineData(BenchmarkServer, 8192)] - [InlineData(BenchmarkServer, 16384)] - public Task Download20_SpecificWindow_MegaBytes_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - [Theory] - //[InlineData(BenchmarkServer, 64)] - //[InlineData(BenchmarkServer, 128)] - //[InlineData(BenchmarkServer, 256)] - //[InlineData(BenchmarkServer, 512)] - //[InlineData(BenchmarkServer, 1024)] - //[InlineData(BenchmarkServer, 2048)] - [InlineData(BenchmarkServer, 4096)] - public Task Download20_SpecificWindow_KiloBytes_Run1(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - [Theory] - //[InlineData(BenchmarkServer, 64)] - //[InlineData(BenchmarkServer, 128)] - //[InlineData(BenchmarkServer, 256)] - //[InlineData(BenchmarkServer, 512)] - //[InlineData(BenchmarkServer, 1024)] - //[InlineData(BenchmarkServer, 2048)] - [InlineData(BenchmarkServer, 4096)] - public Task Download20_SpecificWindow_KiloBytes_Run2(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + public Task Download20_SpecificWindow_KiloBytes(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) { @@ -97,7 +78,8 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) EnableDynamicHttp2StreamWindowSizing = false, InitialStreamWindowSize = initialWindowKbytes * 1024 }; - return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler); + string details = $"SpecificWindow({initialWindowKbytes})"; + return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler, details); } public static TheoryData Download20_ServerAndRatio = new TheoryData @@ -117,12 +99,6 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) [Theory] [MemberData(nameof(Download20_ServerAndRatio))] - public Task Download20_Dynamic_SingleStream_Run1(string hostName, int ratio, double magic) => Download20_Dynamic_SingleStream(hostName, ratio, magic); - - [Theory] - [MemberData(nameof(Download20_ServerAndRatio))] - public Task Download20_Dynamic_SingleStream_Run2(string hostName, int ratio, double magic) => Download20_Dynamic_SingleStream(hostName, ratio, magic); - private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, double magic) { _listener.Enabled = true; @@ -137,12 +113,6 @@ private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, d [Theory] [MemberData(nameof(Download20_ServerAndRatio))] - public Task Download20_StaticRtt_Run1(string hostName, int ratio, double magic) => Download20_StaticRtt(hostName, ratio, magic); - - [Theory] - [MemberData(nameof(Download20_ServerAndRatio))] - public Task Download20_StaticRtt_Run2(string hostName, int ratio, double magic) => Download20_StaticRtt(hostName, ratio, magic); - public async Task Download20_StaticRtt(string hostName, int ratio, double magic) { _listener.Enabled = true; @@ -154,7 +124,8 @@ public async Task Download20_StaticRtt(string hostName, int ratio, double magic) StreamWindowMagicMultiplier = magic }; - await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT | host:{hostName} ratio={ratio} magic={magic}", hostName, true, LengthMb, handler); + string details = $"StaticRtt_R({ratio})_M({magic})"; + await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT | host:{hostName} ratio={ratio} magic={magic}", hostName, true, LengthMb, handler, details); } [Theory] @@ -188,22 +159,35 @@ public async Task Download20_Dynamic_MultiStream(string hostName) _output.WriteLine($"{info}: completed in {elapsedSec} sec"); } - private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler = null) + private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler = null, string details = "") { + handler ??= new SocketsHttpHandler(); + + if (LocalAddress != null) handler.ConnectCallback = CustomConnect; + + string reportFileName = CreateOutputFile(details); + _output.WriteLine("REPORT: " + reportFileName); + using StreamWriter report = new StreamWriter(reportFileName); + for (int i = 0; i < TestRunCount; i++) { _output.WriteLine($"############ run {i} ############"); - await TestHandlerCore(info, hostName, http2, lengthMb, handler); + await TestHandlerCore(info, hostName, http2, lengthMb, handler, report); } + handler.Dispose(); } - private async Task TestHandlerCore(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler = null) + private static string CreateOutputFile(string details) { - handler ??= new SocketsHttpHandler(); - handler.ConnectCallback = CustomConnect; + if (!Directory.Exists(ReportDir)) Directory.CreateDirectory(ReportDir); + return Path.Combine(ReportDir, $"report_{Environment.TickCount64}_{details}.csv"); + } - using var client = new HttpClient(handler, true); - client.Timeout = TimeSpan.FromMinutes(2); + private async Task TestHandlerCore(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler, StreamWriter report) + { + _listener.Log2.Clear(); + using var client = new HttpClient(handler, false); + client.Timeout = TimeSpan.FromMinutes(3); var message = GenerateRequestMessage(hostName, http2, lengthMb); _output.WriteLine($"{info} / {lengthMb} MB from {message.RequestUri}"); Stopwatch sw = Stopwatch.StartNew(); @@ -211,6 +195,23 @@ private async Task TestHandlerCore(string info, string hostName, bool http2, dou double elapsedSec = sw.ElapsedMilliseconds * 0.001; _output.WriteLine($"{info}: completed in {elapsedSec} sec"); + double window = GetStreamWindowSizeInMegabytes(); + report.WriteLine($"{elapsedSec}, {window}"); + } + + private double GetStreamWindowSizeInMegabytes() + { + const string Prefix = "Updated StreamWindowSize: "; + string log = _listener.Log2.ToString(); + + int idx = log.LastIndexOf(Prefix); + if (idx < 0) return 0; + ReadOnlySpan text = log.AsSpan().Slice(idx + Prefix.Length); + text = text.Slice(0, text.IndexOf(',')); + + double size = int.Parse(text); + double sizeMb = size / 1024 / 1024; + return Math.Round(sizeMb, 3); } private static async ValueTask CustomConnect(SocketsHttpConnectionContext ctx, CancellationToken cancellationToken) @@ -299,12 +300,15 @@ public sealed class LogHttpEventListener : EventListener private CancellationTokenSource _stopProcessing; private ITestOutputHelper _log; + public StringBuilder Log2 { get; } + public LogHttpEventListener(ITestOutputHelper log) { _log = log; _messagesChannel = Channel.CreateUnbounded(); _processMessages = ProcessMessagesAsync(); _stopProcessing = new CancellationTokenSource(); + Log2 = new StringBuilder(1024 * 1024); } public bool Enabled { get; set; } @@ -326,7 +330,11 @@ private async Task ProcessMessagesAsync() { await foreach (string message in _messagesChannel.Reader.ReadAllAsync(_stopProcessing.Token)) { - if (Filter(message)) _log.WriteLine(message); + if (Filter(message)) + { + _log.WriteLine(message); + Log2.AppendLine(message); + } } } catch (OperationCanceledException) From 00fb8a90bf8e6a1f3dea63371fdb4f438e4ae350 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 10 Jun 2021 19:08:59 +0200 Subject: [PATCH 062/101] round elapsedSec --- .../System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 347916853e3c7b..4ed41b9655691f 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -194,6 +194,7 @@ private async Task TestHandlerCore(string info, string hostName, bool http2, dou var response = await client.SendAsync(message); double elapsedSec = sw.ElapsedMilliseconds * 0.001; + elapsedSec = Math.Round(elapsedSec, 3); _output.WriteLine($"{info}: completed in {elapsedSec} sec"); double window = GetStreamWindowSizeInMegabytes(); report.WriteLine($"{elapsedSec}, {window}"); From 1b48ac71ddd5cee7669132c79a9f9c91a16e31cb Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 10 Jun 2021 21:22:00 +0200 Subject: [PATCH 063/101] PingEx --- .../FunctionalTests/_LargeFileBenchmark.cs | 276 +++++++++++++++++- 1 file changed, 267 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 4ed41b9655691f..e234caca360c1c 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -1,12 +1,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.Tracing; using System.IO; using System.Linq; using System.Net.NetworkInformation; using System.Net.Sockets; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Channels; @@ -62,13 +64,10 @@ public LargeFileBenchmark(ITestOutputHelper output) public Task Download20_SpecificWindow_MegaBytes(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); [Theory] - [InlineData(BenchmarkServer, 64)] + //[InlineData(BenchmarkServer, 64)] [InlineData(BenchmarkServer, 128)] [InlineData(BenchmarkServer, 256)] [InlineData(BenchmarkServer, 512)] - [InlineData(BenchmarkServer, 1024)] - [InlineData(BenchmarkServer, 2048)] - [InlineData(BenchmarkServer, 4096)] public Task Download20_SpecificWindow_KiloBytes(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) @@ -235,6 +234,13 @@ private static async ValueTask CustomConnect(SocketsHttpConnectionContex } } + [Fact] + public async Task TestEstimateRtt() + { + TimeSpan rtt = await EstimateRttAsync(BenchmarkServer); + _output.WriteLine($"RTT: {rtt.TotalMilliseconds} ms"); + } + private async Task EstimateRttAsync(string hostName) { int sep = hostName.IndexOf(':'); @@ -249,20 +255,25 @@ private async Task EstimateRttAsync(string hostName) addr = (await Dns.GetHostAddressesAsync(hostName)).FirstOrDefault(e => e.AddressFamily == AddressFamily.InterNetwork); } - Ping ping = new Ping(); + IPAddress destAddr = (await Dns.GetHostAddressesAsync(hostName))[0]; // warmup: - await ping.SendPingAsync(addr); + PingEx.Send(LocalAddress, destAddr); + + IPAddress local = LocalAddress != null ? LocalAddress : IPAddress.Loopback; + + var reply1 = PingEx.Send(LocalAddress, destAddr).RoundTripTime; + var reply2 = PingEx.Send(LocalAddress, destAddr).RoundTripTime; + + TimeSpan rtt = reply1 > reply2 ? reply1 : reply2; - PingReply reply1 = await ping.SendPingAsync(addr); - PingReply reply2 = await ping.SendPingAsync(addr); - TimeSpan rtt = TimeSpan.FromMilliseconds(reply1.RoundtripTime + reply2.RoundtripTime) / 2; _output.WriteLine($"Estimated RTT: {rtt}"); if (rtt < TimeSpan.FromMilliseconds(1)) { _output.WriteLine("RTT < 1 ms, changing to 1 ms!"); rtt = TimeSpan.FromMilliseconds(1); } + return rtt; } @@ -372,4 +383,251 @@ public override void Dispose() } } } + + // https://stackoverflow.com/a/66380228/797482 + internal static class PingEx + { + /// + /// Pass in the IP you want to ping as a string along with the name of the NIC on your machine that + /// you want to send the ping from. + /// + /// The destination IP as a string ex. '10.10.10.1' + /// The name of the NIC ex. 'LECO Hardware'. Non-case sensitive. + /// + public static bool PingIpFromNic(string ipToPing, string nicName) + { + var sourceIpStr = GetIpOfNicFromName(nicName); + + if (sourceIpStr == "") + { + return false; + } + + var p = Send( + srcAddress: IPAddress.Parse(sourceIpStr), + destAddress: IPAddress.Parse(ipToPing)); + + return p.Status == IPStatus.Success; + } + + /// + /// Pass in the name of a NIC on your machine and this method will return the IPV4 address of it. + /// + /// The name of the NIC you want the IP of ex. 'TE Hardware' + /// + public static string GetIpOfNicFromName(string nicName) + { + var adapters = NetworkInterface.GetAllNetworkInterfaces(); + + foreach (var adapter in adapters) + { + // Ignoring case in NIC name + if (!string.Equals(adapter.Name, nicName, StringComparison.CurrentCultureIgnoreCase)) continue; + + foreach (var uni in adapter.GetIPProperties().UnicastAddresses) + { + // Return the first one found + return uni.Address.ToString(); + } + } + + return ""; + } + + public static PingReplyEx Send(IPAddress srcAddress, IPAddress destAddress, + int timeout = 5000, + byte[] buffer = null, PingOptions po = null) + { + if (destAddress == null || destAddress.AddressFamily != AddressFamily.InterNetwork || + destAddress.Equals(IPAddress.Any)) + throw new ArgumentException(); + + //Defining pinvoke args + var source = srcAddress == null ? 0 : BitConverter.ToUInt32(srcAddress.GetAddressBytes(), 0); + + var destination = BitConverter.ToUInt32(destAddress.GetAddressBytes(), 0); + + var sendBuffer = buffer ?? new byte[] { }; + + var options = new Interop.Option + { + Ttl = (po == null ? (byte)255 : (byte)po.Ttl), + Flags = (po == null ? (byte)0 : po.DontFragment ? (byte)0x02 : (byte)0) //0x02 + }; + + var fullReplyBufferSize = + Interop.ReplyMarshalLength + + sendBuffer.Length; //Size of Reply struct and the transmitted buffer length. + + var allocSpace = + Marshal.AllocHGlobal( + fullReplyBufferSize); // unmanaged allocation of reply size. TODO Maybe should be allocated on stack + try + { + var start = DateTime.Now; + var nativeCode = Interop.IcmpSendEcho2Ex( + Interop.IcmpHandle, //_In_ HANDLE IcmpHandle, + Event: default(IntPtr), //_In_opt_ HANDLE Event, + apcRoutine: default(IntPtr), //_In_opt_ PIO_APC_ROUTINE ApcRoutine, + apcContext: default(IntPtr), //_In_opt_ PVOID ApcContext + source, //_In_ IPAddr SourceAddress, + destination, //_In_ IPAddr DestinationAddress, + sendBuffer, //_In_ LPVOID RequestData, + (short)sendBuffer.Length, //_In_ WORD RequestSize, + ref options, //_In_opt_ PIP_OPTION_INFORMATION RequestOptions, + replyBuffer: allocSpace, //_Out_ LPVOID ReplyBuffer, + fullReplyBufferSize, //_In_ DWORD ReplySize, + timeout //_In_ DWORD Timeout + ); + + var duration = DateTime.Now - start; + + var reply = (Interop.Reply)Marshal.PtrToStructure(allocSpace, + typeof(Interop.Reply)); // Parse the beginning of reply memory to reply struct + + byte[] replyBuffer = null; + if (sendBuffer.Length != 0) + { + replyBuffer = new byte[sendBuffer.Length]; + Marshal.Copy(allocSpace + Interop.ReplyMarshalLength, replyBuffer, 0, + sendBuffer.Length); //copy the rest of the reply memory to managed byte[] + } + + if (nativeCode == 0) //Means that native method is faulted. + return new PingReplyEx(nativeCode, reply.Status, + new IPAddress(reply.Address), duration); + else + return new PingReplyEx(nativeCode, reply.Status, + new IPAddress(reply.Address), reply.RoundTripTime, + replyBuffer); + } + finally + { + Marshal.FreeHGlobal(allocSpace); //free allocated space + } + } + + + /// Interoperability Helper + /// + /// + public static class Interop + { + private static IntPtr? _icmpHandle; + private static int? _replyStructLength; + + /// Returns the application legal icmp handle. Should be close by IcmpCloseHandle + /// + /// + public static IntPtr IcmpHandle + { + get + { + if (_icmpHandle == null) + { + _icmpHandle = IcmpCreateFile(); + //TODO Close Icmp Handle appropriate + } + + return _icmpHandle.GetValueOrDefault(); + } + } + + /// Returns the the marshaled size of the reply struct. + public static int ReplyMarshalLength + { + get + { + if (_replyStructLength == null) + { + _replyStructLength = Marshal.SizeOf(typeof(Reply)); + } + return _replyStructLength.GetValueOrDefault(); + } + } + + + [DllImport("Iphlpapi.dll", SetLastError = true)] + private static extern IntPtr IcmpCreateFile(); + [DllImport("Iphlpapi.dll", SetLastError = true)] + private static extern bool IcmpCloseHandle(IntPtr handle); + [DllImport("Iphlpapi.dll", SetLastError = true)] + public static extern uint IcmpSendEcho2Ex(IntPtr icmpHandle, IntPtr Event, IntPtr apcRoutine, IntPtr apcContext, uint sourceAddress, UInt32 destinationAddress, byte[] requestData, short requestSize, ref Option requestOptions, IntPtr replyBuffer, int replySize, int timeout); + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct Option + { + public byte Ttl; + public readonly byte Tos; + public byte Flags; + public readonly byte OptionsSize; + public readonly IntPtr OptionsData; + } + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct Reply + { + public readonly UInt32 Address; + public readonly int Status; + public readonly int RoundTripTime; + public readonly short DataSize; + public readonly short Reserved; + public readonly IntPtr DataPtr; + public readonly Option Options; + } + } + + public class PingReplyEx + { + private Win32Exception _exception; + + internal PingReplyEx(uint nativeCode, int replyStatus, IPAddress ipAddress, TimeSpan duration) + { + NativeCode = nativeCode; + IpAddress = ipAddress; + if (Enum.IsDefined(typeof(IPStatus), replyStatus)) + Status = (IPStatus)replyStatus; + } + internal PingReplyEx(uint nativeCode, int replyStatus, IPAddress ipAddress, int roundTripTime, byte[] buffer) + { + NativeCode = nativeCode; + IpAddress = ipAddress; + RoundTripTime = TimeSpan.FromMilliseconds(roundTripTime); + Buffer = buffer; + if (Enum.IsDefined(typeof(IPStatus), replyStatus)) + Status = (IPStatus)replyStatus; + } + + /// Native result from IcmpSendEcho2Ex. + public uint NativeCode { get; } + + public IPStatus Status { get; } = IPStatus.Unknown; + + /// The source address of the reply. + public IPAddress IpAddress { get; } + + public byte[] Buffer { get; } + + public TimeSpan RoundTripTime { get; } = TimeSpan.Zero; + + public Win32Exception Exception + { + get + { + if (Status != IPStatus.Success) + return _exception ?? (_exception = new Win32Exception((int)NativeCode, Status.ToString())); + else + return null; + } + } + + public override string ToString() + { + if (Status == IPStatus.Success) + return Status + " from " + IpAddress + " in " + RoundTripTime + " ms with " + Buffer.Length + " bytes"; + else if (Status != IPStatus.Unknown) + return Status + " from " + IpAddress; + else + return Exception.Message + " from " + IpAddress; + } + } + } } From 5c59fe79a9eba3c777af78a8480b0791f8ea1aaa Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 11 Jun 2021 11:42:24 +0200 Subject: [PATCH 064/101] report RTT --- .../FunctionalTests/_LargeFileBenchmark.cs | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index e234caca360c1c..386589c6aef6cc 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -36,8 +36,8 @@ public LargeFileBenchmark(ITestOutputHelper output) public void Dispose() => _listener?.Dispose(); - private const double LengthMb = 400; - private const int TestRunCount = 5; + private const double LengthMb = 100; + private const int TestRunCount = 10; //private const string BenchmarkServer = "10.194.114.94"; //private const string BenchmarkServer = "169.254.132.170"; // duo1 @@ -53,7 +53,7 @@ public LargeFileBenchmark(ITestOutputHelper output) [Theory] [InlineData(BenchmarkServer)] - public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run1", hostName, false, LengthMb); + public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run1", hostName, false, LengthMb, details: "http1.1"); [Theory] [InlineData(BenchmarkServer, 1024)] @@ -107,6 +107,7 @@ private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, d StreamWindowUpdateRatio = ratio, StreamWindowMagicMultiplier = magic }; + string details = $"Dynamic_R({ratio})_M({magic})"; await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio} magic={magic}", hostName, true, LengthMb, handler); } @@ -185,7 +186,7 @@ private static string CreateOutputFile(string details) private async Task TestHandlerCore(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler, StreamWriter report) { _listener.Log2.Clear(); - using var client = new HttpClient(handler, false); + using var client = new HttpClient(CopyHandler(handler), true); client.Timeout = TimeSpan.FromMinutes(3); var message = GenerateRequestMessage(hostName, http2, lengthMb); _output.WriteLine($"{info} / {lengthMb} MB from {message.RequestUri}"); @@ -195,17 +196,21 @@ private async Task TestHandlerCore(string info, string hostName, bool http2, dou double elapsedSec = sw.ElapsedMilliseconds * 0.001; elapsedSec = Math.Round(elapsedSec, 3); _output.WriteLine($"{info}: completed in {elapsedSec} sec"); - double window = GetStreamWindowSizeInMegabytes(); - report.WriteLine($"{elapsedSec}, {window}"); + report.Write(elapsedSec); + double? window = GetStreamWindowSizeInMegabytes(); + if (window.HasValue) report.Write($", {window}"); + double? rtt = GetRtt(); + if (rtt.HasValue) report.Write($", {rtt}"); + report.WriteLine(); } - private double GetStreamWindowSizeInMegabytes() + private double? GetStreamWindowSizeInMegabytes() { const string Prefix = "Updated StreamWindowSize: "; string log = _listener.Log2.ToString(); int idx = log.LastIndexOf(Prefix); - if (idx < 0) return 0; + if (idx < 0) return null; ReadOnlySpan text = log.AsSpan().Slice(idx + Prefix.Length); text = text.Slice(0, text.IndexOf(',')); @@ -214,6 +219,33 @@ private double GetStreamWindowSizeInMegabytes() return Math.Round(sizeMb, 3); } + private double? GetRtt() + { + const string Prefix = "Updated MinRtt: "; + string log = _listener.Log2.ToString(); + + int idx = log.LastIndexOf(Prefix); + if (idx < 0) return null; + ReadOnlySpan text = log.AsSpan().Slice(idx + Prefix.Length); + text = text.Slice(0, text.IndexOf(' ')); + + double rtt = double.Parse(text); + return Math.Round(rtt, 3); + } + + private static SocketsHttpHandler CopyHandler(SocketsHttpHandler h) + { + return new SocketsHttpHandler() + { + FakeRtt = h.FakeRtt, + EnableDynamicHttp2StreamWindowSizing = h.EnableDynamicHttp2StreamWindowSizing, + InitialStreamWindowSize = h.InitialStreamWindowSize, + StreamWindowUpdateRatio = h.StreamWindowUpdateRatio, + StreamWindowMagicMultiplier = h.StreamWindowMagicMultiplier, + ConnectCallback = h.ConnectCallback + }; + } + private static async ValueTask CustomConnect(SocketsHttpConnectionContext ctx, CancellationToken cancellationToken) { Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) From dab35abf51c78822e24d1b20438395f252348ede Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 11 Jun 2021 13:11:39 +0200 Subject: [PATCH 065/101] more benchmark cases --- .../FunctionalTests/_LargeFileBenchmark.cs | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 386589c6aef6cc..6dd60a31c2faab 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -48,7 +48,7 @@ public LargeFileBenchmark(ITestOutputHelper output) //private static readonly IPAddress LocalAddress = IPAddress.Parse("169.254.59.132"); // duo2 private static readonly IPAddress LocalAddress = null; - //private const string ReportDir = @"c:\_dev\WindowBenchmark"; + //private const string ReportDir = @"C:\_dev\r6r\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; private const string ReportDir = @"C:\Users\anfirszo\dev\dotnet\6.0\runtime\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; [Theory] @@ -81,13 +81,15 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler, details); } - public static TheoryData Download20_ServerAndRatio = new TheoryData + public static TheoryData Download20_Data = new TheoryData { - { BenchmarkServer, 8, 0.5 }, - { BenchmarkServer, 8, 0.25 }, - { BenchmarkServer, 8, 0.125 }, - { BenchmarkServer, 4, 0.5 }, - { BenchmarkServer, 4, 0.25 }, + { BenchmarkServer, 8, 1 }, + { BenchmarkServer, 8, 2 }, + { BenchmarkServer, 8, 4 }, + { BenchmarkServer, 8, 8 }, + { BenchmarkServer, 4, 1 }, + { BenchmarkServer, 4, 2 }, + { BenchmarkServer, 4, 4 }, //{ BenchmarkServerGo, 8, 0.5 }, //{ BenchmarkServerGo, 8, 0.25 }, //{ BenchmarkServerGo, 8, 0.125 }, @@ -95,37 +97,38 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) //{ BenchmarkServerGo, 4, 0.25 }, }; - [Theory] - [MemberData(nameof(Download20_ServerAndRatio))] - private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, double magic) + [MemberData(nameof(Download20_Data))] + public async Task Download20_StaticRtt(string hostName, int ratio, int correction) { _listener.Enabled = true; - _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); - var handler = new SocketsHttpHandler() + _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); + var handler = new SocketsHttpHandler { + FakeRtt = await EstimateRttAsync(hostName), StreamWindowUpdateRatio = ratio, - StreamWindowMagicMultiplier = magic + StreamWindowMagicMultiplier = 1.0 / correction }; - string details = $"Dynamic_R({ratio})_M({magic})"; - await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio} magic={magic}", hostName, true, LengthMb, handler); + + string details = $"StaticRtt_R({ratio})_C({correction})"; + await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT | host:{hostName} ratio={ratio} magic={handler.StreamWindowMagicMultiplier}", + hostName, true, LengthMb, handler, details); } [Theory] - [MemberData(nameof(Download20_ServerAndRatio))] - public async Task Download20_StaticRtt(string hostName, int ratio, double magic) + [MemberData(nameof(Download20_Data))] + private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, int correction) { _listener.Enabled = true; _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); - var handler = new SocketsHttpHandler + var handler = new SocketsHttpHandler() { - FakeRtt = await EstimateRttAsync(hostName), StreamWindowUpdateRatio = ratio, - StreamWindowMagicMultiplier = magic + StreamWindowMagicMultiplier = 1.0/correction }; - - string details = $"StaticRtt_R({ratio})_M({magic})"; - await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT | host:{hostName} ratio={ratio} magic={magic}", hostName, true, LengthMb, handler, details); + string details = $"Dynamic_R({ratio})_C({correction})"; + await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio} magic={handler.StreamWindowMagicMultiplier}", + hostName, true, LengthMb, handler); } [Theory] From da9656a12cca6721a774967d675b5453bc4fda73 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 11 Jun 2021 13:11:39 +0200 Subject: [PATCH 066/101] more benchmark cases --- .../FunctionalTests/_LargeFileBenchmark.cs | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 386589c6aef6cc..cf91a0b7d47910 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -48,7 +48,7 @@ public LargeFileBenchmark(ITestOutputHelper output) //private static readonly IPAddress LocalAddress = IPAddress.Parse("169.254.59.132"); // duo2 private static readonly IPAddress LocalAddress = null; - //private const string ReportDir = @"c:\_dev\WindowBenchmark"; + //private const string ReportDir = @"C:\_dev\r6r\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; private const string ReportDir = @"C:\Users\anfirszo\dev\dotnet\6.0\runtime\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; [Theory] @@ -81,13 +81,15 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler, details); } - public static TheoryData Download20_ServerAndRatio = new TheoryData + public static TheoryData Download20_Data = new TheoryData { - { BenchmarkServer, 8, 0.5 }, - { BenchmarkServer, 8, 0.25 }, - { BenchmarkServer, 8, 0.125 }, - { BenchmarkServer, 4, 0.5 }, - { BenchmarkServer, 4, 0.25 }, + { BenchmarkServer, 8, 1 }, + { BenchmarkServer, 8, 2 }, + { BenchmarkServer, 8, 4 }, + { BenchmarkServer, 8, 8 }, + { BenchmarkServer, 4, 1 }, + { BenchmarkServer, 4, 2 }, + { BenchmarkServer, 4, 4 }, //{ BenchmarkServerGo, 8, 0.5 }, //{ BenchmarkServerGo, 8, 0.25 }, //{ BenchmarkServerGo, 8, 0.125 }, @@ -95,37 +97,38 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) //{ BenchmarkServerGo, 4, 0.25 }, }; - [Theory] - [MemberData(nameof(Download20_ServerAndRatio))] - private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, double magic) + [MemberData(nameof(Download20_Data))] + public async Task Download20_StaticRtt(string hostName, int ratio, int correction) { _listener.Enabled = true; - _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); - var handler = new SocketsHttpHandler() + _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); + var handler = new SocketsHttpHandler { + FakeRtt = await EstimateRttAsync(hostName), StreamWindowUpdateRatio = ratio, - StreamWindowMagicMultiplier = magic + StreamWindowMagicMultiplier = 1.0 / correction }; - string details = $"Dynamic_R({ratio})_M({magic})"; - await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio} magic={magic}", hostName, true, LengthMb, handler); + + string details = $"StaticRtt_R({ratio})_C({correction})"; + await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT | host:{hostName} ratio={ratio} magic={handler.StreamWindowMagicMultiplier}", + hostName, true, LengthMb, handler, details); } [Theory] - [MemberData(nameof(Download20_ServerAndRatio))] - public async Task Download20_StaticRtt(string hostName, int ratio, double magic) + [MemberData(nameof(Download20_Data))] + private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, int correction) { _listener.Enabled = true; _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); - var handler = new SocketsHttpHandler + var handler = new SocketsHttpHandler() { - FakeRtt = await EstimateRttAsync(hostName), StreamWindowUpdateRatio = ratio, - StreamWindowMagicMultiplier = magic + StreamWindowMagicMultiplier = 1.0/correction }; - - string details = $"StaticRtt_R({ratio})_M({magic})"; - await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT | host:{hostName} ratio={ratio} magic={magic}", hostName, true, LengthMb, handler, details); + string details = $"Dynamic_R({ratio})_C({correction})"; + await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio} magic={handler.StreamWindowMagicMultiplier}", + hostName, true, LengthMb, handler, details); } [Theory] From c48e340b5a47e9c38994a876310cac18db35030a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 11 Jun 2021 13:20:38 +0200 Subject: [PATCH 067/101] pass 'details' --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 6dd60a31c2faab..cf91a0b7d47910 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -128,7 +128,7 @@ private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, i }; string details = $"Dynamic_R({ratio})_C({correction})"; await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio} magic={handler.StreamWindowMagicMultiplier}", - hostName, true, LengthMb, handler); + hostName, true, LengthMb, handler, details); } [Theory] From 3ef586cef36d6694d858acd4e4c8db16e886e609 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 11 Jun 2021 13:23:39 +0200 Subject: [PATCH 068/101] have a warmup --- .../FunctionalTests/_LargeFileBenchmark.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index cf91a0b7d47910..2521c32dcb4602 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -172,6 +172,9 @@ private async Task TestHandler(string info, string hostName, bool http2, double _output.WriteLine("REPORT: " + reportFileName); using StreamWriter report = new StreamWriter(reportFileName); + _output.WriteLine($"############ Warmup Run ############"); + await TestHandlerCore(info, hostName, http2, lengthMb, handler, null); + for (int i = 0; i < TestRunCount; i++) { _output.WriteLine($"############ run {i} ############"); @@ -199,12 +202,16 @@ private async Task TestHandlerCore(string info, string hostName, bool http2, dou double elapsedSec = sw.ElapsedMilliseconds * 0.001; elapsedSec = Math.Round(elapsedSec, 3); _output.WriteLine($"{info}: completed in {elapsedSec} sec"); - report.Write(elapsedSec); - double? window = GetStreamWindowSizeInMegabytes(); - if (window.HasValue) report.Write($", {window}"); - double? rtt = GetRtt(); - if (rtt.HasValue) report.Write($", {rtt}"); - report.WriteLine(); + + if (report != null) + { + report.Write(elapsedSec); + double? window = GetStreamWindowSizeInMegabytes(); + if (window.HasValue) report.Write($", {window}"); + double? rtt = GetRtt(); + if (rtt.HasValue) report.Write($", {rtt}"); + report.WriteLine(); + } } private double? GetStreamWindowSizeInMegabytes() From 8a349af62124f1a04e23682e0c4389158968b4a2 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 11 Jun 2021 15:45:04 +0200 Subject: [PATCH 069/101] split benchmarks because of limit --- .../FunctionalTests/_LargeFileBenchmark.cs | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 2521c32dcb4602..433da821ae2f45 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -42,6 +42,7 @@ public LargeFileBenchmark(ITestOutputHelper output) //private const string BenchmarkServer = "10.194.114.94"; //private const string BenchmarkServer = "169.254.132.170"; // duo1 private const string BenchmarkServer = "192.168.0.152"; + //private const string BenchmarkServer = "127.0.0.1"; private const string BenchmarkServerGo = "192.168.0.152:5002"; // private const string BenchmarkServer = "127.0.0.1:5000"; @@ -64,7 +65,7 @@ public LargeFileBenchmark(ITestOutputHelper output) public Task Download20_SpecificWindow_MegaBytes(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); [Theory] - //[InlineData(BenchmarkServer, 64)] + [InlineData(BenchmarkServer, 64)] [InlineData(BenchmarkServer, 128)] [InlineData(BenchmarkServer, 256)] [InlineData(BenchmarkServer, 512)] @@ -97,9 +98,31 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) //{ BenchmarkServerGo, 4, 0.25 }, }; + public static TheoryData Download20_Data8 = new TheoryData + { + { BenchmarkServer, 8, 1 }, + { BenchmarkServer, 8, 2 }, + { BenchmarkServer, 8, 4 }, + { BenchmarkServer, 8, 8 }, + }; + + + public static TheoryData Download20_Data4 = new TheoryData + { + { BenchmarkServer, 4, 1 }, + { BenchmarkServer, 4, 2 }, + { BenchmarkServer, 4, 4 }, + }; + + [Theory] + [MemberData(nameof(Download20_Data8))] + public Task Download20_StaticRtt_8(string hostName, int ratio, int correction) => Download20_StaticRtt(hostName, ratio, correction); + [Theory] - [MemberData(nameof(Download20_Data))] - public async Task Download20_StaticRtt(string hostName, int ratio, int correction) + [MemberData(nameof(Download20_Data4))] + public Task Download20_StaticRtt_4(string hostName, int ratio, int correction) => Download20_StaticRtt(hostName, ratio, correction); + + private async Task Download20_StaticRtt(string hostName, int ratio, int correction) { _listener.Enabled = true; _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); @@ -116,7 +139,13 @@ await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT } [Theory] - [MemberData(nameof(Download20_Data))] + [MemberData(nameof(Download20_Data8))] + public Task Download20_Dynamic_SingleStream_8(string hostName, int ratio, int correction) => Download20_Dynamic_SingleStream(hostName, ratio, correction); + + [Theory] + [MemberData(nameof(Download20_Data4))] + public Task Download20_Dynamic_SingleStream_4(string hostName, int ratio, int correction) => Download20_Dynamic_SingleStream(hostName, ratio, correction); + private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, int correction) { _listener.Enabled = true; From 4a0d05719f79e87a8853c21d4e3695a6a4d904ea Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 15 Jun 2021 13:44:18 +0200 Subject: [PATCH 070/101] benchmark more --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 2 ++ .../System.Net.Http/tests/FunctionalTests/_RunAll.ps1 | 7 +++++++ 2 files changed, 9 insertions(+) create mode 100644 src/libraries/System.Net.Http/tests/FunctionalTests/_RunAll.ps1 diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 433da821ae2f45..ef735ead7985f6 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -104,6 +104,7 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) { BenchmarkServer, 8, 2 }, { BenchmarkServer, 8, 4 }, { BenchmarkServer, 8, 8 }, + { BenchmarkServer, 8, 16 }, }; @@ -112,6 +113,7 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) { BenchmarkServer, 4, 1 }, { BenchmarkServer, 4, 2 }, { BenchmarkServer, 4, 4 }, + { BenchmarkServer, 4, 8 }, }; [Theory] diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_RunAll.ps1 b/src/libraries/System.Net.Http/tests/FunctionalTests/_RunAll.ps1 new file mode 100644 index 00000000000000..b1e44772402d4c --- /dev/null +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_RunAll.ps1 @@ -0,0 +1,7 @@ +dotnet test --no-build -c Release --filter FullyQualifiedName~Download11 +dotnet test --no-build -c Release --filter FullyQualifiedName~Download20_SpecificWindow_KiloBytes +dotnet test --no-build -c Release --filter FullyQualifiedName~Download20_SpecificWindow_MegaBytes +dotnet test --no-build -c Release --filter FullyQualifiedName~Download20_StaticRtt_8 +dotnet test --no-build -c Release --filter FullyQualifiedName~Download20_StaticRtt_4 +dotnet test --no-build -c Release --filter FullyQualifiedName~Download20_Dynamic_SingleStream_8 +dotnet test --no-build -c Release --filter FullyQualifiedName~Download20_Dynamic_SingleStream_4 From a5c95b7eccadd079606e300dff1ddab72162ddf1 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 15 Jun 2021 14:10:45 +0200 Subject: [PATCH 071/101] StreamWindowMagicMultiplier -> StreamWindowThresholdMultiplier --- .../System.Net.Http/ref/System.Net.Http.cs | 2 +- .../BrowserHttpHandler/SocketsHttpHandler.cs | 2 +- .../Http2StreamWindowManager.cs | 19 ++++--------------- .../HttpConnectionSettings.cs | 4 ++-- .../SocketsHttpHandler/SocketsHttpHandler.cs | 6 +++--- .../FunctionalTests/_LargeFileBenchmark.cs | 10 +++++----- 6 files changed, 16 insertions(+), 27 deletions(-) diff --git a/src/libraries/System.Net.Http/ref/System.Net.Http.cs b/src/libraries/System.Net.Http/ref/System.Net.Http.cs index 071dfc4765de19..7615c5d5ec70b2 100644 --- a/src/libraries/System.Net.Http/ref/System.Net.Http.cs +++ b/src/libraries/System.Net.Http/ref/System.Net.Http.cs @@ -358,7 +358,7 @@ public SocketsHttpHandler() { } public int InitialStreamWindowSize { get { throw null; } set { } } public bool EnableDynamicHttp2StreamWindowSizing { get { throw null; } set { } } public int StreamWindowUpdateRatio { get { throw null; } set { } } - public double StreamWindowMagicMultiplier { get { throw null; } set { } } + public int StreamWindowThresholdMultiplier { 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 { } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs index 3f511d2f038b13..362f2782716312 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs @@ -49,7 +49,7 @@ public int StreamWindowUpdateRatio set => throw new PlatformNotSupportedException(); } - public double StreamWindowMagicMultiplier + public int StreamWindowThresholdMultiplier { get => throw new PlatformNotSupportedException(); set => throw new PlatformNotSupportedException(); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 487007c4e94ba2..c865bad5506200 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -68,14 +68,14 @@ private class DynamicHttp2StreamWindowManager : Http2StreamWindowManager private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private TimeSpan _lastWindowUpdate; - private double _magic = 1; + private int _streamWindowThresholdMultiplier = 1; private readonly TimeSpan _start; public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream stream) : base(connection, stream) { - _magic = connection._pool.Settings._streamWindowMagicMultiplier; - _stream.TraceFlowControl($" magic:{_magic} | Stopwatch: IsHighResolution={Stopwatch.IsHighResolution}, Frequency={Stopwatch.Frequency}"); + _streamWindowThresholdMultiplier = connection._pool.Settings._streamWindowThresholdMultiplier; + _stream.TraceFlowControl($" _streamWindowThresholdMultiplier:{_streamWindowThresholdMultiplier} | Stopwatch: IsHighResolution={Stopwatch.IsHighResolution}, Frequency={Stopwatch.Frequency}"); _lastWindowUpdate = _stopwatch.Elapsed; _start = _lastWindowUpdate; } @@ -96,7 +96,7 @@ public override void AdjustWindow(int bytesConsumed) TimeSpan rtt = _connection._rttEstimator.MinRtt; TimeSpan dt = currentTime - _lastWindowUpdate; - if (_magic * _delivered * rtt.Ticks > (double)StreamWindowThreshold * dt.Ticks) + if (_delivered * rtt.Ticks > StreamWindowThreshold * dt.Ticks * _streamWindowThresholdMultiplier) { windowSizeIncrement += _streamWindowSize; _streamWindowSize *= 2; @@ -104,17 +104,6 @@ public override void AdjustWindow(int bytesConsumed) _stream.TraceFlowControl( $"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold} | S-T={(currentTime - _start).TotalSeconds} sec {Environment.NewLine}"); } - else - { - string msg = "No adjustment! |" + GetDiagnostics(); - _stream.TraceFlowControl(msg); - } - - string GetDiagnostics() - { - return $"RTT={rtt.TotalMilliseconds} ms || dt={dt.TotalMilliseconds} ms || " + - $"Magic*_delivered/dt = {_magic * _delivered / dt.TotalSeconds} bytes/sec || StreamWindowThreshold/RTT = {StreamWindowThreshold / rtt.TotalSeconds} bytes/sec"; - } } Task sendWindowUpdateTask = _connection.SendWindowUpdateAsync(_stream.StreamId, windowSizeIncrement); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index bd0a251325bc5b..97bc126939613e 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -72,7 +72,7 @@ internal sealed class HttpConnectionSettings internal int _initialStreamWindowSize = 65535; internal int _streamWindowUpdateRatio = 8; - internal double _streamWindowMagicMultiplier = 1; + internal int _streamWindowThresholdMultiplier = 1; public HttpConnectionSettings() { @@ -130,7 +130,7 @@ public HttpConnectionSettings CloneAndNormalize() _fakeRtt = _fakeRtt, _initialStreamWindowSize = _initialStreamWindowSize, _streamWindowUpdateRatio = _streamWindowUpdateRatio, - _streamWindowMagicMultiplier = _streamWindowMagicMultiplier, + _streamWindowThresholdMultiplier = _streamWindowThresholdMultiplier, _enableDynamicHttp2StreamWindowSizing = _enableDynamicHttp2StreamWindowSizing }; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index ceb99c0cb3b8f5..681d6c5e8dce28 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -79,10 +79,10 @@ public int StreamWindowUpdateRatio set => _settings._streamWindowUpdateRatio = value; } - public double StreamWindowMagicMultiplier + public int StreamWindowThresholdMultiplier { - get => _settings._streamWindowMagicMultiplier; - set => _settings._streamWindowMagicMultiplier = value; + get => _settings._streamWindowThresholdMultiplier; + set => _settings._streamWindowThresholdMultiplier = value; } [AllowNull] diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index ef735ead7985f6..0d3faf8b6dee26 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -132,11 +132,11 @@ private async Task Download20_StaticRtt(string hostName, int ratio, int correcti { FakeRtt = await EstimateRttAsync(hostName), StreamWindowUpdateRatio = ratio, - StreamWindowMagicMultiplier = 1.0 / correction + StreamWindowThresholdMultiplier = correction }; string details = $"StaticRtt_R({ratio})_C({correction})"; - await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT | host:{hostName} ratio={ratio} magic={handler.StreamWindowMagicMultiplier}", + await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT | host:{hostName} ratio={ratio} correction={handler.StreamWindowThresholdMultiplier}", hostName, true, LengthMb, handler, details); } @@ -155,10 +155,10 @@ private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, i var handler = new SocketsHttpHandler() { StreamWindowUpdateRatio = ratio, - StreamWindowMagicMultiplier = 1.0/correction + StreamWindowThresholdMultiplier = correction }; string details = $"Dynamic_R({ratio})_C({correction})"; - await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio} magic={handler.StreamWindowMagicMultiplier}", + await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio} correction={handler.StreamWindowThresholdMultiplier}", hostName, true, LengthMb, handler, details); } @@ -282,7 +282,7 @@ private static SocketsHttpHandler CopyHandler(SocketsHttpHandler h) EnableDynamicHttp2StreamWindowSizing = h.EnableDynamicHttp2StreamWindowSizing, InitialStreamWindowSize = h.InitialStreamWindowSize, StreamWindowUpdateRatio = h.StreamWindowUpdateRatio, - StreamWindowMagicMultiplier = h.StreamWindowMagicMultiplier, + StreamWindowThresholdMultiplier = h.StreamWindowThresholdMultiplier, ConnectCallback = h.ConnectCallback }; } From ed18c5b24fcb50551ca51d7837574155131bb115 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 16 Jun 2021 17:09:43 +0200 Subject: [PATCH 072/101] remove Stopwatch allocation --- .../Http2StreamWindowManager.cs | 22 +++++++++++-------- .../FunctionalTests/_LargeFileBenchmark.cs | 19 ++++++++++++---- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index c865bad5506200..c53ec9f72a3f27 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -65,19 +65,18 @@ public virtual void AdjustWindow(int bytesConsumed) private class DynamicHttp2StreamWindowManager : Http2StreamWindowManager { - private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); - private TimeSpan _lastWindowUpdate; + private static readonly double StopWatchToTimesSpan = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; + + private long _lastWindowUpdate; private int _streamWindowThresholdMultiplier = 1; - private readonly TimeSpan _start; public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream stream) : base(connection, stream) { _streamWindowThresholdMultiplier = connection._pool.Settings._streamWindowThresholdMultiplier; _stream.TraceFlowControl($" _streamWindowThresholdMultiplier:{_streamWindowThresholdMultiplier} | Stopwatch: IsHighResolution={Stopwatch.IsHighResolution}, Frequency={Stopwatch.Frequency}"); - _lastWindowUpdate = _stopwatch.Elapsed; - _start = _lastWindowUpdate; + _lastWindowUpdate = Stopwatch.GetTimestamp(); } public override void AdjustWindow(int bytesConsumed) @@ -89,20 +88,19 @@ public override void AdjustWindow(int bytesConsumed) } int windowSizeIncrement = _delivered; - TimeSpan currentTime = _stopwatch.Elapsed; + long currentTime = Stopwatch.GetTimestamp(); if (_connection._rttEstimator!.MinRtt > TimeSpan.Zero) { TimeSpan rtt = _connection._rttEstimator.MinRtt; - TimeSpan dt = currentTime - _lastWindowUpdate; + TimeSpan dt = StopwatchTicksToTimeSpan(currentTime - _lastWindowUpdate); if (_delivered * rtt.Ticks > StreamWindowThreshold * dt.Ticks * _streamWindowThresholdMultiplier) { windowSizeIncrement += _streamWindowSize; _streamWindowSize *= 2; - _stream.TraceFlowControl( - $"Updated StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold} | S-T={(currentTime - _start).TotalSeconds} sec {Environment.NewLine}"); + _stream.TraceFlowControl($"Updated Stream Window. StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); } } @@ -112,6 +110,12 @@ public override void AdjustWindow(int bytesConsumed) _delivered = 0; _lastWindowUpdate = currentTime; } + + private static TimeSpan StopwatchTicksToTimeSpan(long stopwatchTicks) + { + long ticks = (long)(StopWatchToTimesSpan * stopwatchTicks); + return new TimeSpan(ticks); + } } private class RttEstimator diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 0d3faf8b6dee26..c139c0bfb4020d 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -148,10 +148,21 @@ await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT [MemberData(nameof(Download20_Data4))] public Task Download20_Dynamic_SingleStream_4(string hostName, int ratio, int correction) => Download20_Dynamic_SingleStream(hostName, ratio, correction); - private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, int correction) + [Fact] + public Task Download20_Dynamic_Test() + { + _listener.Enabled = true; + return Download20_Dynamic_SingleStream(BenchmarkServer, 8, 8, true); + } + + private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, int correction, bool keepFilter = false) { _listener.Enabled = true; - _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); + if (!keepFilter) + { + _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); + } + var handler = new SocketsHttpHandler() { StreamWindowUpdateRatio = ratio, @@ -247,7 +258,7 @@ private async Task TestHandlerCore(string info, string hostName, bool http2, dou private double? GetStreamWindowSizeInMegabytes() { - const string Prefix = "Updated StreamWindowSize: "; + const string Prefix = "Updated Stream Window. StreamWindowSize: "; string log = _listener.Log2.ToString(); int idx = log.LastIndexOf(Prefix); @@ -266,7 +277,7 @@ private async Task TestHandlerCore(string info, string hostName, bool http2, dou string log = _listener.Log2.ToString(); int idx = log.LastIndexOf(Prefix); - if (idx < 0) return null; + ReadOnlySpan text = log.AsSpan().Slice(idx + Prefix.Length); text = text.Slice(0, text.IndexOf(' ')); From ce1920ae10c91579967d3a96679bdd18e8260550 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 16 Jun 2021 18:31:03 +0200 Subject: [PATCH 073/101] RttEstimator: do not react when there is no active stream --- .../Common/tests/System/Net/Http/Http2LoopbackConnection.cs | 3 ++- .../System/Net/Http/SocketsHttpHandler/Http2Connection.cs | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs index 01fa9d4e697b5f..0e1d5016fbe03c 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs @@ -722,7 +722,8 @@ public async Task PingPong() PingFrame pingAck = (PingFrame)await ReadFrameAsync(_timeout).ConfigureAwait(false); if (pingAck == null || pingAck.Type != FrameType.Ping || !pingAck.AckFlag) { - throw new Exception("Expected PING ACK"); + string faultDetails = pingAck == null ? "" : $" frame.Type:{pingAck.Type} frame.AckFlag: {pingAck.AckFlag}"; + throw new Exception("Expected PING ACK" + faultDetails); } Assert.Equal(pingData, pingAck.Data); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 07a7552ba13a14..12d1280ae88d16 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -585,13 +585,16 @@ private void ProcessDataFrame(FrameHeader frameHeader) if (http2Stream != null) { bool endStream = frameHeader.EndStreamFlag; + if (frameData.Length > 0) + { + _rttEstimator?.OnDataReceived(); + } http2Stream.OnResponseData(frameData, endStream); } if (frameData.Length > 0) { - _rttEstimator?.OnDataReceived(); ExtendWindow(frameData.Length); } From f30e6b169721fdf62b6e818451267f844cf180ea Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 16 Jun 2021 19:18:27 +0200 Subject: [PATCH 074/101] handle GoAway --- .../System/Net/Http/SocketsHttpHandler/Http2Connection.cs | 3 +++ .../Http/SocketsHttpHandler/Http2StreamWindowManager.cs | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 12d1280ae88d16..9e426feeb058e2 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -1688,6 +1688,9 @@ private void StartTerminatingConnection(int lastValidStream, Exception abortExce // we could hold pool lock while trying to grab connection lock in Dispose(). _pool.InvalidateHttp2Connection(this); + // There is no point sending more PING frames for RTT estimation: + _rttEstimator?.OnGoAwayReceived(); + List streamsToAbort = new List(); lock (SyncObject) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index c53ec9f72a3f27..20f95ab3b63924 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -125,6 +125,7 @@ private enum Status Init, Waiting, PingSent, + Terminating } private const double PingIntervalInSeconds = 1; @@ -181,6 +182,7 @@ internal void OnDataReceived() // Send a PING long payload = Interlocked.Decrement(ref _pingCounter); + _connection.TraceFlowControl("Sending PING in response to DATA."); _connection.LogExceptions(_connection.SendPingAsync(payload, isAck: false)); _pingSentTimestamp = now; _status = Status.PingSent; @@ -192,6 +194,7 @@ internal void OnPingAck(long payload) { Debug.Assert(payload < 0); if (_staticRtt.HasValue) return; + if (_status != Status.PingSent) return; if (Interlocked.Read(ref _pingCounter) != payload) ThrowProtocolError(); @@ -199,6 +202,11 @@ internal void OnPingAck(long payload) _status = Status.Waiting; } + internal void OnGoAwayReceived() + { + _status = Status.Terminating; + } + private void RefreshRtt() { long elapsedTicks = Stopwatch.GetTimestamp() - _pingSentTimestamp; From 86e1d6874e1a97ed911a4f4dc2d150a4fde4861a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 18 Jun 2021 17:36:59 +0200 Subject: [PATCH 075/101] fix more tests --- .../Net/Http/Http2LoopbackConnection.cs | 50 +++++++++++++++++-- .../HttpClientHandlerTest.Http2.cs | 29 ++++++++++- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs index 0e1d5016fbe03c..b1eaa0f386441b 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs @@ -25,6 +25,7 @@ public class Http2LoopbackConnection : GenericLoopbackConnection private TaskCompletionSource _ignoredSettingsAckPromise; private bool _ignoreWindowUpdates; private TaskCompletionSource _expectPingFrame; + private bool _respondToPing; private readonly TimeSpan _timeout; private int _lastStreamId; @@ -200,8 +201,8 @@ private async Task ReadFrameAsync(CancellationToken cancellationToken) if (_expectPingFrame != null && header.Type == FrameType.Ping) { - _expectPingFrame.SetResult(PingFrame.ReadFrom(header, data)); - _expectPingFrame = null; + PingFrame pingFrame = PingFrame.ReadFrom(header, data); + await ProcessExpectedPingFrame(pingFrame); return await ReadFrameAsync(cancellationToken).ConfigureAwait(false); } @@ -229,6 +230,18 @@ private async Task ReadFrameAsync(CancellationToken cancellationToken) } } + private async Task ProcessExpectedPingFrame(PingFrame pingFrame) + { + _expectPingFrame.SetResult(pingFrame); + if (_respondToPing && !pingFrame.AckFlag) + { + await SendPingAckAsync(pingFrame.Data); + } + + _expectPingFrame = null; + _respondToPing = false; + } + // Reset and return underlying networking objects. public (Socket, Stream) ResetNetwork() { @@ -264,11 +277,13 @@ public void IgnoreWindowUpdates() } // Set up loopback server to expect PING frames among other frames. - // Once PING frame is read in ReadFrameAsync, the returned task is completed. + // Once PING frame is read in ReadFrameAsync or PingPong, the returned task is completed. // The returned task is canceled in ReadPingAsync if no PING frame has been read so far. - public Task ExpectPingFrameAsync() + public Task ExpectPingFrameAsync(bool respond = false) { _expectPingFrame ??= new TaskCompletionSource(); + _respondToPing = respond; + return _expectPingFrame.Task; } @@ -292,6 +307,24 @@ public async Task ReadRstStreamAsync(int streamId) } } + public async Task ReadAllPingFrames(bool respond = false) + { + Frame frame = await ReadFrameAsync(_timeout).ConfigureAwait(false); + while (frame != null) + { + PingFrame pingFrame = frame as PingFrame; + if (pingFrame == null) + { + throw new Exception($"Unexpected frame received: {frame}"); + } + if (respond) + { + await SendPingAckAsync(pingFrame.Data); + } + frame = await ReadFrameAsync(_timeout).ConfigureAwait(false); + } + } + // Wait for the client to close the connection, e.g. after the HttpClient is disposed. public async Task WaitForClientDisconnectAsync(bool ignoreUnexpectedFrames = false) { @@ -720,6 +753,14 @@ public async Task PingPong() PingFrame ping = new PingFrame(pingData, FrameFlags.None, 0); await WriteFrameAsync(ping).ConfigureAwait(false); PingFrame pingAck = (PingFrame)await ReadFrameAsync(_timeout).ConfigureAwait(false); + + // Not an ack, but we are expecting a PING frame: + if (pingAck != null && !pingAck.AckFlag && _expectPingFrame != null) + { + await ProcessExpectedPingFrame(pingAck); + pingAck = (PingFrame)await ReadFrameAsync(_timeout).ConfigureAwait(false); + } + if (pingAck == null || pingAck.Type != FrameType.Ping || !pingAck.AckFlag) { string faultDetails = pingAck == null ? "" : $" frame.Type:{pingAck.Type} frame.AckFlag: {pingAck.AckFlag}"; @@ -733,6 +774,7 @@ public async Task ReadPingAsync(TimeSpan timeout) { _expectPingFrame?.TrySetCanceled(); _expectPingFrame = null; + _respondToPing = false; Frame frame = await ReadFrameAsync(timeout).ConfigureAwait(false); Assert.NotNull(frame); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs index 668d194ebef1c9..1b936687a5a6e6 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs @@ -1074,6 +1074,9 @@ public async Task CompletedResponse_FrameReceived_Ignored(bool sendDataFrame) DataFrame dataFrame = new DataFrame(new byte[10], FrameFlags.EndStream, 0, streamId); await connection.WriteFrameAsync(dataFrame); + // We may receive an RTT PING in response to the DATA: + _ = connection.ExpectPingFrameAsync(respond: true); + HttpResponseMessage response = await sendTask; Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -1191,6 +1194,8 @@ public static IEnumerable ValidAndInvalidProtocolErrorsAndBool() [MemberData(nameof(ValidAndInvalidProtocolErrorsAndBool))] public async Task ResetResponseStream_FrameReceived_Ignored(ProtocolErrors error, bool dataFrame) { + using var listener = new LogHttpEventListener(_output); + listener.Enabled = true; using (Http2LoopbackServer server = Http2LoopbackServer.CreateServer()) using (HttpClient client = CreateHttpClient()) { @@ -2431,7 +2436,23 @@ private async Task SendAndReceiveRequestDataAsync(Memory data, Stream requ { await requestStream.WriteAsync(data); await requestStream.FlushAsync(); - DataFrame dataFrame = (DataFrame)await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)); + + // TODO: Shall we make this a configurable? + TimeSpan timeout = TimeSpan.FromSeconds(30); + + Frame frame = await connection.ReadFrameAsync(timeout); + + if (frame is PingFrame pingFrame) + { + // We may receive an RTT estimation PING when sending data: + Assert.False(pingFrame.AckFlag); + await connection.SendPingAckAsync(pingFrame.Data); + + frame = await connection.ReadFrameAsync(timeout); + } + + DataFrame dataFrame = (DataFrame)frame; + Assert.True(data.Span.SequenceEqual(dataFrame.Data.Span)); } @@ -2667,6 +2688,8 @@ public async Task PostAsyncDuplex_RequestContentExceptionAfterResponseEndReceive // Send data on response stream and complete it, but don't read it on the client yet await connection.SendResponseDataAsync(streamId, contentBytes, endStream: true); + // We may have received an RTT ping + _ = connection.ExpectPingFrameAsync(true); // Pingpong to ensure it's processed by client await connection.PingPong(); @@ -2790,6 +2813,7 @@ public async Task PostAsyncDuplex_ServerResetsStream_Throws() // Trying to read on the response stream should fail now, and client should ignore any data received await AssertProtocolErrorForIOExceptionAsync(SendAndReceiveResponseDataAsync(contentBytes, responseStream, connection, streamId), ProtocolErrors.ENHANCE_YOUR_CALM); + // Attempting to write on the request body should now fail with OperationCanceledException. Exception e = await Assert.ThrowsAnyAsync(async () => { await SendAndReceiveRequestDataAsync(contentBytes, requestStream, connection, streamId); }); @@ -2798,6 +2822,9 @@ public async Task PostAsyncDuplex_ServerResetsStream_Throws() duplexContent.Fail(e); } + // We are expecting a few RTT PING frames in response to the DATA sent by the server. We should not receive any other frames at this point. + await connection.ReadAllPingFrames(respond: true).WaitAsync(TimeSpan.FromSeconds(5)); + // On handler dispose, client should shutdown the connection without sending additional frames. await connection.WaitForClientDisconnectAsync(); } From ebb80a96e1bb4309d1439cf877ea062750369701 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 20 Jun 2021 17:54:39 +0200 Subject: [PATCH 076/101] do not send RTT ping when EndStreamFlag is set --- .../System/Net/Http/SocketsHttpHandler/Http2Connection.cs | 7 ++++--- .../tests/FunctionalTests/HttpClientHandlerTest.Http2.cs | 3 +-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 9e426feeb058e2..4bb3aea3c7873e 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -585,12 +585,13 @@ private void ProcessDataFrame(FrameHeader frameHeader) if (http2Stream != null) { bool endStream = frameHeader.EndStreamFlag; - if (frameData.Length > 0) + + http2Stream.OnResponseData(frameData, endStream); + + if (!endStream && frameData.Length > 0) { _rttEstimator?.OnDataReceived(); } - - http2Stream.OnResponseData(frameData, endStream); } if (frameData.Length > 0) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs index 1b936687a5a6e6..db59ffea212d0c 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs @@ -1026,6 +1026,7 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => await connection.SendGoAway(streamId); // Make sure client received GOAWAY + _ = connection.ExpectPingFrameAsync(); await connection.PingPong(); await connection.SendResponseBodyAsync(streamId, new byte[4] { 15, 14, 13, 12 }, isFinal: false); @@ -2688,8 +2689,6 @@ public async Task PostAsyncDuplex_RequestContentExceptionAfterResponseEndReceive // Send data on response stream and complete it, but don't read it on the client yet await connection.SendResponseDataAsync(streamId, contentBytes, endStream: true); - // We may have received an RTT ping - _ = connection.ExpectPingFrameAsync(true); // Pingpong to ensure it's processed by client await connection.PingPong(); From 79024f1c67e69a7614e43de03d7984d18be0fbee Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 21 Jun 2021 13:10:50 +0200 Subject: [PATCH 077/101] send RTT ping when receiving headers --- .../System/Net/Http/SocketsHttpHandler/Http2Connection.cs | 5 +++-- .../Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index b35de23cec88de..dd9b74dacd19aa 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -455,6 +455,7 @@ private async ValueTask ProcessHeadersFrame(FrameHeader frameHeader) if (http2Stream != null) { http2Stream.OnHeadersStart(); + _rttEstimator?.OnDataOrHeadersReceived(); headersHandler = http2Stream; } else @@ -590,7 +591,7 @@ private void ProcessDataFrame(FrameHeader frameHeader) if (!endStream && frameData.Length > 0) { - _rttEstimator?.OnDataReceived(); + _rttEstimator?.OnDataOrHeadersReceived(); } } @@ -2003,7 +2004,7 @@ private void ProcessPingAck(long payload) { if (payload < 0) // RTT ping { - _rttEstimator?.OnPingAck(payload); + _rttEstimator?.OnPingAckReceived(payload); return; } else // Keepalive ping diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 20f95ab3b63924..33ef6cda9e755e 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -168,7 +168,7 @@ internal void OnInitialSettingsAckReceived() _status = Status.Waiting; } - internal void OnDataReceived() + internal void OnDataOrHeadersReceived() { if (_staticRtt.HasValue) return; @@ -190,7 +190,7 @@ internal void OnDataReceived() } } - internal void OnPingAck(long payload) + internal void OnPingAckReceived(long payload) { Debug.Assert(payload < 0); if (_staticRtt.HasValue) return; From bf15458f40b9d31921d4c260c315714db630a570 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 21 Jun 2021 13:13:31 +0200 Subject: [PATCH 078/101] test --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index c139c0bfb4020d..2eaf181b8bd19a 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -50,7 +50,8 @@ public LargeFileBenchmark(ITestOutputHelper output) private static readonly IPAddress LocalAddress = null; //private const string ReportDir = @"C:\_dev\r6r\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; - private const string ReportDir = @"C:\Users\anfirszo\dev\dotnet\6.0\runtime\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; + //private const string ReportDir = @"C:\Users\anfirszo\dev\dotnet\6.0\runtime\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; + private const string ReportDir = @"C:\_dev\r6d\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Debug\TestResults"; [Theory] [InlineData(BenchmarkServer)] @@ -277,6 +278,7 @@ private async Task TestHandlerCore(string info, string hostName, bool http2, dou string log = _listener.Log2.ToString(); int idx = log.LastIndexOf(Prefix); + if (idx < 0) return null; ReadOnlySpan text = log.AsSpan().Slice(idx + Prefix.Length); text = text.Slice(0, text.IndexOf(' ')); From d42194c850caa85f79e8d482300fb177c9dac80b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 21 Jun 2021 22:04:15 +0200 Subject: [PATCH 079/101] fixed a bunch of Inner Loop HTTP2 tests --- .../Net/Http/Http2LoopbackConnection.cs | 65 +++++++++++-------- .../System/Net/Http/HttpClientHandlerTest.cs | 9 +++ .../HttpClientHandlerTest.Finalization.cs | 5 ++ .../HttpClientHandlerTest.Http2.cs | 60 +++++++++++------ .../FunctionalTests/SocketsHttpHandlerTest.cs | 16 +++-- 5 files changed, 104 insertions(+), 51 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs index b1eaa0f386441b..e44e0a1de75d21 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs @@ -25,6 +25,7 @@ public class Http2LoopbackConnection : GenericLoopbackConnection private TaskCompletionSource _ignoredSettingsAckPromise; private bool _ignoreWindowUpdates; private TaskCompletionSource _expectPingFrame; + private bool _autoProcessPingFrames; private bool _respondToPing; private readonly TimeSpan _timeout; private int _lastStreamId; @@ -202,8 +203,17 @@ private async Task ReadFrameAsync(CancellationToken cancellationToken) if (_expectPingFrame != null && header.Type == FrameType.Ping) { PingFrame pingFrame = PingFrame.ReadFrom(header, data); - await ProcessExpectedPingFrame(pingFrame); - return await ReadFrameAsync(cancellationToken).ConfigureAwait(false); + + // _expectPingFrame is not intended to work with PING ACK: + if (!pingFrame.AckFlag) + { + await ProcessExpectedPingFrame(pingFrame); + return await ReadFrameAsync(cancellationToken).ConfigureAwait(false); + } + else + { + return pingFrame; + } } // Construct the correct frame type and return it. @@ -240,6 +250,11 @@ private async Task ProcessExpectedPingFrame(PingFrame pingFrame) _expectPingFrame = null; _respondToPing = false; + + if (_autoProcessPingFrames) + { + _ = ExpectPingFrameAsync(true); + } } // Reset and return underlying networking objects. @@ -276,8 +291,8 @@ public void IgnoreWindowUpdates() _ignoreWindowUpdates = true; } - // Set up loopback server to expect PING frames among other frames. - // Once PING frame is read in ReadFrameAsync or PingPong, the returned task is completed. + // Set up loopback server to expect a (non-ACK) PING frame among other frames. + // Once PING frame is read in ReadFrameAsync, the returned task is completed. // The returned task is canceled in ReadPingAsync if no PING frame has been read so far. public Task ExpectPingFrameAsync(bool respond = false) { @@ -287,6 +302,21 @@ public Task ExpectPingFrameAsync(bool respond = false) return _expectPingFrame.Task; } + // Recurring variant of ExpectPingFrame(). + // Starting from the time of the call, respond to all (non-ACK) PING frames which are received among other frames. + public void SetupAutomaticPingResponse() + { + _autoProcessPingFrames = true; + _ = ExpectPingFrameAsync(true); + } + + // Tear down automatic PING responses, but still expect (at most one) PING in flight + public void TearDownAutomaticPingResponse() + { + _respondToPing = false; + _autoProcessPingFrames = false; + } + public async Task ReadRstStreamAsync(int streamId) { Frame frame = await ReadFrameAsync(_timeout); @@ -307,22 +337,12 @@ public async Task ReadRstStreamAsync(int streamId) } } - public async Task ReadAllPingFrames(bool respond = false) + // Receive a single PING frame and respond with an ACK + public async Task RespondToPingFrameAsync() { - Frame frame = await ReadFrameAsync(_timeout).ConfigureAwait(false); - while (frame != null) - { - PingFrame pingFrame = frame as PingFrame; - if (pingFrame == null) - { - throw new Exception($"Unexpected frame received: {frame}"); - } - if (respond) - { - await SendPingAckAsync(pingFrame.Data); - } - frame = await ReadFrameAsync(_timeout).ConfigureAwait(false); - } + PingFrame pingFrame = (PingFrame)await ReadFrameAsync(_timeout); + Assert.False(pingFrame.AckFlag, "Unexpected PING ACK"); + await SendPingAckAsync(pingFrame.Data); } // Wait for the client to close the connection, e.g. after the HttpClient is disposed. @@ -754,13 +774,6 @@ public async Task PingPong() await WriteFrameAsync(ping).ConfigureAwait(false); PingFrame pingAck = (PingFrame)await ReadFrameAsync(_timeout).ConfigureAwait(false); - // Not an ack, but we are expecting a PING frame: - if (pingAck != null && !pingAck.AckFlag && _expectPingFrame != null) - { - await ProcessExpectedPingFrame(pingAck); - pingAck = (PingFrame)await ReadFrameAsync(_timeout).ConfigureAwait(false); - } - if (pingAck == null || pingAck.Type != FrameType.Ping || !pingAck.AckFlag) { string faultDetails = pingAck == null ? "" : $" frame.Type:{pingAck.Type} frame.AckFlag: {pingAck.AckFlag}"; diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index 06e80b66b00be5..27d37f9d2151ad 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -1463,6 +1463,10 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => await server.AcceptConnectionAsync(async connection => { await connection.ReadRequestDataAsync(readBody: false); + if (connection is Http2LoopbackConnection http2Connection) + { + http2Connection.SetupAutomaticPingResponse(); // Respond to RTT PING + } // Send multiple 100-Continue responses. for (int count = 0 ; count < 4; count++) { @@ -1565,6 +1569,11 @@ await server.AcceptConnectionAsync(async connection => await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] {new HttpHeaderData("Content-Length", $"{ResponseString.Length}")}, isFinal : false); + if (connection is Http2LoopbackConnection http2Connection) + { + http2Connection.SetupAutomaticPingResponse(); // Respond to RTT PING + } + byte[] body = await connection.ReadRequestBodyAsync(); Assert.Equal(RequestString, Encoding.ASCII.GetString(body)); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Finalization.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Finalization.cs index ac1ba7ebee4ed7..82a789e36e3433 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Finalization.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Finalization.cs @@ -53,6 +53,11 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async url => { HttpRequestData data = await connection.ReadRequestDataAsync(readBody: false); await connection.SendResponseHeadersAsync(headers: new HttpHeaderData[] { new HttpHeaderData("SomeHeaderName", "AndValue") }); + if (connection is Http2LoopbackConnection http2Connection) + { + // We may receive an RTT PING in response to HEADERS + _ = http2Connection.ExpectPingFrameAsync(true); + } await connection.WaitForCancellationAsync(); } finally diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs index db59ffea212d0c..e565c00a3d1f4f 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs @@ -334,6 +334,7 @@ public async Task GetAsync_StreamRefused_RequestIsRetried() using (HttpClient client = CreateHttpClient()) { (_, Http2LoopbackConnection connection) = await EstablishConnectionAndProcessOneRequestAsync(client, server); + connection.SetupAutomaticPingResponse(); Task sendTask = client.GetAsync(server.Address); (int streamId1, HttpRequestData requestData1) = await connection.ReadAndParseRequestHeaderAsync(readBody: true); @@ -371,6 +372,7 @@ public async Task PostAsync_StreamRefused_RequestIsRetried() const string Content = "Hello world"; (_, Http2LoopbackConnection connection) = await EstablishConnectionAndProcessOneRequestAsync(client, server); + connection.SetupAutomaticPingResponse(); Task sendTask = client.PostAsync(server.Address, new StringContent(Content)); (int streamId1, HttpRequestData requestData1) = await connection.ReadAndParseRequestHeaderAsync(readBody: true); @@ -742,6 +744,7 @@ public async Task ResponseStreamFrames_HeadersAfterHeadersWithoutEndHeaders_Conn Task sendTask = client.GetAsync(server.Address); Http2LoopbackConnection connection = await server.EstablishConnectionAsync(); int streamId = await connection.ReadRequestHeaderAsync(); + connection.SetupAutomaticPingResponse(); await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false)); await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false)); @@ -765,6 +768,7 @@ public async Task ResponseStreamFrames_HeadersAfterHeadersAndContinuationWithout int streamId = await connection.ReadRequestHeaderAsync(); await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false)); + await connection.RespondToPingFrameAsync(); // Respond to 1 RTT PING await connection.WriteFrameAsync(MakeSimpleContinuationFrame(streamId, endHeaders: false)); await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false)); @@ -787,6 +791,7 @@ public async Task ResponseStreamFrames_DataAfterHeadersWithoutEndHeaders_Connect int streamId = await connection.ReadRequestHeaderAsync(); await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false)); + await connection.RespondToPingFrameAsync(); // Receive 1 RTT PING await connection.WriteFrameAsync(MakeSimpleDataFrame(streamId)); // As this is a connection level error, the client should see the request fail. @@ -808,6 +813,7 @@ public async Task ResponseStreamFrames_DataAfterHeadersAndContinuationWithoutEnd int streamId = await connection.ReadRequestHeaderAsync(); await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false)); + await connection.RespondToPingFrameAsync(); // Respond to 1 RTT PING await connection.WriteFrameAsync(MakeSimpleContinuationFrame(streamId, endHeaders: false)); await connection.WriteFrameAsync(MakeSimpleDataFrame(streamId)); @@ -868,6 +874,7 @@ public async Task GoAwayFrame_NewRequest_NewConnection() Http2LoopbackConnection connection2 = await server.EstablishConnectionAsync(); int streamId2 = await connection2.ReadRequestHeaderAsync(); await connection2.SendDefaultResponseAsync(streamId2); + await connection2.RespondToPingFrameAsync(); // Receive 1 RTT PING in response to HEADERS HttpResponseMessage response2 = await sendTask2; Assert.Equal(HttpStatusCode.OK, response2.StatusCode); @@ -942,6 +949,7 @@ public async Task GoAwayFrame_UnprocessedStreamFirstRequestFinishedFirst_Request // Complete second request int streamId3 = await connection2.ReadRequestHeaderAsync(); await connection2.SendDefaultResponseAsync(streamId3); + await connection2.RespondToPingFrameAsync(); // Receive 1 RTT PING in response to HEADERS HttpResponseMessage response2 = await sendTask2; Assert.Equal(HttpStatusCode.OK, response2.StatusCode); await connection2.WaitForConnectionShutdownAsync(); @@ -978,6 +986,7 @@ public async Task GoAwayFrame_UnprocessedStreamFirstRequestWaitsUntilSecondFinis // Complete second request int streamId3 = await connection2.ReadRequestHeaderAsync(); await connection2.SendDefaultResponseAsync(streamId3); + await connection2.RespondToPingFrameAsync(); // Expect 1 RTT PING in response to HEADERS HttpResponseMessage response2 = await sendTask2; Assert.Equal(HttpStatusCode.OK, response2.StatusCode); await connection2.WaitForConnectionShutdownAsync(); @@ -1107,6 +1116,7 @@ public async Task EmptyResponse_FrameReceived_Ignored(bool sendDataFrame) // Send empty response. await connection.SendDefaultResponseAsync(streamId); + connection.SetupAutomaticPingResponse(); // Respond to RTT PINGs HttpResponseMessage response = await sendTask; Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -1136,6 +1146,9 @@ public async Task CompletedResponse_WindowUpdateFrameReceived_Success() // Send empty response. await connection.SendDefaultResponseAsync(streamId); + // We expect an RTT PING in response to HEADERS: + await connection.RespondToPingFrameAsync(); + HttpResponseMessage response = await sendTask; Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -1195,8 +1208,6 @@ public static IEnumerable ValidAndInvalidProtocolErrorsAndBool() [MemberData(nameof(ValidAndInvalidProtocolErrorsAndBool))] public async Task ResetResponseStream_FrameReceived_Ignored(ProtocolErrors error, bool dataFrame) { - using var listener = new LogHttpEventListener(_output); - listener.Enabled = true; using (Http2LoopbackServer server = Http2LoopbackServer.CreateServer()) using (HttpClient client = CreateHttpClient()) { @@ -1206,6 +1217,9 @@ public async Task ResetResponseStream_FrameReceived_Ignored(ProtocolErrors error int streamId = await connection.ReadRequestHeaderAsync(); await connection.SendDefaultResponseHeadersAsync(streamId); + // Auto-respond to all incoming RTT PINGs: + connection.SetupAutomaticPingResponse(); + // Send a reset stream frame so that stream 1 moves to a terminal state. RstStreamFrame resetStream = new RstStreamFrame(FrameFlags.None, (int)error, streamId); await connection.WriteFrameAsync(resetStream); @@ -1260,6 +1274,9 @@ public async Task GoAwayFrame_NoPendingStreams_ConnectionClosed() { (int streamId, Http2LoopbackConnection connection) = await EstablishConnectionAndProcessOneRequestAsync(client, server); + // We expect one RTT PING as a response to HEADERS + await connection.RespondToPingFrameAsync(); + // Send GOAWAY. GoAwayFrame goAwayFrame = new GoAwayFrame(streamId, 0, new byte[0], 0); await connection.WriteFrameAsync(goAwayFrame); @@ -1280,6 +1297,9 @@ public async Task GoAwayFrame_AllPendingStreamsValid_RequestsSucceedAndConnectio { (_, Http2LoopbackConnection connection) = await EstablishConnectionAndProcessOneRequestAsync(client, server); + // Handle RTT PINGs: + connection.SetupAutomaticPingResponse(); + // Issue three requests Task sendTask1 = client.GetAsync(server.Address); Task sendTask2 = client.GetAsync(server.Address); @@ -1314,6 +1334,8 @@ public async Task GoAwayFrame_AllPendingStreamsValid_RequestsSucceedAndConnectio await connection.SendResponseDataAsync(streamId2, new byte[10], endStream: true); await connection.SendResponseDataAsync(streamId3, new byte[5], endStream: true); + // Send no more PING ACK: + connection.TearDownAutomaticPingResponse(); // We will not send any more frames, so send EOF now, and ensure the client handles this properly. connection.ShutdownSend(); @@ -1347,6 +1369,7 @@ public async Task GoAwayFrame_AbortAllPendingStreams_StreamFailWithExpectedExcep server.AllowMultipleConnections = true; (_, Http2LoopbackConnection connection) = await EstablishConnectionAndProcessOneRequestAsync(client, server); + connection.SetupAutomaticPingResponse(); // Respond to RTT PINGs // Issue three requests, we want to make sure the specific task is related with specific stream Task sendTask1 = client.GetAsync("request1"); @@ -1387,6 +1410,7 @@ public async Task GoAwayFrame_AbortAllPendingStreams_StreamFailWithExpectedExcep string headerData = Encoding.UTF8.GetString(retriedFrame.Data.Span); await newConnection.SendDefaultResponseHeadersAsync(retriedStreamId); + newConnection.SetupAutomaticPingResponse(); await newConnection.SendResponseDataAsync(retriedStreamId, new byte[3], endStream: true); Assert.Contains("request1", headerData); @@ -2310,6 +2334,8 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async url => (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync(readBody : false); Assert.Equal("100-continue", requestData.GetSingleHeaderValue("Expect")); + connection.SetupAutomaticPingResponse(); + if (send100Continue) { await connection.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.Continue); @@ -2356,6 +2382,7 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async url => // Reject content with 403. await connection.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.Forbidden); + connection.SetupAutomaticPingResponse(); // Respond to RTT PINGs await connection.SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes(responseContent)); // Client should send empty request body @@ -2438,20 +2465,7 @@ private async Task SendAndReceiveRequestDataAsync(Memory data, Stream requ await requestStream.WriteAsync(data); await requestStream.FlushAsync(); - // TODO: Shall we make this a configurable? - TimeSpan timeout = TimeSpan.FromSeconds(30); - - Frame frame = await connection.ReadFrameAsync(timeout); - - if (frame is PingFrame pingFrame) - { - // We may receive an RTT estimation PING when sending data: - Assert.False(pingFrame.AckFlag); - await connection.SendPingAckAsync(pingFrame.Data); - - frame = await connection.ReadFrameAsync(timeout); - } - + Frame frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)); DataFrame dataFrame = (DataFrame)frame; Assert.True(data.Span.SequenceEqual(dataFrame.Data.Span)); @@ -2500,6 +2514,7 @@ public async Task PostAsyncDuplex_ClientSendsEndStream_Success() // Send response headers await connection.SendResponseHeadersAsync(streamId, endStream: false); + connection.SetupAutomaticPingResponse(); HttpResponseMessage response = await responseTask; Stream responseStream = await response.Content.ReadAsStreamAsync(); @@ -2560,6 +2575,7 @@ public async Task PostAsyncDuplex_ServerSendsEndStream_Success() // Send response headers await connection.SendResponseHeadersAsync(streamId, endStream: false); + connection.SetupAutomaticPingResponse(); // Handle RTT PING HttpResponseMessage response = await responseTask; Stream responseStream = await response.Content.ReadAsStreamAsync(); @@ -2625,6 +2641,7 @@ public async Task PostAsyncDuplex_RequestContentException_ResetsStream() // Send some data back and forth await SendAndReceiveResponseDataAsync(contentBytes, responseStream, connection, streamId); + connection.SetupAutomaticPingResponse(); // Handle RTT PING frames await SendAndReceiveResponseDataAsync(contentBytes, responseStream, connection, streamId); await SendAndReceiveRequestDataAsync(contentBytes, requestStream, connection, streamId); await SendAndReceiveRequestDataAsync(contentBytes, requestStream, connection, streamId); @@ -2677,6 +2694,7 @@ public async Task PostAsyncDuplex_RequestContentExceptionAfterResponseEndReceive // Send response headers await connection.SendResponseHeadersAsync(streamId, endStream: false); + connection.SetupAutomaticPingResponse(); // Handle RTT PING HttpResponseMessage response = await responseTask; Stream responseStream = await response.Content.ReadAsStreamAsync(); @@ -2797,6 +2815,7 @@ public async Task PostAsyncDuplex_ServerResetsStream_Throws() // Send response headers await connection.SendResponseHeadersAsync(streamId, endStream: false); + connection.SetupAutomaticPingResponse(); HttpResponseMessage response = await responseTask; Stream responseStream = await response.Content.ReadAsStreamAsync(); @@ -2821,9 +2840,6 @@ public async Task PostAsyncDuplex_ServerResetsStream_Throws() duplexContent.Fail(e); } - // We are expecting a few RTT PING frames in response to the DATA sent by the server. We should not receive any other frames at this point. - await connection.ReadAllPingFrames(respond: true).WaitAsync(TimeSpan.FromSeconds(5)); - // On handler dispose, client should shutdown the connection without sending additional frames. await connection.WaitForClientDisconnectAsync(); } @@ -3069,6 +3085,7 @@ public async Task PostAsyncDuplex_ServerCompletesResponseBodyThenResetsStreamWit // Send data to the server, even before we've received response headers. await SendAndReceiveRequestDataAsync(contentBytes, requestStream, connection, streamId); + connection.SetupAutomaticPingResponse(); // Send response headers await connection.SendResponseHeadersAsync(streamId, endStream: false); @@ -3526,6 +3543,8 @@ await Http2LoopbackServer.CreateClientAndServerAsync( // Write the response. await connection.SendDefaultResponseHeadersAsync(streamId); + // Auto-respond to all incoming RTT PINGs + connection.SetupAutomaticPingResponse(); byte[] buffer = new byte[4096]; int totalSent = 0; @@ -3796,6 +3815,7 @@ await Http2LoopbackServer.CreateClientAndServerAsync( pos += HPackEncoder.EncodeHeader("header-that-gos", "bar", HPackFlags.NewIndexed, frameData.AsSpan(pos)); pos += HPackEncoder.EncodeHeader("header-that-stays", "foo", HPackFlags.NewIndexed, frameData.AsSpan(pos)); await con.WriteFrameAsync(new HeadersFrame(frameData.AsMemory(0, pos), FrameFlags.EndHeaders | FrameFlags.EndStream, 0, 0, 0, streamId)); + con.SetupAutomaticPingResponse(); // Second stream, resize the table so that the header-that-gos is removed from table, and add a new header. // 1) resize: header-that-stays: 62 @@ -3848,6 +3868,8 @@ await Http2LoopbackServer.CreateClientAndServerAsync( Debug.Assert(settings.GetHeaderTableSize() >= 4096, "Data for this theory requires a header table size of at least 4096."); + con.SetupAutomaticPingResponse(); + // First stream, create dynamic indexes. int streamId = await con.ReadRequestHeaderAsync(); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index 0ac38b257277fa..d48ede7b0aaa2c 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -2145,11 +2145,11 @@ public async Task Http2_MultipleConnectionsEnabled_OpenAndCloseMultipleConnectio { server.AllowMultipleConnections = true; List> sendTasks = new List>(); - Http2LoopbackConnection connection0 = await PrepareConnection(server, client, MaxConcurrentStreams).ConfigureAwait(false); + Http2LoopbackConnection connection0 = await PrepareConnection(server, client, MaxConcurrentStreams, setupAutomaticPingResponse: true).ConfigureAwait(false); AcquireAllStreamSlots(server, client, sendTasks, MaxConcurrentStreams); - Http2LoopbackConnection connection1 = await PrepareConnection(server, client, MaxConcurrentStreams).ConfigureAwait(false); + Http2LoopbackConnection connection1 = await PrepareConnection(server, client, MaxConcurrentStreams, setupAutomaticPingResponse: true).ConfigureAwait(false); AcquireAllStreamSlots(server, client, sendTasks, MaxConcurrentStreams); - Http2LoopbackConnection connection2 = await PrepareConnection(server, client, MaxConcurrentStreams).ConfigureAwait(false); + Http2LoopbackConnection connection2 = await PrepareConnection(server, client, MaxConcurrentStreams, setupAutomaticPingResponse: true).ConfigureAwait(false); AcquireAllStreamSlots(server, client, sendTasks, MaxConcurrentStreams); Task<(int Count, int LastStreamId)>[] handleRequestTasks = new[] { @@ -2170,9 +2170,9 @@ public async Task Http2_MultipleConnectionsEnabled_OpenAndCloseMultipleConnectio //Fill all connection1's stream slots AcquireAllStreamSlots(server, client, sendTasks, MaxConcurrentStreams); - Http2LoopbackConnection connection3 = await PrepareConnection(server, client, MaxConcurrentStreams).ConfigureAwait(false); + Http2LoopbackConnection connection3 = await PrepareConnection(server, client, MaxConcurrentStreams, setupAutomaticPingResponse: true).ConfigureAwait(false); AcquireAllStreamSlots(server, client, sendTasks, MaxConcurrentStreams); - Http2LoopbackConnection connection4 = await PrepareConnection(server, client, MaxConcurrentStreams).ConfigureAwait(false); + Http2LoopbackConnection connection4 = await PrepareConnection(server, client, MaxConcurrentStreams, setupAutomaticPingResponse: true).ConfigureAwait(false); AcquireAllStreamSlots(server, client, sendTasks, MaxConcurrentStreams); Task<(int Count, int LastStreamId)>[] finalHandleTasks = new[] { @@ -2272,10 +2272,14 @@ private async Task VerifySendTasks(IReadOnlyList> send SslOptions = { RemoteCertificateValidationCallback = delegate { return true; } } }; - private async Task PrepareConnection(Http2LoopbackServer server, HttpClient client, uint maxConcurrentStreams, int readTimeout = 3, int expectedWarmUpTasks = 1) + private async Task PrepareConnection(Http2LoopbackServer server, HttpClient client, uint maxConcurrentStreams, int readTimeout = 3, int expectedWarmUpTasks = 1, bool setupAutomaticPingResponse = false) { Task warmUpTask = client.GetAsync(server.Address); Http2LoopbackConnection connection = await GetConnection(server, maxConcurrentStreams, readTimeout).WaitAsync(TestHelper.PassingTestTimeout * 2).ConfigureAwait(false); + if (setupAutomaticPingResponse) + { + connection.SetupAutomaticPingResponse(); // Respond to RTT PING frames + } // Wait until the client confirms MaxConcurrentStreams setting took into effect. Task settingAckReceived = connection.SettingAckWaiter; while (true) From 6e0dc5e1ab60398a88fedac16da83eddaa0d95e6 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 21 Jun 2021 22:40:53 +0200 Subject: [PATCH 080/101] fix some OuterLoop tests --- .../tests/FunctionalTests/HttpClientHandlerTest.Http2.cs | 8 ++++++++ .../tests/FunctionalTests/TelemetryTest.cs | 1 + 2 files changed, 9 insertions(+) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs index e565c00a3d1f4f..f16e174d637dc6 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs @@ -1925,6 +1925,7 @@ public async Task Http2_MaxConcurrentStreams_LimitEnforced() // Process first request and send response. int streamId = await connection.ReadRequestHeaderAsync(); await connection.SendDefaultResponseAsync(streamId); + connection.SetupAutomaticPingResponse(); // Handle RTT PING HttpResponseMessage response = await sendTask; Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -1993,6 +1994,7 @@ public async Task Http2_WaitingForStream_Cancellation() // Process first request and send response. int streamId = await connection.ReadRequestHeaderAsync(); await connection.SendDefaultResponseAsync(streamId); + await connection.RespondToPingFrameAsync(); // Handle 1 RTT PING received in response to HEADERS HttpResponseMessage response = await sendTask; Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -2167,6 +2169,7 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async url => frame = null; (streamId, requestData) = await connection.ReadAndParseRequestHeaderAsync(); await connection.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.OK); + connection.SetupAutomaticPingResponse(); // Handle RTT PING await connection.SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes($"Http2_PendingSend_SendsReset(waitForData: {waitForData})"), isFinal: false); // Wait for any lingering frames or extra reset frames. try @@ -2878,6 +2881,7 @@ public async Task PostAsyncDuplex_DisposeResponseBodyBeforeEnd_ResetsStreamAndTh // Send response headers await connection.SendResponseHeadersAsync(streamId, endStream: false); + connection.SetupAutomaticPingResponse(); // Handler RTT PING HttpResponseMessage response = await responseTask; Stream responseStream = await response.Content.ReadAsStreamAsync(); @@ -2945,6 +2949,7 @@ public async Task PostAsyncDuplex_DisposeResponseBodyAfterEndReceivedButBeforeCo // Send response headers await connection.SendResponseHeadersAsync(streamId, endStream: false); + connection.SetupAutomaticPingResponse(); // Handle RTT PING HttpResponseMessage response = await responseTask; Stream responseStream = await response.Content.ReadAsStreamAsync(); @@ -3018,6 +3023,7 @@ public async Task PostAsyncDuplex_FinishRequestBodyAndDisposeResponseBodyAfterEn // Send response headers await connection.SendResponseHeadersAsync(streamId, endStream: false); + connection.SetupAutomaticPingResponse(); // Handle RTT PING HttpResponseMessage response = await responseTask; Stream responseStream = await response.Content.ReadAsStreamAsync(); @@ -3213,6 +3219,7 @@ public async Task SendAsync_ConcurentSendReceive_Ok(bool shouldWaitForRequestBod // Send response headers await connection.SendResponseHeadersAsync(streamId, endStream: false, responseCode); + connection.SetupAutomaticPingResponse(); // Handle RTT PING HttpResponseMessage response = await responseTask; Assert.Equal(responseCode, response.StatusCode); @@ -3353,6 +3360,7 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async url => Http2LoopbackConnection connection = await server.EstablishConnectionAsync(); (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync().ConfigureAwait(false); await connection.SendResponseHeadersAsync(streamId); + await connection.RespondToPingFrameAsync(); // Handle 1 RTT PING in response to HEADERS await sendAsyncCompleted.Task; await connection.WaitForConnectionShutdownAsync(); }); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/TelemetryTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/TelemetryTest.cs index dc4f2ef45ab086..2f6db3a348dec1 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/TelemetryTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/TelemetryTest.cs @@ -621,6 +621,7 @@ await GetFactoryForVersion(version).CreateClientAndServerAsync( { http2Server.AllowMultipleConnections = true; connection = await http2Server.EstablishConnectionAsync(new SettingsEntry { SettingId = SettingId.MaxConcurrentStreams, Value = 1 }); + ((Http2LoopbackConnection)connection).SetupAutomaticPingResponse(); // Handle RTT PING } else { From b02415f574db1294975f27a0f69f442eb3ff9fc4 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 22 Jun 2021 15:27:55 +0200 Subject: [PATCH 081/101] window scaling tests --- .../Net/Http/Http2LoopbackConnection.cs | 2 + .../HttpClientHandlerTest.Http2.cs | 121 ++++++++++++++++++ 2 files changed, 123 insertions(+) diff --git a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs index e44e0a1de75d21..c0a63d8ad5a009 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs @@ -235,6 +235,8 @@ private async Task ReadFrameAsync(CancellationToken cancellationToken) return GoAwayFrame.ReadFrom(header, data); case FrameType.Continuation: return ContinuationFrame.ReadFrom(header, data); + case FrameType.WindowUpdate: + return WindowUpdateFrame.ReadFrom(header, data); default: return header; } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs index f16e174d637dc6..c12469a01a7347 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs @@ -1469,6 +1469,127 @@ private static async Task ReadToEndOfStream(Http2LoopbackConnection connect const int DefaultInitialWindowSize = 65535; + [ConditionalFact(nameof(SupportsAlpn))] + public async Task Http2_FlowControl_HighBandwidthDelayProduct_ClientStreamReceiveWindowWindowScalesUp() + { + int maxCredit = await TestClientWindowScalingAsync( + TimeSpan.FromMilliseconds(30), + TimeSpan.Zero, + 2 * 1024 * 1024); + + // Expect the client receive window to grow over 1MB: + Assert.True(maxCredit > 1024 * 1024); + } + + [ConditionalFact(nameof(SupportsAlpn))] + public async Task Http2_FlowControl_LowBandwidthDelayProduct_ClientStreamReceiveWindowStopsScaling() + { + int maxCredit = await TestClientWindowScalingAsync( + TimeSpan.Zero, + TimeSpan.FromMilliseconds(15), + 2 * 1024 * 1024); + + // Expect the client receive window to stay below 1MB: + Assert.True(maxCredit < 1024 * 1024); + } + + private async Task TestClientWindowScalingAsync(TimeSpan networkDelay, TimeSpan slowBandwidthSimDelay, int bytesToDownload) + { + TimeSpan timeout = TimeSpan.FromSeconds(30); + + using Http2LoopbackServer server = Http2LoopbackServer.CreateServer(); + using HttpClient client = CreateHttpClient(); + + Task clientTask = client.GetAsync(server.Address); + Http2LoopbackConnection connection = await server.AcceptConnectionAsync().ConfigureAwait(false); + SettingsFrame clientSettingsFrame = await connection.ReadSettingsAsync().ConfigureAwait(false); + + // send server SETTINGS: + await connection.WriteFrameAsync(new SettingsFrame()).ConfigureAwait(false); + + // Initial client SETTINGS also works as a PING. Do not send ACK immediately to avoid low RTT estimation + await Task.Delay(networkDelay); + await connection.WriteFrameAsync(new SettingsFrame(FrameFlags.Ack, new SettingsEntry[0])); + + // Expect SETTINGS ACK from client: + await connection.ExpectSettingsAckAsync(); + + SettingsEntry initialWindowSettingsEntry = clientSettingsFrame.Entries.SingleOrDefault(e => e.SettingId == SettingId.InitialWindowSize); + + int maxCredit = (int)clientSettingsFrame.Entries.SingleOrDefault(e => e.SettingId == SettingId.InitialWindowSize)?.Value; + int credit = maxCredit; + + int streamId = await connection.ReadRequestHeaderAsync(); + // Write the response. + await connection.SendDefaultResponseHeadersAsync(streamId); + + using SemaphoreSlim creditReceivedSemaphore = new SemaphoreSlim(0); + using SemaphoreSlim writeSemaphore = new SemaphoreSlim(1); + int remainingBytes = bytesToDownload; + _ = Task.Run(ProcessIncomingFramesAsync); + byte[] buffer = new byte[16384]; + + while (remainingBytes > 0) + { + Wait(slowBandwidthSimDelay); + while (credit == 0) await creditReceivedSemaphore.WaitAsync(timeout); + int bytesToSend = Math.Min(Math.Min(buffer.Length, credit), remainingBytes); + + Memory responseData = buffer.AsMemory(0, bytesToSend); + + int nextRemainingBytes = remainingBytes - bytesToSend; + bool endStream = nextRemainingBytes == 0; + + await writeSemaphore.WaitAsync(); + await connection.SendResponseDataAsync(streamId, responseData, endStream); + writeSemaphore.Release(); + + credit -= bytesToSend; + + remainingBytes = nextRemainingBytes; + } + + using HttpResponseMessage response = await clientTask; + int dataReceived = (await response.Content.ReadAsByteArrayAsync()).Length; + Assert.Equal(bytesToDownload, dataReceived); + + return maxCredit; + + async Task ProcessIncomingFramesAsync() + { + while (remainingBytes > 0) + { + Frame frame = await connection.ReadFrameAsync(timeout); + + if (frame is PingFrame pingFrame) + { + // Simulate network delay for RTT PING + Wait(networkDelay); + + await writeSemaphore.WaitAsync(); + await connection.SendPingAckAsync(pingFrame.Data); + writeSemaphore.Release(); + } + else if (frame is WindowUpdateFrame windowUpdateFrame) + { + // Ignore connection window: + if (windowUpdateFrame.StreamId != streamId) continue; + + credit += windowUpdateFrame.UpdateSize; + maxCredit = Math.Max(credit, maxCredit); // Detect if client grows the window + _output.WriteLine("MaxCredit: " + maxCredit); + creditReceivedSemaphore.Release(); + } + else if (frame is not null) + { + throw new Exception("Unexpected frame: " + frame); + } + } + } + + static void Wait(TimeSpan dt) { if (dt != TimeSpan.Zero) Thread.Sleep(dt); } + } + [OuterLoop("Uses Task.Delay")] [ConditionalFact(nameof(SupportsAlpn))] public async Task Http2_FlowControl_ClientDoesNotExceedWindows() From 9a86ec9f60d0699e0b4a944820923b5950f251aa Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 22 Jun 2021 15:35:33 +0200 Subject: [PATCH 082/101] comment out _LargeFileBenchmark --- .../FunctionalTests/_LargeFileBenchmark.cs | 1432 ++++++++--------- 1 file changed, 716 insertions(+), 716 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 2eaf181b8bd19a..407f1708415b95 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -1,719 +1,719 @@ -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Diagnostics.Tracing; -using System.IO; -using System.Linq; -using System.Net.NetworkInformation; -using System.Net.Sockets; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; -using Xunit; -using Xunit.Abstractions; - -namespace System.Net.Http.Functional.Tests -{ - [CollectionDefinition("NoParallelTests", DisableParallelization = true)] - public class LargeFileBenchmark_ShouldNotBeParallell { } - - [Collection(nameof(LargeFileBenchmark_ShouldNotBeParallell))] - public class LargeFileBenchmark : IDisposable - { - private readonly ITestOutputHelper _output; - private LogHttpEventListener _listener; - - public LargeFileBenchmark(ITestOutputHelper output) - { - _output = output; - _listener = new LogHttpEventListener(output); - _listener.Filter = m => m.Contains("[FlowControl]"); - } - - public void Dispose() => _listener?.Dispose(); - - private const double LengthMb = 100; - private const int TestRunCount = 10; - - //private const string BenchmarkServer = "10.194.114.94"; - //private const string BenchmarkServer = "169.254.132.170"; // duo1 - private const string BenchmarkServer = "192.168.0.152"; - //private const string BenchmarkServer = "127.0.0.1"; - private const string BenchmarkServerGo = "192.168.0.152:5002"; - // private const string BenchmarkServer = "127.0.0.1:5000"; - - //private static readonly IPAddress LocalAddress = IPAddress.Parse("169.254.59.132"); // duo2 - private static readonly IPAddress LocalAddress = null; - - //private const string ReportDir = @"C:\_dev\r6r\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; - //private const string ReportDir = @"C:\Users\anfirszo\dev\dotnet\6.0\runtime\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; - private const string ReportDir = @"C:\_dev\r6d\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Debug\TestResults"; - - [Theory] - [InlineData(BenchmarkServer)] - public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run1", hostName, false, LengthMb, details: "http1.1"); - - [Theory] - [InlineData(BenchmarkServer, 1024)] - [InlineData(BenchmarkServer, 2048)] - [InlineData(BenchmarkServer, 4096)] - [InlineData(BenchmarkServer, 8192)] - [InlineData(BenchmarkServer, 16384)] - public Task Download20_SpecificWindow_MegaBytes(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - [Theory] - [InlineData(BenchmarkServer, 64)] - [InlineData(BenchmarkServer, 128)] - [InlineData(BenchmarkServer, 256)] - [InlineData(BenchmarkServer, 512)] - public Task Download20_SpecificWindow_KiloBytes(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - - private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) - { - SocketsHttpHandler handler = new SocketsHttpHandler() - { - EnableDynamicHttp2StreamWindowSizing = false, - InitialStreamWindowSize = initialWindowKbytes * 1024 - }; - string details = $"SpecificWindow({initialWindowKbytes})"; - return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler, details); - } - - public static TheoryData Download20_Data = new TheoryData - { - { BenchmarkServer, 8, 1 }, - { BenchmarkServer, 8, 2 }, - { BenchmarkServer, 8, 4 }, - { BenchmarkServer, 8, 8 }, - { BenchmarkServer, 4, 1 }, - { BenchmarkServer, 4, 2 }, - { BenchmarkServer, 4, 4 }, - //{ BenchmarkServerGo, 8, 0.5 }, - //{ BenchmarkServerGo, 8, 0.25 }, - //{ BenchmarkServerGo, 8, 0.125 }, - //{ BenchmarkServerGo, 4, 0.5 }, - //{ BenchmarkServerGo, 4, 0.25 }, - }; - - public static TheoryData Download20_Data8 = new TheoryData - { - { BenchmarkServer, 8, 1 }, - { BenchmarkServer, 8, 2 }, - { BenchmarkServer, 8, 4 }, - { BenchmarkServer, 8, 8 }, - { BenchmarkServer, 8, 16 }, - }; - - - public static TheoryData Download20_Data4 = new TheoryData - { - { BenchmarkServer, 4, 1 }, - { BenchmarkServer, 4, 2 }, - { BenchmarkServer, 4, 4 }, - { BenchmarkServer, 4, 8 }, - }; - - [Theory] - [MemberData(nameof(Download20_Data8))] - public Task Download20_StaticRtt_8(string hostName, int ratio, int correction) => Download20_StaticRtt(hostName, ratio, correction); - - [Theory] - [MemberData(nameof(Download20_Data4))] - public Task Download20_StaticRtt_4(string hostName, int ratio, int correction) => Download20_StaticRtt(hostName, ratio, correction); - - private async Task Download20_StaticRtt(string hostName, int ratio, int correction) - { - _listener.Enabled = true; - _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); - var handler = new SocketsHttpHandler - { - FakeRtt = await EstimateRttAsync(hostName), - StreamWindowUpdateRatio = ratio, - StreamWindowThresholdMultiplier = correction - }; - - string details = $"StaticRtt_R({ratio})_C({correction})"; - await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT | host:{hostName} ratio={ratio} correction={handler.StreamWindowThresholdMultiplier}", - hostName, true, LengthMb, handler, details); - } - - [Theory] - [MemberData(nameof(Download20_Data8))] - public Task Download20_Dynamic_SingleStream_8(string hostName, int ratio, int correction) => Download20_Dynamic_SingleStream(hostName, ratio, correction); - - [Theory] - [MemberData(nameof(Download20_Data4))] - public Task Download20_Dynamic_SingleStream_4(string hostName, int ratio, int correction) => Download20_Dynamic_SingleStream(hostName, ratio, correction); - - [Fact] - public Task Download20_Dynamic_Test() - { - _listener.Enabled = true; - return Download20_Dynamic_SingleStream(BenchmarkServer, 8, 8, true); - } - - private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, int correction, bool keepFilter = false) - { - _listener.Enabled = true; - if (!keepFilter) - { - _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); - } +//// The .NET Foundation licenses this file to you under the MIT license. + +//using System.Collections.Generic; +//using System.ComponentModel; +//using System.Diagnostics; +//using System.Diagnostics.Tracing; +//using System.IO; +//using System.Linq; +//using System.Net.NetworkInformation; +//using System.Net.Sockets; +//using System.Runtime.InteropServices; +//using System.Text; +//using System.Threading; +//using System.Threading.Channels; +//using System.Threading.Tasks; +//using Xunit; +//using Xunit.Abstractions; + +//namespace System.Net.Http.Functional.Tests +//{ +// [CollectionDefinition("NoParallelTests", DisableParallelization = true)] +// public class LargeFileBenchmark_ShouldNotBeParallell { } + +// [Collection(nameof(LargeFileBenchmark_ShouldNotBeParallell))] +// public class LargeFileBenchmark : IDisposable +// { +// private readonly ITestOutputHelper _output; +// private LogHttpEventListener _listener; + +// public LargeFileBenchmark(ITestOutputHelper output) +// { +// _output = output; +// _listener = new LogHttpEventListener(output); +// _listener.Filter = m => m.Contains("[FlowControl]"); +// } + +// public void Dispose() => _listener?.Dispose(); + +// private const double LengthMb = 100; +// private const int TestRunCount = 10; + +// //private const string BenchmarkServer = "10.194.114.94"; +// //private const string BenchmarkServer = "169.254.132.170"; // duo1 +// private const string BenchmarkServer = "192.168.0.152"; +// //private const string BenchmarkServer = "127.0.0.1"; +// private const string BenchmarkServerGo = "192.168.0.152:5002"; +// // private const string BenchmarkServer = "127.0.0.1:5000"; + +// //private static readonly IPAddress LocalAddress = IPAddress.Parse("169.254.59.132"); // duo2 +// private static readonly IPAddress LocalAddress = null; + +// //private const string ReportDir = @"C:\_dev\r6r\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; +// //private const string ReportDir = @"C:\Users\anfirszo\dev\dotnet\6.0\runtime\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; +// private const string ReportDir = @"C:\_dev\r6d\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Debug\TestResults"; + +// [Theory] +// [InlineData(BenchmarkServer)] +// public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run1", hostName, false, LengthMb, details: "http1.1"); + +// [Theory] +// [InlineData(BenchmarkServer, 1024)] +// [InlineData(BenchmarkServer, 2048)] +// [InlineData(BenchmarkServer, 4096)] +// [InlineData(BenchmarkServer, 8192)] +// [InlineData(BenchmarkServer, 16384)] +// public Task Download20_SpecificWindow_MegaBytes(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + +// [Theory] +// [InlineData(BenchmarkServer, 64)] +// [InlineData(BenchmarkServer, 128)] +// [InlineData(BenchmarkServer, 256)] +// [InlineData(BenchmarkServer, 512)] +// public Task Download20_SpecificWindow_KiloBytes(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + +// private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) +// { +// SocketsHttpHandler handler = new SocketsHttpHandler() +// { +// EnableDynamicHttp2StreamWindowSizing = false, +// InitialStreamWindowSize = initialWindowKbytes * 1024 +// }; +// string details = $"SpecificWindow({initialWindowKbytes})"; +// return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler, details); +// } + +// public static TheoryData Download20_Data = new TheoryData +// { +// { BenchmarkServer, 8, 1 }, +// { BenchmarkServer, 8, 2 }, +// { BenchmarkServer, 8, 4 }, +// { BenchmarkServer, 8, 8 }, +// { BenchmarkServer, 4, 1 }, +// { BenchmarkServer, 4, 2 }, +// { BenchmarkServer, 4, 4 }, +// //{ BenchmarkServerGo, 8, 0.5 }, +// //{ BenchmarkServerGo, 8, 0.25 }, +// //{ BenchmarkServerGo, 8, 0.125 }, +// //{ BenchmarkServerGo, 4, 0.5 }, +// //{ BenchmarkServerGo, 4, 0.25 }, +// }; + +// public static TheoryData Download20_Data8 = new TheoryData +// { +// { BenchmarkServer, 8, 1 }, +// { BenchmarkServer, 8, 2 }, +// { BenchmarkServer, 8, 4 }, +// { BenchmarkServer, 8, 8 }, +// { BenchmarkServer, 8, 16 }, +// }; + + +// public static TheoryData Download20_Data4 = new TheoryData +// { +// { BenchmarkServer, 4, 1 }, +// { BenchmarkServer, 4, 2 }, +// { BenchmarkServer, 4, 4 }, +// { BenchmarkServer, 4, 8 }, +// }; + +// [Theory] +// [MemberData(nameof(Download20_Data8))] +// public Task Download20_StaticRtt_8(string hostName, int ratio, int correction) => Download20_StaticRtt(hostName, ratio, correction); + +// [Theory] +// [MemberData(nameof(Download20_Data4))] +// public Task Download20_StaticRtt_4(string hostName, int ratio, int correction) => Download20_StaticRtt(hostName, ratio, correction); + +// private async Task Download20_StaticRtt(string hostName, int ratio, int correction) +// { +// _listener.Enabled = true; +// _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); +// var handler = new SocketsHttpHandler +// { +// FakeRtt = await EstimateRttAsync(hostName), +// StreamWindowUpdateRatio = ratio, +// StreamWindowThresholdMultiplier = correction +// }; + +// string details = $"StaticRtt_R({ratio})_C({correction})"; +// await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT | host:{hostName} ratio={ratio} correction={handler.StreamWindowThresholdMultiplier}", +// hostName, true, LengthMb, handler, details); +// } + +// [Theory] +// [MemberData(nameof(Download20_Data8))] +// public Task Download20_Dynamic_SingleStream_8(string hostName, int ratio, int correction) => Download20_Dynamic_SingleStream(hostName, ratio, correction); + +// [Theory] +// [MemberData(nameof(Download20_Data4))] +// public Task Download20_Dynamic_SingleStream_4(string hostName, int ratio, int correction) => Download20_Dynamic_SingleStream(hostName, ratio, correction); + +// [Fact] +// public Task Download20_Dynamic_Test() +// { +// _listener.Enabled = true; +// return Download20_Dynamic_SingleStream(BenchmarkServer, 8, 8, true); +// } + +// private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, int correction, bool keepFilter = false) +// { +// _listener.Enabled = true; +// if (!keepFilter) +// { +// _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); +// } - var handler = new SocketsHttpHandler() - { - StreamWindowUpdateRatio = ratio, - StreamWindowThresholdMultiplier = correction - }; - string details = $"Dynamic_R({ratio})_C({correction})"; - await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio} correction={handler.StreamWindowThresholdMultiplier}", - hostName, true, LengthMb, handler, details); - } - - [Theory] - [InlineData(BenchmarkServer)] - //[InlineData("10.194.114.94:5001")] - //[InlineData("10.194.114.94:5002")] - public async Task Download20_Dynamic_MultiStream(string hostName) - { - _listener.Enabled = true; - var handler = new SocketsHttpHandler(); - using var client = new HttpClient(handler, true); - client.Timeout = TimeSpan.FromMinutes(3); - const int NStreams = 4; - string info = $"SocketsHttpHandler HTTP 2.0 Dynamic {NStreams} concurrent streams"; - - - var message = GenerateRequestMessage(hostName, true, LengthMb); - _output.WriteLine($"{info} / {LengthMb} MB from {message.RequestUri}"); - - Stopwatch sw = Stopwatch.StartNew(); - List tasks = new List(); - for (int i = 0; i < NStreams; i++) - { - var task = Task.Run(() => client.SendAsync(GenerateRequestMessage(hostName, true, LengthMb))); - tasks.Add(task); - } - - await Task.WhenAll(tasks); - - double elapsedSec = sw.ElapsedMilliseconds * 0.001; - _output.WriteLine($"{info}: completed in {elapsedSec} sec"); - } - - private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler = null, string details = "") - { - handler ??= new SocketsHttpHandler(); - - if (LocalAddress != null) handler.ConnectCallback = CustomConnect; - - string reportFileName = CreateOutputFile(details); - _output.WriteLine("REPORT: " + reportFileName); - using StreamWriter report = new StreamWriter(reportFileName); - - _output.WriteLine($"############ Warmup Run ############"); - await TestHandlerCore(info, hostName, http2, lengthMb, handler, null); - - for (int i = 0; i < TestRunCount; i++) - { - _output.WriteLine($"############ run {i} ############"); - await TestHandlerCore(info, hostName, http2, lengthMb, handler, report); - } - handler.Dispose(); - } - - private static string CreateOutputFile(string details) - { - if (!Directory.Exists(ReportDir)) Directory.CreateDirectory(ReportDir); - return Path.Combine(ReportDir, $"report_{Environment.TickCount64}_{details}.csv"); - } - - private async Task TestHandlerCore(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler, StreamWriter report) - { - _listener.Log2.Clear(); - using var client = new HttpClient(CopyHandler(handler), true); - client.Timeout = TimeSpan.FromMinutes(3); - var message = GenerateRequestMessage(hostName, http2, lengthMb); - _output.WriteLine($"{info} / {lengthMb} MB from {message.RequestUri}"); - Stopwatch sw = Stopwatch.StartNew(); - var response = await client.SendAsync(message); - - double elapsedSec = sw.ElapsedMilliseconds * 0.001; - elapsedSec = Math.Round(elapsedSec, 3); - _output.WriteLine($"{info}: completed in {elapsedSec} sec"); - - if (report != null) - { - report.Write(elapsedSec); - double? window = GetStreamWindowSizeInMegabytes(); - if (window.HasValue) report.Write($", {window}"); - double? rtt = GetRtt(); - if (rtt.HasValue) report.Write($", {rtt}"); - report.WriteLine(); - } - } - - private double? GetStreamWindowSizeInMegabytes() - { - const string Prefix = "Updated Stream Window. StreamWindowSize: "; - string log = _listener.Log2.ToString(); - - int idx = log.LastIndexOf(Prefix); - if (idx < 0) return null; - ReadOnlySpan text = log.AsSpan().Slice(idx + Prefix.Length); - text = text.Slice(0, text.IndexOf(',')); - - double size = int.Parse(text); - double sizeMb = size / 1024 / 1024; - return Math.Round(sizeMb, 3); - } - - private double? GetRtt() - { - const string Prefix = "Updated MinRtt: "; - string log = _listener.Log2.ToString(); - - int idx = log.LastIndexOf(Prefix); - if (idx < 0) return null; +// var handler = new SocketsHttpHandler() +// { +// StreamWindowUpdateRatio = ratio, +// StreamWindowThresholdMultiplier = correction +// }; +// string details = $"Dynamic_R({ratio})_C({correction})"; +// await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio} correction={handler.StreamWindowThresholdMultiplier}", +// hostName, true, LengthMb, handler, details); +// } + +// [Theory] +// [InlineData(BenchmarkServer)] +// //[InlineData("10.194.114.94:5001")] +// //[InlineData("10.194.114.94:5002")] +// public async Task Download20_Dynamic_MultiStream(string hostName) +// { +// _listener.Enabled = true; +// var handler = new SocketsHttpHandler(); +// using var client = new HttpClient(handler, true); +// client.Timeout = TimeSpan.FromMinutes(3); +// const int NStreams = 4; +// string info = $"SocketsHttpHandler HTTP 2.0 Dynamic {NStreams} concurrent streams"; + + +// var message = GenerateRequestMessage(hostName, true, LengthMb); +// _output.WriteLine($"{info} / {LengthMb} MB from {message.RequestUri}"); + +// Stopwatch sw = Stopwatch.StartNew(); +// List tasks = new List(); +// for (int i = 0; i < NStreams; i++) +// { +// var task = Task.Run(() => client.SendAsync(GenerateRequestMessage(hostName, true, LengthMb))); +// tasks.Add(task); +// } + +// await Task.WhenAll(tasks); + +// double elapsedSec = sw.ElapsedMilliseconds * 0.001; +// _output.WriteLine($"{info}: completed in {elapsedSec} sec"); +// } + +// private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler = null, string details = "") +// { +// handler ??= new SocketsHttpHandler(); + +// if (LocalAddress != null) handler.ConnectCallback = CustomConnect; + +// string reportFileName = CreateOutputFile(details); +// _output.WriteLine("REPORT: " + reportFileName); +// using StreamWriter report = new StreamWriter(reportFileName); + +// _output.WriteLine($"############ Warmup Run ############"); +// await TestHandlerCore(info, hostName, http2, lengthMb, handler, null); + +// for (int i = 0; i < TestRunCount; i++) +// { +// _output.WriteLine($"############ run {i} ############"); +// await TestHandlerCore(info, hostName, http2, lengthMb, handler, report); +// } +// handler.Dispose(); +// } + +// private static string CreateOutputFile(string details) +// { +// if (!Directory.Exists(ReportDir)) Directory.CreateDirectory(ReportDir); +// return Path.Combine(ReportDir, $"report_{Environment.TickCount64}_{details}.csv"); +// } + +// private async Task TestHandlerCore(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler, StreamWriter report) +// { +// _listener.Log2.Clear(); +// using var client = new HttpClient(CopyHandler(handler), true); +// client.Timeout = TimeSpan.FromMinutes(3); +// var message = GenerateRequestMessage(hostName, http2, lengthMb); +// _output.WriteLine($"{info} / {lengthMb} MB from {message.RequestUri}"); +// Stopwatch sw = Stopwatch.StartNew(); +// var response = await client.SendAsync(message); + +// double elapsedSec = sw.ElapsedMilliseconds * 0.001; +// elapsedSec = Math.Round(elapsedSec, 3); +// _output.WriteLine($"{info}: completed in {elapsedSec} sec"); + +// if (report != null) +// { +// report.Write(elapsedSec); +// double? window = GetStreamWindowSizeInMegabytes(); +// if (window.HasValue) report.Write($", {window}"); +// double? rtt = GetRtt(); +// if (rtt.HasValue) report.Write($", {rtt}"); +// report.WriteLine(); +// } +// } + +// private double? GetStreamWindowSizeInMegabytes() +// { +// const string Prefix = "Updated Stream Window. StreamWindowSize: "; +// string log = _listener.Log2.ToString(); + +// int idx = log.LastIndexOf(Prefix); +// if (idx < 0) return null; +// ReadOnlySpan text = log.AsSpan().Slice(idx + Prefix.Length); +// text = text.Slice(0, text.IndexOf(',')); + +// double size = int.Parse(text); +// double sizeMb = size / 1024 / 1024; +// return Math.Round(sizeMb, 3); +// } + +// private double? GetRtt() +// { +// const string Prefix = "Updated MinRtt: "; +// string log = _listener.Log2.ToString(); + +// int idx = log.LastIndexOf(Prefix); +// if (idx < 0) return null; - ReadOnlySpan text = log.AsSpan().Slice(idx + Prefix.Length); - text = text.Slice(0, text.IndexOf(' ')); - - double rtt = double.Parse(text); - return Math.Round(rtt, 3); - } - - private static SocketsHttpHandler CopyHandler(SocketsHttpHandler h) - { - return new SocketsHttpHandler() - { - FakeRtt = h.FakeRtt, - EnableDynamicHttp2StreamWindowSizing = h.EnableDynamicHttp2StreamWindowSizing, - InitialStreamWindowSize = h.InitialStreamWindowSize, - StreamWindowUpdateRatio = h.StreamWindowUpdateRatio, - StreamWindowThresholdMultiplier = h.StreamWindowThresholdMultiplier, - ConnectCallback = h.ConnectCallback - }; - } - - private static async ValueTask CustomConnect(SocketsHttpConnectionContext ctx, CancellationToken cancellationToken) - { - Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) - { - NoDelay = true - }; - socket.Bind(new IPEndPoint(LocalAddress, 0)); - - try - { - await socket.ConnectAsync(ctx.DnsEndPoint, cancellationToken).ConfigureAwait(false); - return new NetworkStream(socket, ownsSocket: true); - } - catch - { - socket.Dispose(); - throw; - } - } - - [Fact] - public async Task TestEstimateRtt() - { - TimeSpan rtt = await EstimateRttAsync(BenchmarkServer); - _output.WriteLine($"RTT: {rtt.TotalMilliseconds} ms"); - } - - private async Task EstimateRttAsync(string hostName) - { - int sep = hostName.IndexOf(':'); - if (sep > 0) - { - hostName = hostName.Substring(0, sep); - } - - IPAddress addr; - if (!IPAddress.TryParse(hostName, out addr)) - { - addr = (await Dns.GetHostAddressesAsync(hostName)).FirstOrDefault(e => e.AddressFamily == AddressFamily.InterNetwork); - } - - IPAddress destAddr = (await Dns.GetHostAddressesAsync(hostName))[0]; - - // warmup: - PingEx.Send(LocalAddress, destAddr); - - IPAddress local = LocalAddress != null ? LocalAddress : IPAddress.Loopback; - - var reply1 = PingEx.Send(LocalAddress, destAddr).RoundTripTime; - var reply2 = PingEx.Send(LocalAddress, destAddr).RoundTripTime; - - TimeSpan rtt = reply1 > reply2 ? reply1 : reply2; - - _output.WriteLine($"Estimated RTT: {rtt}"); - if (rtt < TimeSpan.FromMilliseconds(1)) - { - _output.WriteLine("RTT < 1 ms, changing to 1 ms!"); - rtt = TimeSpan.FromMilliseconds(1); - } - - return rtt; - } - - - static HttpRequestMessage GenerateRequestMessage(string hostName, bool http2, double lengthMb = 5) - { - int port = http2 ? 5001 : 5000; - int sep = hostName.IndexOf(':'); - if (sep > 0) - { - string portStr = hostName.Substring(sep + 1, hostName.Length - sep - 1); - int.TryParse(portStr, out port); - hostName = hostName.Substring(0, sep); - } - - string url = $"http://{hostName}:{port}?lengthMb={lengthMb}"; - var msg = new HttpRequestMessage(HttpMethod.Get, url) - { - Version = new Version(1, 1) - }; - - if (http2) - { - msg.Version = new Version(2, 0); - msg.VersionPolicy = HttpVersionPolicy.RequestVersionExact; - } +// ReadOnlySpan text = log.AsSpan().Slice(idx + Prefix.Length); +// text = text.Slice(0, text.IndexOf(' ')); + +// double rtt = double.Parse(text); +// return Math.Round(rtt, 3); +// } + +// private static SocketsHttpHandler CopyHandler(SocketsHttpHandler h) +// { +// return new SocketsHttpHandler() +// { +// FakeRtt = h.FakeRtt, +// EnableDynamicHttp2StreamWindowSizing = h.EnableDynamicHttp2StreamWindowSizing, +// InitialStreamWindowSize = h.InitialStreamWindowSize, +// StreamWindowUpdateRatio = h.StreamWindowUpdateRatio, +// StreamWindowThresholdMultiplier = h.StreamWindowThresholdMultiplier, +// ConnectCallback = h.ConnectCallback +// }; +// } + +// private static async ValueTask CustomConnect(SocketsHttpConnectionContext ctx, CancellationToken cancellationToken) +// { +// Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) +// { +// NoDelay = true +// }; +// socket.Bind(new IPEndPoint(LocalAddress, 0)); + +// try +// { +// await socket.ConnectAsync(ctx.DnsEndPoint, cancellationToken).ConfigureAwait(false); +// return new NetworkStream(socket, ownsSocket: true); +// } +// catch +// { +// socket.Dispose(); +// throw; +// } +// } + +// [Fact] +// public async Task TestEstimateRtt() +// { +// TimeSpan rtt = await EstimateRttAsync(BenchmarkServer); +// _output.WriteLine($"RTT: {rtt.TotalMilliseconds} ms"); +// } + +// private async Task EstimateRttAsync(string hostName) +// { +// int sep = hostName.IndexOf(':'); +// if (sep > 0) +// { +// hostName = hostName.Substring(0, sep); +// } + +// IPAddress addr; +// if (!IPAddress.TryParse(hostName, out addr)) +// { +// addr = (await Dns.GetHostAddressesAsync(hostName)).FirstOrDefault(e => e.AddressFamily == AddressFamily.InterNetwork); +// } + +// IPAddress destAddr = (await Dns.GetHostAddressesAsync(hostName))[0]; + +// // warmup: +// PingEx.Send(LocalAddress, destAddr); + +// IPAddress local = LocalAddress != null ? LocalAddress : IPAddress.Loopback; + +// var reply1 = PingEx.Send(LocalAddress, destAddr).RoundTripTime; +// var reply2 = PingEx.Send(LocalAddress, destAddr).RoundTripTime; + +// TimeSpan rtt = reply1 > reply2 ? reply1 : reply2; + +// _output.WriteLine($"Estimated RTT: {rtt}"); +// if (rtt < TimeSpan.FromMilliseconds(1)) +// { +// _output.WriteLine("RTT < 1 ms, changing to 1 ms!"); +// rtt = TimeSpan.FromMilliseconds(1); +// } + +// return rtt; +// } + + +// static HttpRequestMessage GenerateRequestMessage(string hostName, bool http2, double lengthMb = 5) +// { +// int port = http2 ? 5001 : 5000; +// int sep = hostName.IndexOf(':'); +// if (sep > 0) +// { +// string portStr = hostName.Substring(sep + 1, hostName.Length - sep - 1); +// int.TryParse(portStr, out port); +// hostName = hostName.Substring(0, sep); +// } + +// string url = $"http://{hostName}:{port}?lengthMb={lengthMb}"; +// var msg = new HttpRequestMessage(HttpMethod.Get, url) +// { +// Version = new Version(1, 1) +// }; + +// if (http2) +// { +// msg.Version = new Version(2, 0); +// msg.VersionPolicy = HttpVersionPolicy.RequestVersionExact; +// } - return msg; - } - } - - public sealed class LogHttpEventListener : EventListener - { - private Channel _messagesChannel = Channel.CreateUnbounded(); - private Task _processMessages; - private CancellationTokenSource _stopProcessing; - private ITestOutputHelper _log; - - public StringBuilder Log2 { get; } - - public LogHttpEventListener(ITestOutputHelper log) - { - _log = log; - _messagesChannel = Channel.CreateUnbounded(); - _processMessages = ProcessMessagesAsync(); - _stopProcessing = new CancellationTokenSource(); - Log2 = new StringBuilder(1024 * 1024); - } - - public bool Enabled { get; set; } - public Predicate Filter { get; set; } = _ => true; - - protected override void OnEventSourceCreated(EventSource eventSource) - { - if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http") - { - EnableEvents(eventSource, EventLevel.LogAlways); - } - } - - private async Task ProcessMessagesAsync() - { - await Task.Yield(); - - try - { - await foreach (string message in _messagesChannel.Reader.ReadAllAsync(_stopProcessing.Token)) - { - if (Filter(message)) - { - _log.WriteLine(message); - Log2.AppendLine(message); - } - } - } - catch (OperationCanceledException) - { - return; - } - } - - protected override async void OnEventWritten(EventWrittenEventArgs eventData) - { - if (!Enabled) return; - - var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] "); - for (int i = 0; i < eventData.Payload?.Count; i++) - { - if (i > 0) - { - sb.Append(", "); - } - sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]); - } - await _messagesChannel.Writer.WriteAsync(sb.ToString()); - } - - public override void Dispose() - { - base.Dispose(); - var timeout = TimeSpan.FromSeconds(2); - - if (!_processMessages.Wait(timeout)) - { - _stopProcessing.Cancel(); - _processMessages.Wait(timeout); - } - } - } - - // https://stackoverflow.com/a/66380228/797482 - internal static class PingEx - { - /// - /// Pass in the IP you want to ping as a string along with the name of the NIC on your machine that - /// you want to send the ping from. - /// - /// The destination IP as a string ex. '10.10.10.1' - /// The name of the NIC ex. 'LECO Hardware'. Non-case sensitive. - /// - public static bool PingIpFromNic(string ipToPing, string nicName) - { - var sourceIpStr = GetIpOfNicFromName(nicName); - - if (sourceIpStr == "") - { - return false; - } - - var p = Send( - srcAddress: IPAddress.Parse(sourceIpStr), - destAddress: IPAddress.Parse(ipToPing)); - - return p.Status == IPStatus.Success; - } - - /// - /// Pass in the name of a NIC on your machine and this method will return the IPV4 address of it. - /// - /// The name of the NIC you want the IP of ex. 'TE Hardware' - /// - public static string GetIpOfNicFromName(string nicName) - { - var adapters = NetworkInterface.GetAllNetworkInterfaces(); - - foreach (var adapter in adapters) - { - // Ignoring case in NIC name - if (!string.Equals(adapter.Name, nicName, StringComparison.CurrentCultureIgnoreCase)) continue; - - foreach (var uni in adapter.GetIPProperties().UnicastAddresses) - { - // Return the first one found - return uni.Address.ToString(); - } - } - - return ""; - } - - public static PingReplyEx Send(IPAddress srcAddress, IPAddress destAddress, - int timeout = 5000, - byte[] buffer = null, PingOptions po = null) - { - if (destAddress == null || destAddress.AddressFamily != AddressFamily.InterNetwork || - destAddress.Equals(IPAddress.Any)) - throw new ArgumentException(); - - //Defining pinvoke args - var source = srcAddress == null ? 0 : BitConverter.ToUInt32(srcAddress.GetAddressBytes(), 0); - - var destination = BitConverter.ToUInt32(destAddress.GetAddressBytes(), 0); - - var sendBuffer = buffer ?? new byte[] { }; - - var options = new Interop.Option - { - Ttl = (po == null ? (byte)255 : (byte)po.Ttl), - Flags = (po == null ? (byte)0 : po.DontFragment ? (byte)0x02 : (byte)0) //0x02 - }; - - var fullReplyBufferSize = - Interop.ReplyMarshalLength + - sendBuffer.Length; //Size of Reply struct and the transmitted buffer length. - - var allocSpace = - Marshal.AllocHGlobal( - fullReplyBufferSize); // unmanaged allocation of reply size. TODO Maybe should be allocated on stack - try - { - var start = DateTime.Now; - var nativeCode = Interop.IcmpSendEcho2Ex( - Interop.IcmpHandle, //_In_ HANDLE IcmpHandle, - Event: default(IntPtr), //_In_opt_ HANDLE Event, - apcRoutine: default(IntPtr), //_In_opt_ PIO_APC_ROUTINE ApcRoutine, - apcContext: default(IntPtr), //_In_opt_ PVOID ApcContext - source, //_In_ IPAddr SourceAddress, - destination, //_In_ IPAddr DestinationAddress, - sendBuffer, //_In_ LPVOID RequestData, - (short)sendBuffer.Length, //_In_ WORD RequestSize, - ref options, //_In_opt_ PIP_OPTION_INFORMATION RequestOptions, - replyBuffer: allocSpace, //_Out_ LPVOID ReplyBuffer, - fullReplyBufferSize, //_In_ DWORD ReplySize, - timeout //_In_ DWORD Timeout - ); - - var duration = DateTime.Now - start; - - var reply = (Interop.Reply)Marshal.PtrToStructure(allocSpace, - typeof(Interop.Reply)); // Parse the beginning of reply memory to reply struct - - byte[] replyBuffer = null; - if (sendBuffer.Length != 0) - { - replyBuffer = new byte[sendBuffer.Length]; - Marshal.Copy(allocSpace + Interop.ReplyMarshalLength, replyBuffer, 0, - sendBuffer.Length); //copy the rest of the reply memory to managed byte[] - } - - if (nativeCode == 0) //Means that native method is faulted. - return new PingReplyEx(nativeCode, reply.Status, - new IPAddress(reply.Address), duration); - else - return new PingReplyEx(nativeCode, reply.Status, - new IPAddress(reply.Address), reply.RoundTripTime, - replyBuffer); - } - finally - { - Marshal.FreeHGlobal(allocSpace); //free allocated space - } - } - - - /// Interoperability Helper - /// - /// - public static class Interop - { - private static IntPtr? _icmpHandle; - private static int? _replyStructLength; - - /// Returns the application legal icmp handle. Should be close by IcmpCloseHandle - /// - /// - public static IntPtr IcmpHandle - { - get - { - if (_icmpHandle == null) - { - _icmpHandle = IcmpCreateFile(); - //TODO Close Icmp Handle appropriate - } - - return _icmpHandle.GetValueOrDefault(); - } - } - - /// Returns the the marshaled size of the reply struct. - public static int ReplyMarshalLength - { - get - { - if (_replyStructLength == null) - { - _replyStructLength = Marshal.SizeOf(typeof(Reply)); - } - return _replyStructLength.GetValueOrDefault(); - } - } - - - [DllImport("Iphlpapi.dll", SetLastError = true)] - private static extern IntPtr IcmpCreateFile(); - [DllImport("Iphlpapi.dll", SetLastError = true)] - private static extern bool IcmpCloseHandle(IntPtr handle); - [DllImport("Iphlpapi.dll", SetLastError = true)] - public static extern uint IcmpSendEcho2Ex(IntPtr icmpHandle, IntPtr Event, IntPtr apcRoutine, IntPtr apcContext, uint sourceAddress, UInt32 destinationAddress, byte[] requestData, short requestSize, ref Option requestOptions, IntPtr replyBuffer, int replySize, int timeout); - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - public struct Option - { - public byte Ttl; - public readonly byte Tos; - public byte Flags; - public readonly byte OptionsSize; - public readonly IntPtr OptionsData; - } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - public struct Reply - { - public readonly UInt32 Address; - public readonly int Status; - public readonly int RoundTripTime; - public readonly short DataSize; - public readonly short Reserved; - public readonly IntPtr DataPtr; - public readonly Option Options; - } - } - - public class PingReplyEx - { - private Win32Exception _exception; - - internal PingReplyEx(uint nativeCode, int replyStatus, IPAddress ipAddress, TimeSpan duration) - { - NativeCode = nativeCode; - IpAddress = ipAddress; - if (Enum.IsDefined(typeof(IPStatus), replyStatus)) - Status = (IPStatus)replyStatus; - } - internal PingReplyEx(uint nativeCode, int replyStatus, IPAddress ipAddress, int roundTripTime, byte[] buffer) - { - NativeCode = nativeCode; - IpAddress = ipAddress; - RoundTripTime = TimeSpan.FromMilliseconds(roundTripTime); - Buffer = buffer; - if (Enum.IsDefined(typeof(IPStatus), replyStatus)) - Status = (IPStatus)replyStatus; - } - - /// Native result from IcmpSendEcho2Ex. - public uint NativeCode { get; } - - public IPStatus Status { get; } = IPStatus.Unknown; - - /// The source address of the reply. - public IPAddress IpAddress { get; } - - public byte[] Buffer { get; } - - public TimeSpan RoundTripTime { get; } = TimeSpan.Zero; - - public Win32Exception Exception - { - get - { - if (Status != IPStatus.Success) - return _exception ?? (_exception = new Win32Exception((int)NativeCode, Status.ToString())); - else - return null; - } - } - - public override string ToString() - { - if (Status == IPStatus.Success) - return Status + " from " + IpAddress + " in " + RoundTripTime + " ms with " + Buffer.Length + " bytes"; - else if (Status != IPStatus.Unknown) - return Status + " from " + IpAddress; - else - return Exception.Message + " from " + IpAddress; - } - } - } -} +// return msg; +// } +// } + +// public sealed class LogHttpEventListener : EventListener +// { +// private Channel _messagesChannel = Channel.CreateUnbounded(); +// private Task _processMessages; +// private CancellationTokenSource _stopProcessing; +// private ITestOutputHelper _log; + +// public StringBuilder Log2 { get; } + +// public LogHttpEventListener(ITestOutputHelper log) +// { +// _log = log; +// _messagesChannel = Channel.CreateUnbounded(); +// _processMessages = ProcessMessagesAsync(); +// _stopProcessing = new CancellationTokenSource(); +// Log2 = new StringBuilder(1024 * 1024); +// } + +// public bool Enabled { get; set; } +// public Predicate Filter { get; set; } = _ => true; + +// protected override void OnEventSourceCreated(EventSource eventSource) +// { +// if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http") +// { +// EnableEvents(eventSource, EventLevel.LogAlways); +// } +// } + +// private async Task ProcessMessagesAsync() +// { +// await Task.Yield(); + +// try +// { +// await foreach (string message in _messagesChannel.Reader.ReadAllAsync(_stopProcessing.Token)) +// { +// if (Filter(message)) +// { +// _log.WriteLine(message); +// Log2.AppendLine(message); +// } +// } +// } +// catch (OperationCanceledException) +// { +// return; +// } +// } + +// protected override async void OnEventWritten(EventWrittenEventArgs eventData) +// { +// if (!Enabled) return; + +// var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] "); +// for (int i = 0; i < eventData.Payload?.Count; i++) +// { +// if (i > 0) +// { +// sb.Append(", "); +// } +// sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]); +// } +// await _messagesChannel.Writer.WriteAsync(sb.ToString()); +// } + +// public override void Dispose() +// { +// base.Dispose(); +// var timeout = TimeSpan.FromSeconds(2); + +// if (!_processMessages.Wait(timeout)) +// { +// _stopProcessing.Cancel(); +// _processMessages.Wait(timeout); +// } +// } +// } + +// // https://stackoverflow.com/a/66380228/797482 +// internal static class PingEx +// { +// /// +// /// Pass in the IP you want to ping as a string along with the name of the NIC on your machine that +// /// you want to send the ping from. +// /// +// /// The destination IP as a string ex. '10.10.10.1' +// /// The name of the NIC ex. 'LECO Hardware'. Non-case sensitive. +// /// +// public static bool PingIpFromNic(string ipToPing, string nicName) +// { +// var sourceIpStr = GetIpOfNicFromName(nicName); + +// if (sourceIpStr == "") +// { +// return false; +// } + +// var p = Send( +// srcAddress: IPAddress.Parse(sourceIpStr), +// destAddress: IPAddress.Parse(ipToPing)); + +// return p.Status == IPStatus.Success; +// } + +// /// +// /// Pass in the name of a NIC on your machine and this method will return the IPV4 address of it. +// /// +// /// The name of the NIC you want the IP of ex. 'TE Hardware' +// /// +// public static string GetIpOfNicFromName(string nicName) +// { +// var adapters = NetworkInterface.GetAllNetworkInterfaces(); + +// foreach (var adapter in adapters) +// { +// // Ignoring case in NIC name +// if (!string.Equals(adapter.Name, nicName, StringComparison.CurrentCultureIgnoreCase)) continue; + +// foreach (var uni in adapter.GetIPProperties().UnicastAddresses) +// { +// // Return the first one found +// return uni.Address.ToString(); +// } +// } + +// return ""; +// } + +// public static PingReplyEx Send(IPAddress srcAddress, IPAddress destAddress, +// int timeout = 5000, +// byte[] buffer = null, PingOptions po = null) +// { +// if (destAddress == null || destAddress.AddressFamily != AddressFamily.InterNetwork || +// destAddress.Equals(IPAddress.Any)) +// throw new ArgumentException(); + +// //Defining pinvoke args +// var source = srcAddress == null ? 0 : BitConverter.ToUInt32(srcAddress.GetAddressBytes(), 0); + +// var destination = BitConverter.ToUInt32(destAddress.GetAddressBytes(), 0); + +// var sendBuffer = buffer ?? new byte[] { }; + +// var options = new Interop.Option +// { +// Ttl = (po == null ? (byte)255 : (byte)po.Ttl), +// Flags = (po == null ? (byte)0 : po.DontFragment ? (byte)0x02 : (byte)0) //0x02 +// }; + +// var fullReplyBufferSize = +// Interop.ReplyMarshalLength + +// sendBuffer.Length; //Size of Reply struct and the transmitted buffer length. + +// var allocSpace = +// Marshal.AllocHGlobal( +// fullReplyBufferSize); // unmanaged allocation of reply size. TODO Maybe should be allocated on stack +// try +// { +// var start = DateTime.Now; +// var nativeCode = Interop.IcmpSendEcho2Ex( +// Interop.IcmpHandle, //_In_ HANDLE IcmpHandle, +// Event: default(IntPtr), //_In_opt_ HANDLE Event, +// apcRoutine: default(IntPtr), //_In_opt_ PIO_APC_ROUTINE ApcRoutine, +// apcContext: default(IntPtr), //_In_opt_ PVOID ApcContext +// source, //_In_ IPAddr SourceAddress, +// destination, //_In_ IPAddr DestinationAddress, +// sendBuffer, //_In_ LPVOID RequestData, +// (short)sendBuffer.Length, //_In_ WORD RequestSize, +// ref options, //_In_opt_ PIP_OPTION_INFORMATION RequestOptions, +// replyBuffer: allocSpace, //_Out_ LPVOID ReplyBuffer, +// fullReplyBufferSize, //_In_ DWORD ReplySize, +// timeout //_In_ DWORD Timeout +// ); + +// var duration = DateTime.Now - start; + +// var reply = (Interop.Reply)Marshal.PtrToStructure(allocSpace, +// typeof(Interop.Reply)); // Parse the beginning of reply memory to reply struct + +// byte[] replyBuffer = null; +// if (sendBuffer.Length != 0) +// { +// replyBuffer = new byte[sendBuffer.Length]; +// Marshal.Copy(allocSpace + Interop.ReplyMarshalLength, replyBuffer, 0, +// sendBuffer.Length); //copy the rest of the reply memory to managed byte[] +// } + +// if (nativeCode == 0) //Means that native method is faulted. +// return new PingReplyEx(nativeCode, reply.Status, +// new IPAddress(reply.Address), duration); +// else +// return new PingReplyEx(nativeCode, reply.Status, +// new IPAddress(reply.Address), reply.RoundTripTime, +// replyBuffer); +// } +// finally +// { +// Marshal.FreeHGlobal(allocSpace); //free allocated space +// } +// } + + +// /// Interoperability Helper +// /// +// /// +// public static class Interop +// { +// private static IntPtr? _icmpHandle; +// private static int? _replyStructLength; + +// /// Returns the application legal icmp handle. Should be close by IcmpCloseHandle +// /// +// /// +// public static IntPtr IcmpHandle +// { +// get +// { +// if (_icmpHandle == null) +// { +// _icmpHandle = IcmpCreateFile(); +// //TODO Close Icmp Handle appropriate +// } + +// return _icmpHandle.GetValueOrDefault(); +// } +// } + +// /// Returns the the marshaled size of the reply struct. +// public static int ReplyMarshalLength +// { +// get +// { +// if (_replyStructLength == null) +// { +// _replyStructLength = Marshal.SizeOf(typeof(Reply)); +// } +// return _replyStructLength.GetValueOrDefault(); +// } +// } + + +// [DllImport("Iphlpapi.dll", SetLastError = true)] +// private static extern IntPtr IcmpCreateFile(); +// [DllImport("Iphlpapi.dll", SetLastError = true)] +// private static extern bool IcmpCloseHandle(IntPtr handle); +// [DllImport("Iphlpapi.dll", SetLastError = true)] +// public static extern uint IcmpSendEcho2Ex(IntPtr icmpHandle, IntPtr Event, IntPtr apcRoutine, IntPtr apcContext, uint sourceAddress, UInt32 destinationAddress, byte[] requestData, short requestSize, ref Option requestOptions, IntPtr replyBuffer, int replySize, int timeout); +// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] +// public struct Option +// { +// public byte Ttl; +// public readonly byte Tos; +// public byte Flags; +// public readonly byte OptionsSize; +// public readonly IntPtr OptionsData; +// } +// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] +// public struct Reply +// { +// public readonly UInt32 Address; +// public readonly int Status; +// public readonly int RoundTripTime; +// public readonly short DataSize; +// public readonly short Reserved; +// public readonly IntPtr DataPtr; +// public readonly Option Options; +// } +// } + +// public class PingReplyEx +// { +// private Win32Exception _exception; + +// internal PingReplyEx(uint nativeCode, int replyStatus, IPAddress ipAddress, TimeSpan duration) +// { +// NativeCode = nativeCode; +// IpAddress = ipAddress; +// if (Enum.IsDefined(typeof(IPStatus), replyStatus)) +// Status = (IPStatus)replyStatus; +// } +// internal PingReplyEx(uint nativeCode, int replyStatus, IPAddress ipAddress, int roundTripTime, byte[] buffer) +// { +// NativeCode = nativeCode; +// IpAddress = ipAddress; +// RoundTripTime = TimeSpan.FromMilliseconds(roundTripTime); +// Buffer = buffer; +// if (Enum.IsDefined(typeof(IPStatus), replyStatus)) +// Status = (IPStatus)replyStatus; +// } + +// /// Native result from IcmpSendEcho2Ex. +// public uint NativeCode { get; } + +// public IPStatus Status { get; } = IPStatus.Unknown; + +// /// The source address of the reply. +// public IPAddress IpAddress { get; } + +// public byte[] Buffer { get; } + +// public TimeSpan RoundTripTime { get; } = TimeSpan.Zero; + +// public Win32Exception Exception +// { +// get +// { +// if (Status != IPStatus.Success) +// return _exception ?? (_exception = new Win32Exception((int)NativeCode, Status.ToString())); +// else +// return null; +// } +// } + +// public override string ToString() +// { +// if (Status == IPStatus.Success) +// return Status + " from " + IpAddress + " in " + RoundTripTime + " ms with " + Buffer.Length + " bytes"; +// else if (Status != IPStatus.Unknown) +// return Status + " from " + IpAddress; +// else +// return Exception.Message + " from " + IpAddress; +// } +// } +// } +//} From 25f775780d1f6e7cea937ad31fbe5c241f40933c Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 22 Jun 2021 15:38:43 +0200 Subject: [PATCH 083/101] fix test --- .../tests/FunctionalTests/HttpClientHandlerTest.Http2.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs index c12469a01a7347..e007343b3419ea 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs @@ -1514,9 +1514,8 @@ private async Task TestClientWindowScalingAsync(TimeSpan networkDelay, Time // Expect SETTINGS ACK from client: await connection.ExpectSettingsAckAsync(); - SettingsEntry initialWindowSettingsEntry = clientSettingsFrame.Entries.SingleOrDefault(e => e.SettingId == SettingId.InitialWindowSize); - - int maxCredit = (int)clientSettingsFrame.Entries.SingleOrDefault(e => e.SettingId == SettingId.InitialWindowSize)?.Value; + int maxCredit = (int)clientSettingsFrame.Entries.SingleOrDefault(e => e.SettingId == SettingId.InitialWindowSize).Value; + if (maxCredit == default) maxCredit = DefaultInitialWindowSize; int credit = maxCredit; int streamId = await connection.ReadRequestHeaderAsync(); From 37ee6f914a05c7abc86ff45869b412577c283a1d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 22 Jun 2021 18:53:23 +0200 Subject: [PATCH 084/101] remote "static RTT" tests --- .../FunctionalTests/_LargeFileBenchmark.cs | 1120 ++++++----------- 1 file changed, 401 insertions(+), 719 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 407f1708415b95..7502146c1d2a08 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -1,719 +1,401 @@ -//// The .NET Foundation licenses this file to you under the MIT license. - -//using System.Collections.Generic; -//using System.ComponentModel; -//using System.Diagnostics; -//using System.Diagnostics.Tracing; -//using System.IO; -//using System.Linq; -//using System.Net.NetworkInformation; -//using System.Net.Sockets; -//using System.Runtime.InteropServices; -//using System.Text; -//using System.Threading; -//using System.Threading.Channels; -//using System.Threading.Tasks; -//using Xunit; -//using Xunit.Abstractions; - -//namespace System.Net.Http.Functional.Tests -//{ -// [CollectionDefinition("NoParallelTests", DisableParallelization = true)] -// public class LargeFileBenchmark_ShouldNotBeParallell { } - -// [Collection(nameof(LargeFileBenchmark_ShouldNotBeParallell))] -// public class LargeFileBenchmark : IDisposable -// { -// private readonly ITestOutputHelper _output; -// private LogHttpEventListener _listener; - -// public LargeFileBenchmark(ITestOutputHelper output) -// { -// _output = output; -// _listener = new LogHttpEventListener(output); -// _listener.Filter = m => m.Contains("[FlowControl]"); -// } - -// public void Dispose() => _listener?.Dispose(); - -// private const double LengthMb = 100; -// private const int TestRunCount = 10; - -// //private const string BenchmarkServer = "10.194.114.94"; -// //private const string BenchmarkServer = "169.254.132.170"; // duo1 -// private const string BenchmarkServer = "192.168.0.152"; -// //private const string BenchmarkServer = "127.0.0.1"; -// private const string BenchmarkServerGo = "192.168.0.152:5002"; -// // private const string BenchmarkServer = "127.0.0.1:5000"; - -// //private static readonly IPAddress LocalAddress = IPAddress.Parse("169.254.59.132"); // duo2 -// private static readonly IPAddress LocalAddress = null; - -// //private const string ReportDir = @"C:\_dev\r6r\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; -// //private const string ReportDir = @"C:\Users\anfirszo\dev\dotnet\6.0\runtime\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; -// private const string ReportDir = @"C:\_dev\r6d\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Debug\TestResults"; - -// [Theory] -// [InlineData(BenchmarkServer)] -// public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run1", hostName, false, LengthMb, details: "http1.1"); - -// [Theory] -// [InlineData(BenchmarkServer, 1024)] -// [InlineData(BenchmarkServer, 2048)] -// [InlineData(BenchmarkServer, 4096)] -// [InlineData(BenchmarkServer, 8192)] -// [InlineData(BenchmarkServer, 16384)] -// public Task Download20_SpecificWindow_MegaBytes(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - -// [Theory] -// [InlineData(BenchmarkServer, 64)] -// [InlineData(BenchmarkServer, 128)] -// [InlineData(BenchmarkServer, 256)] -// [InlineData(BenchmarkServer, 512)] -// public Task Download20_SpecificWindow_KiloBytes(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); - -// private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) -// { -// SocketsHttpHandler handler = new SocketsHttpHandler() -// { -// EnableDynamicHttp2StreamWindowSizing = false, -// InitialStreamWindowSize = initialWindowKbytes * 1024 -// }; -// string details = $"SpecificWindow({initialWindowKbytes})"; -// return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler, details); -// } - -// public static TheoryData Download20_Data = new TheoryData -// { -// { BenchmarkServer, 8, 1 }, -// { BenchmarkServer, 8, 2 }, -// { BenchmarkServer, 8, 4 }, -// { BenchmarkServer, 8, 8 }, -// { BenchmarkServer, 4, 1 }, -// { BenchmarkServer, 4, 2 }, -// { BenchmarkServer, 4, 4 }, -// //{ BenchmarkServerGo, 8, 0.5 }, -// //{ BenchmarkServerGo, 8, 0.25 }, -// //{ BenchmarkServerGo, 8, 0.125 }, -// //{ BenchmarkServerGo, 4, 0.5 }, -// //{ BenchmarkServerGo, 4, 0.25 }, -// }; - -// public static TheoryData Download20_Data8 = new TheoryData -// { -// { BenchmarkServer, 8, 1 }, -// { BenchmarkServer, 8, 2 }, -// { BenchmarkServer, 8, 4 }, -// { BenchmarkServer, 8, 8 }, -// { BenchmarkServer, 8, 16 }, -// }; - - -// public static TheoryData Download20_Data4 = new TheoryData -// { -// { BenchmarkServer, 4, 1 }, -// { BenchmarkServer, 4, 2 }, -// { BenchmarkServer, 4, 4 }, -// { BenchmarkServer, 4, 8 }, -// }; - -// [Theory] -// [MemberData(nameof(Download20_Data8))] -// public Task Download20_StaticRtt_8(string hostName, int ratio, int correction) => Download20_StaticRtt(hostName, ratio, correction); - -// [Theory] -// [MemberData(nameof(Download20_Data4))] -// public Task Download20_StaticRtt_4(string hostName, int ratio, int correction) => Download20_StaticRtt(hostName, ratio, correction); - -// private async Task Download20_StaticRtt(string hostName, int ratio, int correction) -// { -// _listener.Enabled = true; -// _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); -// var handler = new SocketsHttpHandler -// { -// FakeRtt = await EstimateRttAsync(hostName), -// StreamWindowUpdateRatio = ratio, -// StreamWindowThresholdMultiplier = correction -// }; - -// string details = $"StaticRtt_R({ratio})_C({correction})"; -// await TestHandler($"SocketsHttpHandler HTTP 2.0 dynamic Window with Static RTT | host:{hostName} ratio={ratio} correction={handler.StreamWindowThresholdMultiplier}", -// hostName, true, LengthMb, handler, details); -// } - -// [Theory] -// [MemberData(nameof(Download20_Data8))] -// public Task Download20_Dynamic_SingleStream_8(string hostName, int ratio, int correction) => Download20_Dynamic_SingleStream(hostName, ratio, correction); - -// [Theory] -// [MemberData(nameof(Download20_Data4))] -// public Task Download20_Dynamic_SingleStream_4(string hostName, int ratio, int correction) => Download20_Dynamic_SingleStream(hostName, ratio, correction); - -// [Fact] -// public Task Download20_Dynamic_Test() -// { -// _listener.Enabled = true; -// return Download20_Dynamic_SingleStream(BenchmarkServer, 8, 8, true); -// } - -// private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, int correction, bool keepFilter = false) -// { -// _listener.Enabled = true; -// if (!keepFilter) -// { -// _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); -// } - -// var handler = new SocketsHttpHandler() -// { -// StreamWindowUpdateRatio = ratio, -// StreamWindowThresholdMultiplier = correction -// }; -// string details = $"Dynamic_R({ratio})_C({correction})"; -// await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio} correction={handler.StreamWindowThresholdMultiplier}", -// hostName, true, LengthMb, handler, details); -// } - -// [Theory] -// [InlineData(BenchmarkServer)] -// //[InlineData("10.194.114.94:5001")] -// //[InlineData("10.194.114.94:5002")] -// public async Task Download20_Dynamic_MultiStream(string hostName) -// { -// _listener.Enabled = true; -// var handler = new SocketsHttpHandler(); -// using var client = new HttpClient(handler, true); -// client.Timeout = TimeSpan.FromMinutes(3); -// const int NStreams = 4; -// string info = $"SocketsHttpHandler HTTP 2.0 Dynamic {NStreams} concurrent streams"; - - -// var message = GenerateRequestMessage(hostName, true, LengthMb); -// _output.WriteLine($"{info} / {LengthMb} MB from {message.RequestUri}"); - -// Stopwatch sw = Stopwatch.StartNew(); -// List tasks = new List(); -// for (int i = 0; i < NStreams; i++) -// { -// var task = Task.Run(() => client.SendAsync(GenerateRequestMessage(hostName, true, LengthMb))); -// tasks.Add(task); -// } - -// await Task.WhenAll(tasks); - -// double elapsedSec = sw.ElapsedMilliseconds * 0.001; -// _output.WriteLine($"{info}: completed in {elapsedSec} sec"); -// } - -// private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler = null, string details = "") -// { -// handler ??= new SocketsHttpHandler(); - -// if (LocalAddress != null) handler.ConnectCallback = CustomConnect; - -// string reportFileName = CreateOutputFile(details); -// _output.WriteLine("REPORT: " + reportFileName); -// using StreamWriter report = new StreamWriter(reportFileName); - -// _output.WriteLine($"############ Warmup Run ############"); -// await TestHandlerCore(info, hostName, http2, lengthMb, handler, null); - -// for (int i = 0; i < TestRunCount; i++) -// { -// _output.WriteLine($"############ run {i} ############"); -// await TestHandlerCore(info, hostName, http2, lengthMb, handler, report); -// } -// handler.Dispose(); -// } - -// private static string CreateOutputFile(string details) -// { -// if (!Directory.Exists(ReportDir)) Directory.CreateDirectory(ReportDir); -// return Path.Combine(ReportDir, $"report_{Environment.TickCount64}_{details}.csv"); -// } - -// private async Task TestHandlerCore(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler, StreamWriter report) -// { -// _listener.Log2.Clear(); -// using var client = new HttpClient(CopyHandler(handler), true); -// client.Timeout = TimeSpan.FromMinutes(3); -// var message = GenerateRequestMessage(hostName, http2, lengthMb); -// _output.WriteLine($"{info} / {lengthMb} MB from {message.RequestUri}"); -// Stopwatch sw = Stopwatch.StartNew(); -// var response = await client.SendAsync(message); - -// double elapsedSec = sw.ElapsedMilliseconds * 0.001; -// elapsedSec = Math.Round(elapsedSec, 3); -// _output.WriteLine($"{info}: completed in {elapsedSec} sec"); - -// if (report != null) -// { -// report.Write(elapsedSec); -// double? window = GetStreamWindowSizeInMegabytes(); -// if (window.HasValue) report.Write($", {window}"); -// double? rtt = GetRtt(); -// if (rtt.HasValue) report.Write($", {rtt}"); -// report.WriteLine(); -// } -// } - -// private double? GetStreamWindowSizeInMegabytes() -// { -// const string Prefix = "Updated Stream Window. StreamWindowSize: "; -// string log = _listener.Log2.ToString(); - -// int idx = log.LastIndexOf(Prefix); -// if (idx < 0) return null; -// ReadOnlySpan text = log.AsSpan().Slice(idx + Prefix.Length); -// text = text.Slice(0, text.IndexOf(',')); - -// double size = int.Parse(text); -// double sizeMb = size / 1024 / 1024; -// return Math.Round(sizeMb, 3); -// } - -// private double? GetRtt() -// { -// const string Prefix = "Updated MinRtt: "; -// string log = _listener.Log2.ToString(); - -// int idx = log.LastIndexOf(Prefix); -// if (idx < 0) return null; - -// ReadOnlySpan text = log.AsSpan().Slice(idx + Prefix.Length); -// text = text.Slice(0, text.IndexOf(' ')); - -// double rtt = double.Parse(text); -// return Math.Round(rtt, 3); -// } - -// private static SocketsHttpHandler CopyHandler(SocketsHttpHandler h) -// { -// return new SocketsHttpHandler() -// { -// FakeRtt = h.FakeRtt, -// EnableDynamicHttp2StreamWindowSizing = h.EnableDynamicHttp2StreamWindowSizing, -// InitialStreamWindowSize = h.InitialStreamWindowSize, -// StreamWindowUpdateRatio = h.StreamWindowUpdateRatio, -// StreamWindowThresholdMultiplier = h.StreamWindowThresholdMultiplier, -// ConnectCallback = h.ConnectCallback -// }; -// } - -// private static async ValueTask CustomConnect(SocketsHttpConnectionContext ctx, CancellationToken cancellationToken) -// { -// Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) -// { -// NoDelay = true -// }; -// socket.Bind(new IPEndPoint(LocalAddress, 0)); - -// try -// { -// await socket.ConnectAsync(ctx.DnsEndPoint, cancellationToken).ConfigureAwait(false); -// return new NetworkStream(socket, ownsSocket: true); -// } -// catch -// { -// socket.Dispose(); -// throw; -// } -// } - -// [Fact] -// public async Task TestEstimateRtt() -// { -// TimeSpan rtt = await EstimateRttAsync(BenchmarkServer); -// _output.WriteLine($"RTT: {rtt.TotalMilliseconds} ms"); -// } - -// private async Task EstimateRttAsync(string hostName) -// { -// int sep = hostName.IndexOf(':'); -// if (sep > 0) -// { -// hostName = hostName.Substring(0, sep); -// } - -// IPAddress addr; -// if (!IPAddress.TryParse(hostName, out addr)) -// { -// addr = (await Dns.GetHostAddressesAsync(hostName)).FirstOrDefault(e => e.AddressFamily == AddressFamily.InterNetwork); -// } - -// IPAddress destAddr = (await Dns.GetHostAddressesAsync(hostName))[0]; - -// // warmup: -// PingEx.Send(LocalAddress, destAddr); - -// IPAddress local = LocalAddress != null ? LocalAddress : IPAddress.Loopback; - -// var reply1 = PingEx.Send(LocalAddress, destAddr).RoundTripTime; -// var reply2 = PingEx.Send(LocalAddress, destAddr).RoundTripTime; - -// TimeSpan rtt = reply1 > reply2 ? reply1 : reply2; - -// _output.WriteLine($"Estimated RTT: {rtt}"); -// if (rtt < TimeSpan.FromMilliseconds(1)) -// { -// _output.WriteLine("RTT < 1 ms, changing to 1 ms!"); -// rtt = TimeSpan.FromMilliseconds(1); -// } - -// return rtt; -// } - - -// static HttpRequestMessage GenerateRequestMessage(string hostName, bool http2, double lengthMb = 5) -// { -// int port = http2 ? 5001 : 5000; -// int sep = hostName.IndexOf(':'); -// if (sep > 0) -// { -// string portStr = hostName.Substring(sep + 1, hostName.Length - sep - 1); -// int.TryParse(portStr, out port); -// hostName = hostName.Substring(0, sep); -// } - -// string url = $"http://{hostName}:{port}?lengthMb={lengthMb}"; -// var msg = new HttpRequestMessage(HttpMethod.Get, url) -// { -// Version = new Version(1, 1) -// }; - -// if (http2) -// { -// msg.Version = new Version(2, 0); -// msg.VersionPolicy = HttpVersionPolicy.RequestVersionExact; -// } - -// return msg; -// } -// } - -// public sealed class LogHttpEventListener : EventListener -// { -// private Channel _messagesChannel = Channel.CreateUnbounded(); -// private Task _processMessages; -// private CancellationTokenSource _stopProcessing; -// private ITestOutputHelper _log; - -// public StringBuilder Log2 { get; } - -// public LogHttpEventListener(ITestOutputHelper log) -// { -// _log = log; -// _messagesChannel = Channel.CreateUnbounded(); -// _processMessages = ProcessMessagesAsync(); -// _stopProcessing = new CancellationTokenSource(); -// Log2 = new StringBuilder(1024 * 1024); -// } - -// public bool Enabled { get; set; } -// public Predicate Filter { get; set; } = _ => true; - -// protected override void OnEventSourceCreated(EventSource eventSource) -// { -// if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http") -// { -// EnableEvents(eventSource, EventLevel.LogAlways); -// } -// } - -// private async Task ProcessMessagesAsync() -// { -// await Task.Yield(); - -// try -// { -// await foreach (string message in _messagesChannel.Reader.ReadAllAsync(_stopProcessing.Token)) -// { -// if (Filter(message)) -// { -// _log.WriteLine(message); -// Log2.AppendLine(message); -// } -// } -// } -// catch (OperationCanceledException) -// { -// return; -// } -// } - -// protected override async void OnEventWritten(EventWrittenEventArgs eventData) -// { -// if (!Enabled) return; - -// var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] "); -// for (int i = 0; i < eventData.Payload?.Count; i++) -// { -// if (i > 0) -// { -// sb.Append(", "); -// } -// sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]); -// } -// await _messagesChannel.Writer.WriteAsync(sb.ToString()); -// } - -// public override void Dispose() -// { -// base.Dispose(); -// var timeout = TimeSpan.FromSeconds(2); - -// if (!_processMessages.Wait(timeout)) -// { -// _stopProcessing.Cancel(); -// _processMessages.Wait(timeout); -// } -// } -// } - -// // https://stackoverflow.com/a/66380228/797482 -// internal static class PingEx -// { -// /// -// /// Pass in the IP you want to ping as a string along with the name of the NIC on your machine that -// /// you want to send the ping from. -// /// -// /// The destination IP as a string ex. '10.10.10.1' -// /// The name of the NIC ex. 'LECO Hardware'. Non-case sensitive. -// /// -// public static bool PingIpFromNic(string ipToPing, string nicName) -// { -// var sourceIpStr = GetIpOfNicFromName(nicName); - -// if (sourceIpStr == "") -// { -// return false; -// } - -// var p = Send( -// srcAddress: IPAddress.Parse(sourceIpStr), -// destAddress: IPAddress.Parse(ipToPing)); - -// return p.Status == IPStatus.Success; -// } - -// /// -// /// Pass in the name of a NIC on your machine and this method will return the IPV4 address of it. -// /// -// /// The name of the NIC you want the IP of ex. 'TE Hardware' -// /// -// public static string GetIpOfNicFromName(string nicName) -// { -// var adapters = NetworkInterface.GetAllNetworkInterfaces(); - -// foreach (var adapter in adapters) -// { -// // Ignoring case in NIC name -// if (!string.Equals(adapter.Name, nicName, StringComparison.CurrentCultureIgnoreCase)) continue; - -// foreach (var uni in adapter.GetIPProperties().UnicastAddresses) -// { -// // Return the first one found -// return uni.Address.ToString(); -// } -// } - -// return ""; -// } - -// public static PingReplyEx Send(IPAddress srcAddress, IPAddress destAddress, -// int timeout = 5000, -// byte[] buffer = null, PingOptions po = null) -// { -// if (destAddress == null || destAddress.AddressFamily != AddressFamily.InterNetwork || -// destAddress.Equals(IPAddress.Any)) -// throw new ArgumentException(); - -// //Defining pinvoke args -// var source = srcAddress == null ? 0 : BitConverter.ToUInt32(srcAddress.GetAddressBytes(), 0); - -// var destination = BitConverter.ToUInt32(destAddress.GetAddressBytes(), 0); - -// var sendBuffer = buffer ?? new byte[] { }; - -// var options = new Interop.Option -// { -// Ttl = (po == null ? (byte)255 : (byte)po.Ttl), -// Flags = (po == null ? (byte)0 : po.DontFragment ? (byte)0x02 : (byte)0) //0x02 -// }; - -// var fullReplyBufferSize = -// Interop.ReplyMarshalLength + -// sendBuffer.Length; //Size of Reply struct and the transmitted buffer length. - -// var allocSpace = -// Marshal.AllocHGlobal( -// fullReplyBufferSize); // unmanaged allocation of reply size. TODO Maybe should be allocated on stack -// try -// { -// var start = DateTime.Now; -// var nativeCode = Interop.IcmpSendEcho2Ex( -// Interop.IcmpHandle, //_In_ HANDLE IcmpHandle, -// Event: default(IntPtr), //_In_opt_ HANDLE Event, -// apcRoutine: default(IntPtr), //_In_opt_ PIO_APC_ROUTINE ApcRoutine, -// apcContext: default(IntPtr), //_In_opt_ PVOID ApcContext -// source, //_In_ IPAddr SourceAddress, -// destination, //_In_ IPAddr DestinationAddress, -// sendBuffer, //_In_ LPVOID RequestData, -// (short)sendBuffer.Length, //_In_ WORD RequestSize, -// ref options, //_In_opt_ PIP_OPTION_INFORMATION RequestOptions, -// replyBuffer: allocSpace, //_Out_ LPVOID ReplyBuffer, -// fullReplyBufferSize, //_In_ DWORD ReplySize, -// timeout //_In_ DWORD Timeout -// ); - -// var duration = DateTime.Now - start; - -// var reply = (Interop.Reply)Marshal.PtrToStructure(allocSpace, -// typeof(Interop.Reply)); // Parse the beginning of reply memory to reply struct - -// byte[] replyBuffer = null; -// if (sendBuffer.Length != 0) -// { -// replyBuffer = new byte[sendBuffer.Length]; -// Marshal.Copy(allocSpace + Interop.ReplyMarshalLength, replyBuffer, 0, -// sendBuffer.Length); //copy the rest of the reply memory to managed byte[] -// } - -// if (nativeCode == 0) //Means that native method is faulted. -// return new PingReplyEx(nativeCode, reply.Status, -// new IPAddress(reply.Address), duration); -// else -// return new PingReplyEx(nativeCode, reply.Status, -// new IPAddress(reply.Address), reply.RoundTripTime, -// replyBuffer); -// } -// finally -// { -// Marshal.FreeHGlobal(allocSpace); //free allocated space -// } -// } - - -// /// Interoperability Helper -// /// -// /// -// public static class Interop -// { -// private static IntPtr? _icmpHandle; -// private static int? _replyStructLength; - -// /// Returns the application legal icmp handle. Should be close by IcmpCloseHandle -// /// -// /// -// public static IntPtr IcmpHandle -// { -// get -// { -// if (_icmpHandle == null) -// { -// _icmpHandle = IcmpCreateFile(); -// //TODO Close Icmp Handle appropriate -// } - -// return _icmpHandle.GetValueOrDefault(); -// } -// } - -// /// Returns the the marshaled size of the reply struct. -// public static int ReplyMarshalLength -// { -// get -// { -// if (_replyStructLength == null) -// { -// _replyStructLength = Marshal.SizeOf(typeof(Reply)); -// } -// return _replyStructLength.GetValueOrDefault(); -// } -// } - - -// [DllImport("Iphlpapi.dll", SetLastError = true)] -// private static extern IntPtr IcmpCreateFile(); -// [DllImport("Iphlpapi.dll", SetLastError = true)] -// private static extern bool IcmpCloseHandle(IntPtr handle); -// [DllImport("Iphlpapi.dll", SetLastError = true)] -// public static extern uint IcmpSendEcho2Ex(IntPtr icmpHandle, IntPtr Event, IntPtr apcRoutine, IntPtr apcContext, uint sourceAddress, UInt32 destinationAddress, byte[] requestData, short requestSize, ref Option requestOptions, IntPtr replyBuffer, int replySize, int timeout); -// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] -// public struct Option -// { -// public byte Ttl; -// public readonly byte Tos; -// public byte Flags; -// public readonly byte OptionsSize; -// public readonly IntPtr OptionsData; -// } -// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] -// public struct Reply -// { -// public readonly UInt32 Address; -// public readonly int Status; -// public readonly int RoundTripTime; -// public readonly short DataSize; -// public readonly short Reserved; -// public readonly IntPtr DataPtr; -// public readonly Option Options; -// } -// } - -// public class PingReplyEx -// { -// private Win32Exception _exception; - -// internal PingReplyEx(uint nativeCode, int replyStatus, IPAddress ipAddress, TimeSpan duration) -// { -// NativeCode = nativeCode; -// IpAddress = ipAddress; -// if (Enum.IsDefined(typeof(IPStatus), replyStatus)) -// Status = (IPStatus)replyStatus; -// } -// internal PingReplyEx(uint nativeCode, int replyStatus, IPAddress ipAddress, int roundTripTime, byte[] buffer) -// { -// NativeCode = nativeCode; -// IpAddress = ipAddress; -// RoundTripTime = TimeSpan.FromMilliseconds(roundTripTime); -// Buffer = buffer; -// if (Enum.IsDefined(typeof(IPStatus), replyStatus)) -// Status = (IPStatus)replyStatus; -// } - -// /// Native result from IcmpSendEcho2Ex. -// public uint NativeCode { get; } - -// public IPStatus Status { get; } = IPStatus.Unknown; - -// /// The source address of the reply. -// public IPAddress IpAddress { get; } - -// public byte[] Buffer { get; } - -// public TimeSpan RoundTripTime { get; } = TimeSpan.Zero; - -// public Win32Exception Exception -// { -// get -// { -// if (Status != IPStatus.Success) -// return _exception ?? (_exception = new Win32Exception((int)NativeCode, Status.ToString())); -// else -// return null; -// } -// } - -// public override string ToString() -// { -// if (Status == IPStatus.Success) -// return Status + " from " + IpAddress + " in " + RoundTripTime + " ms with " + Buffer.Length + " bytes"; -// else if (Status != IPStatus.Unknown) -// return Status + " from " + IpAddress; -// else -// return Exception.Message + " from " + IpAddress; -// } -// } -// } -//} +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.Tracing; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace System.Net.Http.Functional.Tests +{ + [CollectionDefinition("NoParallelTests", DisableParallelization = true)] + public class LargeFileBenchmark_ShouldNotBeParallell { } + + [Collection(nameof(LargeFileBenchmark_ShouldNotBeParallell))] + public class LargeFileBenchmark : IDisposable + { +#pragma warning disable xUnit1004 // Test methods should not be skipped + //public const string SkipSwitch = null; + public const string SkipSwitch = "Local benchmark"; + + private readonly ITestOutputHelper _output; + private LogHttpEventListener _listener; + + public LargeFileBenchmark(ITestOutputHelper output) + { + _output = output; + _listener = new LogHttpEventListener(output); + _listener.Filter = m => m.Contains("[FlowControl]"); + } + + public void Dispose() => _listener?.Dispose(); + + private const double LengthMb = 100; + private const int TestRunCount = 10; + + //private const string BenchmarkServer = "10.194.114.94"; + //private const string BenchmarkServer = "169.254.132.170"; // duo1 + private const string BenchmarkServer = "192.168.0.152"; + //private const string BenchmarkServer = "127.0.0.1"; + private const string BenchmarkServerGo = "192.168.0.152:5002"; + // private const string BenchmarkServer = "127.0.0.1:5000"; + + //private static readonly IPAddress LocalAddress = IPAddress.Parse("169.254.59.132"); // duo2 + private static readonly IPAddress LocalAddress = null; + + //private const string ReportDir = @"C:\_dev\r6r\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; + //private const string ReportDir = @"C:\Users\anfirszo\dev\dotnet\6.0\runtime\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; + private const string ReportDir = @"C:\_dev\r6d\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Debug\TestResults"; + + [Theory(Skip = SkipSwitch)] + [InlineData(BenchmarkServer)] + public Task Download11(string hostName) => TestHandler("SocketsHttpHandler HTTP 1.1 - Run1", hostName, false, LengthMb, details: "http1.1"); + + [Theory(Skip = SkipSwitch)] + [InlineData(BenchmarkServer, 1024)] + [InlineData(BenchmarkServer, 2048)] + [InlineData(BenchmarkServer, 4096)] + [InlineData(BenchmarkServer, 8192)] + [InlineData(BenchmarkServer, 16384)] + public Task Download20_SpecificWindow_MegaBytes(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + + [Theory(Skip = SkipSwitch)] + [InlineData(BenchmarkServer, 64)] + [InlineData(BenchmarkServer, 128)] + [InlineData(BenchmarkServer, 256)] + [InlineData(BenchmarkServer, 512)] + public Task Download20_SpecificWindow_KiloBytes(string hostName, int initialWindowKbytes) => Download20_SpecificWindow(hostName, initialWindowKbytes); + + private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) + { + SocketsHttpHandler handler = new SocketsHttpHandler() + { + EnableDynamicHttp2StreamWindowSizing = false, + InitialStreamWindowSize = initialWindowKbytes * 1024 + }; + string details = $"SpecificWindow({initialWindowKbytes})"; + return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler, details); + } + + public static TheoryData Download20_Data = new TheoryData + { + { BenchmarkServer, 8, 1 }, + { BenchmarkServer, 8, 2 }, + { BenchmarkServer, 8, 4 }, + { BenchmarkServer, 8, 8 }, + { BenchmarkServer, 4, 1 }, + { BenchmarkServer, 4, 2 }, + { BenchmarkServer, 4, 4 }, + }; + + public static TheoryData Download20_Data8 = new TheoryData + { + { BenchmarkServer, 8, 1 }, + { BenchmarkServer, 8, 2 }, + { BenchmarkServer, 8, 4 }, + { BenchmarkServer, 8, 8 }, + { BenchmarkServer, 8, 16 }, + }; + + + public static TheoryData Download20_Data4 = new TheoryData + { + { BenchmarkServer, 4, 1 }, + { BenchmarkServer, 4, 2 }, + { BenchmarkServer, 4, 4 }, + { BenchmarkServer, 4, 8 }, + }; + + [Theory(Skip = SkipSwitch)] + [MemberData(nameof(Download20_Data8))] + public Task Download20_Dynamic_SingleStream_8(string hostName, int ratio, int correction) => Download20_Dynamic_SingleStream(hostName, ratio, correction); + + [Theory(Skip = SkipSwitch)] + [MemberData(nameof(Download20_Data4))] + public Task Download20_Dynamic_SingleStream_4(string hostName, int ratio, int correction) => Download20_Dynamic_SingleStream(hostName, ratio, correction); + + [Fact(Skip = SkipSwitch)] + public Task Download20_Dynamic_Test() + { + _listener.Enabled = true; + return Download20_Dynamic_SingleStream(BenchmarkServer, 8, 8, true); + } + + private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, int correction, bool keepFilter = false) + { + _listener.Enabled = true; + if (!keepFilter) + { + _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); + } + + var handler = new SocketsHttpHandler() + { + StreamWindowUpdateRatio = ratio, + StreamWindowThresholdMultiplier = correction + }; + string details = $"Dynamic_R({ratio})_C({correction})"; + await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio} correction={handler.StreamWindowThresholdMultiplier}", + hostName, true, LengthMb, handler, details); + } + + [Theory(Skip = SkipSwitch)] + [InlineData(BenchmarkServer)] + public async Task Download20_Dynamic_MultiStream(string hostName) + { + _listener.Enabled = true; + var handler = new SocketsHttpHandler(); + using var client = new HttpClient(handler, true); + client.Timeout = TimeSpan.FromMinutes(3); + const int NStreams = 4; + string info = $"SocketsHttpHandler HTTP 2.0 Dynamic {NStreams} concurrent streams"; + + + var message = GenerateRequestMessage(hostName, true, LengthMb); + _output.WriteLine($"{info} / {LengthMb} MB from {message.RequestUri}"); + + Stopwatch sw = Stopwatch.StartNew(); + List tasks = new List(); + for (int i = 0; i < NStreams; i++) + { + var task = Task.Run(() => client.SendAsync(GenerateRequestMessage(hostName, true, LengthMb))); + tasks.Add(task); + } + + await Task.WhenAll(tasks); + + double elapsedSec = sw.ElapsedMilliseconds * 0.001; + _output.WriteLine($"{info}: completed in {elapsedSec} sec"); + } + + private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler = null, string details = "") + { + handler ??= new SocketsHttpHandler(); + + if (LocalAddress != null) handler.ConnectCallback = CustomConnect; + + string reportFileName = CreateOutputFile(details); + _output.WriteLine("REPORT: " + reportFileName); + using StreamWriter report = new StreamWriter(reportFileName); + + _output.WriteLine($"############ Warmup Run ############"); + await TestHandlerCore(info, hostName, http2, lengthMb, handler, null); + + for (int i = 0; i < TestRunCount; i++) + { + _output.WriteLine($"############ run {i} ############"); + await TestHandlerCore(info, hostName, http2, lengthMb, handler, report); + } + handler.Dispose(); + } + + private static string CreateOutputFile(string details) + { + if (!Directory.Exists(ReportDir)) Directory.CreateDirectory(ReportDir); + return Path.Combine(ReportDir, $"report_{Environment.TickCount64}_{details}.csv"); + } + + private async Task TestHandlerCore(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler, StreamWriter report) + { + _listener.Log2.Clear(); + using var client = new HttpClient(CopyHandler(handler), true); + client.Timeout = TimeSpan.FromMinutes(3); + var message = GenerateRequestMessage(hostName, http2, lengthMb); + _output.WriteLine($"{info} / {lengthMb} MB from {message.RequestUri}"); + Stopwatch sw = Stopwatch.StartNew(); + var response = await client.SendAsync(message); + + double elapsedSec = sw.ElapsedMilliseconds * 0.001; + elapsedSec = Math.Round(elapsedSec, 3); + _output.WriteLine($"{info}: completed in {elapsedSec} sec"); + + if (report != null) + { + report.Write(elapsedSec); + double? window = GetStreamWindowSizeInMegabytes(); + if (window.HasValue) report.Write($", {window}"); + double? rtt = GetRtt(); + if (rtt.HasValue) report.Write($", {rtt}"); + report.WriteLine(); + } + } + + private double? GetStreamWindowSizeInMegabytes() + { + const string Prefix = "Updated Stream Window. StreamWindowSize: "; + string log = _listener.Log2.ToString(); + + int idx = log.LastIndexOf(Prefix); + if (idx < 0) return null; + ReadOnlySpan text = log.AsSpan().Slice(idx + Prefix.Length); + text = text.Slice(0, text.IndexOf(',')); + + double size = int.Parse(text); + double sizeMb = size / 1024 / 1024; + return Math.Round(sizeMb, 3); + } + + private double? GetRtt() + { + const string Prefix = "Updated MinRtt: "; + string log = _listener.Log2.ToString(); + + int idx = log.LastIndexOf(Prefix); + if (idx < 0) return null; + + ReadOnlySpan text = log.AsSpan().Slice(idx + Prefix.Length); + text = text.Slice(0, text.IndexOf(' ')); + + double rtt = double.Parse(text); + return Math.Round(rtt, 3); + } + + private static SocketsHttpHandler CopyHandler(SocketsHttpHandler h) + { + return new SocketsHttpHandler() + { + FakeRtt = h.FakeRtt, + EnableDynamicHttp2StreamWindowSizing = h.EnableDynamicHttp2StreamWindowSizing, + InitialStreamWindowSize = h.InitialStreamWindowSize, + StreamWindowUpdateRatio = h.StreamWindowUpdateRatio, + StreamWindowThresholdMultiplier = h.StreamWindowThresholdMultiplier, + ConnectCallback = h.ConnectCallback + }; + } + + private static async ValueTask CustomConnect(SocketsHttpConnectionContext ctx, CancellationToken cancellationToken) + { + Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) + { + NoDelay = true + }; + socket.Bind(new IPEndPoint(LocalAddress, 0)); + + try + { + await socket.ConnectAsync(ctx.DnsEndPoint, cancellationToken).ConfigureAwait(false); + return new NetworkStream(socket, ownsSocket: true); + } + catch + { + socket.Dispose(); + throw; + } + } + + static HttpRequestMessage GenerateRequestMessage(string hostName, bool http2, double lengthMb = 5) + { + int port = http2 ? 5001 : 5000; + int sep = hostName.IndexOf(':'); + if (sep > 0) + { + string portStr = hostName.Substring(sep + 1, hostName.Length - sep - 1); + int.TryParse(portStr, out port); + hostName = hostName.Substring(0, sep); + } + + string url = $"http://{hostName}:{port}?lengthMb={lengthMb}"; + var msg = new HttpRequestMessage(HttpMethod.Get, url) + { + Version = new Version(1, 1) + }; + + if (http2) + { + msg.Version = new Version(2, 0); + msg.VersionPolicy = HttpVersionPolicy.RequestVersionExact; + } + + return msg; + } + } + + public sealed class LogHttpEventListener : EventListener + { + private Channel _messagesChannel = Channel.CreateUnbounded(); + private Task _processMessages; + private CancellationTokenSource _stopProcessing; + private ITestOutputHelper _log; + + public StringBuilder Log2 { get; } + + public LogHttpEventListener(ITestOutputHelper log) + { + _log = log; + _messagesChannel = Channel.CreateUnbounded(); + _processMessages = ProcessMessagesAsync(); + _stopProcessing = new CancellationTokenSource(); + Log2 = new StringBuilder(1024 * 1024); + } + + public bool Enabled { get; set; } + public Predicate Filter { get; set; } = _ => true; + + protected override void OnEventSourceCreated(EventSource eventSource) + { + if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http") + { + EnableEvents(eventSource, EventLevel.LogAlways); + } + } + + private async Task ProcessMessagesAsync() + { + await Task.Yield(); + + try + { + await foreach (string message in _messagesChannel.Reader.ReadAllAsync(_stopProcessing.Token)) + { + if (Filter(message)) + { + _log.WriteLine(message); + Log2.AppendLine(message); + } + } + } + catch (OperationCanceledException) + { + return; + } + } + + protected override async void OnEventWritten(EventWrittenEventArgs eventData) + { + if (!Enabled) return; + + var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] "); + for (int i = 0; i < eventData.Payload?.Count; i++) + { + if (i > 0) + { + sb.Append(", "); + } + sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]); + } + await _messagesChannel.Writer.WriteAsync(sb.ToString()); + } + + public override void Dispose() + { + base.Dispose(); + var timeout = TimeSpan.FromSeconds(2); + + if (!_processMessages.Wait(timeout)) + { + _stopProcessing.Cancel(); + _processMessages.Wait(timeout); + } + } + } +} From 145ace94f3e2c50376ecbfaf1f8f8b33749b5736 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 22 Jun 2021 21:53:11 +0200 Subject: [PATCH 085/101] improve naming --- .../SocketsHttpHandler/Http2Connection.cs | 31 +++++++++---------- .../Http2StreamWindowManager.cs | 4 +-- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index dd9b74dacd19aa..f8e7778e7fdb19 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -42,7 +42,7 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable private int _nextStream; private bool _expectingSettingsAck; - private int _initialWindowSize; + private int _initialServerStreamWindowSize; private int _maxConcurrentStreams; private int _pendingWindowUpdate; private long _idleSinceTickCount; @@ -80,12 +80,9 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable #else private const int InitialConnectionBufferSize = 4096; #endif - // 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 - // 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; + // The default initial window size streams and connections according to the RFC: + // https://datatracker.ietf.org/doc/html/rfc7540#section-5.2.1 + private const int DefaultInitialWindowSize = 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. @@ -134,16 +131,16 @@ public Http2Connection(HttpConnectionPool pool, Stream stream) _httpStreams = new Dictionary(); - _connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialConnectionWindowSize); + _connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialWindowSize); _concurrentStreams = new CreditManager(this, nameof(_concurrentStreams), InitialMaxConcurrentStreams); - InitialStreamWindowSize = pool.Settings._initialStreamWindowSize; + InitialClientStreamWindowSize = pool.Settings._initialStreamWindowSize; _rttEstimator = pool.Settings._enableDynamicHttp2StreamWindowSizing ? new RttEstimator(this, pool.Settings._fakeRtt) : null; _writeChannel = Channel.CreateUnbounded(s_channelOptions); _nextStream = 1; - _initialWindowSize = DefaultInitialConnectionWindowSize; + _initialServerStreamWindowSize = DefaultInitialWindowSize; _maxConcurrentStreams = InitialMaxConcurrentStreams; _pendingWindowUpdate = 0; @@ -196,11 +193,13 @@ public async ValueTask SetupAsync() _outgoingBuffer.Commit(4); BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.InitialWindowSize); _outgoingBuffer.Commit(2); - BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, (uint)InitialStreamWindowSize); + BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, (uint)InitialClientStreamWindowSize); _outgoingBuffer.Commit(4); - // Send initial connection-level WINDOW_UPDATE - uint windowUpdateAmount = (uint)(ConnectionWindowSize - InitialStreamWindowSize); + // The connection-level window size can not be initialized by SETTINGS frames: + // https://datatracker.ietf.org/doc/html/rfc7540#section-6.9.2 + // Send an initial connection-level WINDOW_UPDATE to setup the desired ConnectionWindowSize: + uint windowUpdateAmount = (ConnectionWindowSize - DefaultInitialWindowSize); 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); @@ -715,8 +714,8 @@ private void ChangeInitialWindowSize(int newSize) lock (SyncObject) { - int delta = newSize - _initialWindowSize; - _initialWindowSize = newSize; + int delta = newSize - _initialServerStreamWindowSize; + _initialServerStreamWindowSize = newSize; // Adjust existing streams foreach (KeyValuePair kvp in _httpStreams) @@ -1419,7 +1418,7 @@ await PerformWriteAsync(totalSize, (thisRef: this, http2Stream, headerBytes, end // assigning the stream ID to ensure only one stream gets an ID, and it must be held // across setting the initial window size (available credit) and storing the stream into // collection such that window size updates are able to atomically affect all known streams. - s.http2Stream.Initialize(s.thisRef._nextStream, s.thisRef._initialWindowSize); + s.http2Stream.Initialize(s.thisRef._nextStream, s.thisRef._initialServerStreamWindowSize); // Client-initiated streams are always odd-numbered, so increase by 2. s.thisRef._nextStream += 2; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 33ef6cda9e755e..57d09340507c94 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -11,7 +11,7 @@ namespace System.Net.Http { internal sealed partial class Http2Connection { - private int InitialStreamWindowSize { get; } = 65535; + private int InitialClientStreamWindowSize { get; } = 65535; private class Http2StreamWindowManager { @@ -28,7 +28,7 @@ public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) _connection = connection; _stream = stream; - _streamWindowSize = connection.InitialStreamWindowSize; + _streamWindowSize = connection.InitialClientStreamWindowSize; _streamWindowUpdateRatio = _connection._pool.Settings._streamWindowUpdateRatio; _stream.TraceFlowControl($"StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}, streamWindowUpdateRatio: {_streamWindowUpdateRatio}"); } From 3776806029d54de8d7675c7c787ee07fc121cc0b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 23 Jun 2021 13:07:21 +0200 Subject: [PATCH 086/101] fix SendAsync_Unexpected1xxResponses_DropAllInterimResponses --- .../Common/tests/System/Net/Http/HttpClientHandlerTest.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index 27d37f9d2151ad..840d5a1f8d21c0 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -1414,6 +1414,11 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => { await server.AcceptConnectionAsync(async connection => { + if (connection is Http2LoopbackConnection http2Connection) + { + http2Connection.SetupAutomaticPingResponse(); // Handle RTT PING + } + // Send unexpected 1xx responses. HttpRequestData requestData = await connection.ReadRequestDataAsync(readBody: false); await connection.SendResponseAsync(responseStatusCode, isFinal: false); From 8b16b7cc08cae1b144f7b479fc3713b652aede55 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 23 Jun 2021 14:41:55 +0200 Subject: [PATCH 087/101] multi-stream benchmarks --- .../FunctionalTests/_LargeFileBenchmark.cs | 96 +++++++++++++------ 1 file changed, 69 insertions(+), 27 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 7502146c1d2a08..7bc7e0385ac22a 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -25,8 +25,8 @@ public class LargeFileBenchmark_ShouldNotBeParallell { } public class LargeFileBenchmark : IDisposable { #pragma warning disable xUnit1004 // Test methods should not be skipped - //public const string SkipSwitch = null; - public const string SkipSwitch = "Local benchmark"; + public const string SkipSwitch = null; + //public const string SkipSwitch = "Local benchmark"; private readonly ITestOutputHelper _output; private LogHttpEventListener _listener; @@ -55,7 +55,7 @@ public LargeFileBenchmark(ITestOutputHelper output) //private const string ReportDir = @"C:\_dev\r6r\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; //private const string ReportDir = @"C:\Users\anfirszo\dev\dotnet\6.0\runtime\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; - private const string ReportDir = @"C:\_dev\r6d\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Debug\TestResults"; + private const string ReportDir = @"C:\_dev\r6r\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; [Theory(Skip = SkipSwitch)] [InlineData(BenchmarkServer)] @@ -150,35 +150,27 @@ await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{ho } [Theory(Skip = SkipSwitch)] - [InlineData(BenchmarkServer)] - public async Task Download20_Dynamic_MultiStream(string hostName) + [InlineData(BenchmarkServer, 1)] + [InlineData(BenchmarkServer, 10)] + [InlineData(BenchmarkServer, 50)] + public async Task Download20_Dynamic_MultiStream(string hostName, int streamCount) { _listener.Enabled = true; - var handler = new SocketsHttpHandler(); - using var client = new HttpClient(handler, true); - client.Timeout = TimeSpan.FromMinutes(3); - const int NStreams = 4; - string info = $"SocketsHttpHandler HTTP 2.0 Dynamic {NStreams} concurrent streams"; - + _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); + string info = $"SocketsHttpHandler HTTP 2.0 Dynamic {streamCount} concurrent streams R=8 D=8"; - var message = GenerateRequestMessage(hostName, true, LengthMb); - _output.WriteLine($"{info} / {LengthMb} MB from {message.RequestUri}"); - - Stopwatch sw = Stopwatch.StartNew(); - List tasks = new List(); - for (int i = 0; i < NStreams; i++) + var handler = new SocketsHttpHandler() { - var task = Task.Run(() => client.SendAsync(GenerateRequestMessage(hostName, true, LengthMb))); - tasks.Add(task); - } + StreamWindowUpdateRatio = 8, + StreamWindowThresholdMultiplier = 8 + }; - await Task.WhenAll(tasks); + string details = $"SC({streamCount})"; - double elapsedSec = sw.ElapsedMilliseconds * 0.001; - _output.WriteLine($"{info}: completed in {elapsedSec} sec"); + await TestHandler(info, hostName, true, LengthMb, handler, details, streamCount); } - private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler = null, string details = "") + private async Task TestHandler(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler = null, string details = "", int streamCount = -1) { handler ??= new SocketsHttpHandler(); @@ -194,7 +186,15 @@ private async Task TestHandler(string info, string hostName, bool http2, double for (int i = 0; i < TestRunCount; i++) { _output.WriteLine($"############ run {i} ############"); - await TestHandlerCore(info, hostName, http2, lengthMb, handler, report); + if (streamCount > 0) + { + await TestHandlerCoreMultiStream(info, hostName, http2, lengthMb, handler, report, streamCount); + } + else + { + await TestHandlerCore(info, hostName, http2, lengthMb, handler, report); + } + await report.FlushAsync(); } handler.Dispose(); } @@ -210,10 +210,10 @@ private async Task TestHandlerCore(string info, string hostName, bool http2, dou _listener.Log2.Clear(); using var client = new HttpClient(CopyHandler(handler), true); client.Timeout = TimeSpan.FromMinutes(3); - var message = GenerateRequestMessage(hostName, http2, lengthMb); + using var message = GenerateRequestMessage(hostName, http2, lengthMb); _output.WriteLine($"{info} / {lengthMb} MB from {message.RequestUri}"); Stopwatch sw = Stopwatch.StartNew(); - var response = await client.SendAsync(message); + using var response = await client.SendAsync(message); double elapsedSec = sw.ElapsedMilliseconds * 0.001; elapsedSec = Math.Round(elapsedSec, 3); @@ -230,6 +230,48 @@ private async Task TestHandlerCore(string info, string hostName, bool http2, dou } } + private async Task TestHandlerCoreMultiStream(string info, string hostName, bool http2, double lengthMb, SocketsHttpHandler handler, StreamWriter report, int streamCount) + { + _listener.Log2.Clear(); + using var client = new HttpClient(CopyHandler(handler), true); + client.Timeout = TimeSpan.FromMinutes(3); + + async Task SendRequestAsync(int i) + { + using var message = GenerateRequestMessage(hostName, http2, lengthMb); + _output.WriteLine($"[STREAM {i}] {info} / {lengthMb} MB from {message.RequestUri}"); + Stopwatch sw = Stopwatch.StartNew(); + + using var response = await client.SendAsync(message).ConfigureAwait(false); + double elapsedSec = sw.ElapsedMilliseconds * 0.001; + elapsedSec = Math.Round(elapsedSec, 3); + _output.WriteLine($"[STREAM {i}] {info}: completed in {elapsedSec} sec"); + return elapsedSec; + } + + List> allTasks = new List>(); + + for (int i = 0; i < streamCount; i++) + { + Task task = SendRequestAsync(i); + allTasks.Add(task); + } + + await Task.WhenAll(allTasks); + + if (report != null) + { + double averageTime = allTasks.Select(t => t.Result).Average(); + averageTime = Math.Round(averageTime, 3); + report.Write($"{averageTime}"); + double? window = GetStreamWindowSizeInMegabytes(); + if (window.HasValue) report.Write($", {window}"); + double? rtt = GetRtt(); + if (rtt.HasValue) report.Write($", {rtt}"); + report.WriteLine(); + } + } + private double? GetStreamWindowSizeInMegabytes() { const string Prefix = "Updated Stream Window. StreamWindowSize: "; From 511bf56ecb873d7f96016f038639b060703b731a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 23 Jun 2021 15:54:22 +0200 Subject: [PATCH 088/101] API --- .../System.Net.Http/ref/System.Net.Http.cs | 3 +-- .../BrowserHttpHandler/SocketsHttpHandler.cs | 8 +------- .../Http/SocketsHttpHandler/Http2Connection.cs | 6 +++--- .../SocketsHttpHandler/HttpConnectionSettings.cs | 4 ++-- .../SocketsHttpHandler/SocketsHttpHandler.cs | 16 +++++++--------- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 9 ++++----- 6 files changed, 18 insertions(+), 28 deletions(-) diff --git a/src/libraries/System.Net.Http/ref/System.Net.Http.cs b/src/libraries/System.Net.Http/ref/System.Net.Http.cs index 74c2654f2d8898..43c868a2ffb805 100644 --- a/src/libraries/System.Net.Http/ref/System.Net.Http.cs +++ b/src/libraries/System.Net.Http/ref/System.Net.Http.cs @@ -354,9 +354,8 @@ public sealed partial class SocketsHttpHandler : System.Net.Http.HttpMessageHand { public SocketsHttpHandler() { } [System.Runtime.Versioning.UnsupportedOSPlatformGuardAttribute("browser")] + public int InitialHttp2StreamWindowSize { get { throw null; } set { } } public static bool IsSupported { get { throw null; } } - public TimeSpan? FakeRtt { get { throw null; } set { } } - public int InitialStreamWindowSize { get { throw null; } set { } } public bool EnableDynamicHttp2StreamWindowSizing { get { throw null; } set { } } public int StreamWindowUpdateRatio { get { throw null; } set { } } public int StreamWindowThresholdMultiplier { get { throw null; } set { } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs index 362f2782716312..5d0b6bc18e8fdc 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs @@ -25,19 +25,13 @@ public bool UseCookies set => throw new PlatformNotSupportedException(); } - public TimeSpan? FakeRtt - { - get => throw new PlatformNotSupportedException(); - set => throw new PlatformNotSupportedException(); - } - public bool EnableDynamicHttp2StreamWindowSizing { get => throw new PlatformNotSupportedException(); set => throw new PlatformNotSupportedException(); } - public int InitialStreamWindowSize + public int InitialHttp2StreamWindowSize { get => throw new PlatformNotSupportedException(); set => throw new PlatformNotSupportedException(); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index f8e7778e7fdb19..b1b048c97f8333 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -80,9 +80,9 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable #else private const int InitialConnectionBufferSize = 4096; #endif - // The default initial window size streams and connections according to the RFC: + // The default initial window size for streams and connections according to the RFC: // https://datatracker.ietf.org/doc/html/rfc7540#section-5.2.1 - private const int DefaultInitialWindowSize = 65535; + internal const int DefaultInitialWindowSize = 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. @@ -133,7 +133,7 @@ public Http2Connection(HttpConnectionPool pool, Stream stream) _connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialWindowSize); _concurrentStreams = new CreditManager(this, nameof(_concurrentStreams), InitialMaxConcurrentStreams); - InitialClientStreamWindowSize = pool.Settings._initialStreamWindowSize; + InitialClientStreamWindowSize = pool.Settings._initialHttp2StreamWindowSize; _rttEstimator = pool.Settings._enableDynamicHttp2StreamWindowSizing ? new RttEstimator(this, pool.Settings._fakeRtt) : null; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index 56b0ee771a4a6c..d45d1e6b188be7 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -70,7 +70,7 @@ internal sealed class HttpConnectionSettings internal TimeSpan? _fakeRtt; internal bool _enableDynamicHttp2StreamWindowSizing = true; - internal int _initialStreamWindowSize = 65535; + internal int _initialHttp2StreamWindowSize = Http2Connection.DefaultInitialWindowSize; internal int _streamWindowUpdateRatio = 8; internal int _streamWindowThresholdMultiplier = 1; @@ -128,7 +128,7 @@ public HttpConnectionSettings CloneAndNormalize() _connectCallback = _connectCallback, _plaintextStreamFilter = _plaintextStreamFilter, _fakeRtt = _fakeRtt, - _initialStreamWindowSize = _initialStreamWindowSize, + _initialHttp2StreamWindowSize = _initialHttp2StreamWindowSize, _streamWindowUpdateRatio = _streamWindowUpdateRatio, _streamWindowThresholdMultiplier = _streamWindowThresholdMultiplier, _enableDynamicHttp2StreamWindowSizing = _enableDynamicHttp2StreamWindowSizing diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index 903f0dc67258f5..b56421a96396b7 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -61,16 +61,14 @@ public bool EnableDynamicHttp2StreamWindowSizing set => _settings._enableDynamicHttp2StreamWindowSizing = value; } - public TimeSpan? FakeRtt + public int InitialHttp2StreamWindowSize { - get => _settings._fakeRtt; - set => _settings._fakeRtt = value; - } - - public int InitialStreamWindowSize - { - get => _settings._initialStreamWindowSize; - set => _settings._initialStreamWindowSize = value; + get => _settings._initialHttp2StreamWindowSize; + set + { + CheckDisposedOrStarted(); + _settings._initialHttp2StreamWindowSize = value; + } } public int StreamWindowUpdateRatio diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 7bc7e0385ac22a..4e4e8b80e6612f 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -25,8 +25,8 @@ public class LargeFileBenchmark_ShouldNotBeParallell { } public class LargeFileBenchmark : IDisposable { #pragma warning disable xUnit1004 // Test methods should not be skipped - public const string SkipSwitch = null; - //public const string SkipSwitch = "Local benchmark"; + //public const string SkipSwitch = null; + public const string SkipSwitch = "Local benchmark"; private readonly ITestOutputHelper _output; private LogHttpEventListener _listener; @@ -81,7 +81,7 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) SocketsHttpHandler handler = new SocketsHttpHandler() { EnableDynamicHttp2StreamWindowSizing = false, - InitialStreamWindowSize = initialWindowKbytes * 1024 + InitialHttp2StreamWindowSize = initialWindowKbytes * 1024 }; string details = $"SpecificWindow({initialWindowKbytes})"; return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler, details); @@ -306,9 +306,8 @@ private static SocketsHttpHandler CopyHandler(SocketsHttpHandler h) { return new SocketsHttpHandler() { - FakeRtt = h.FakeRtt, EnableDynamicHttp2StreamWindowSizing = h.EnableDynamicHttp2StreamWindowSizing, - InitialStreamWindowSize = h.InitialStreamWindowSize, + InitialHttp2StreamWindowSize = h.InitialHttp2StreamWindowSize, StreamWindowUpdateRatio = h.StreamWindowUpdateRatio, StreamWindowThresholdMultiplier = h.StreamWindowThresholdMultiplier, ConnectCallback = h.ConnectCallback From 49841af0da1ad499efb826d5f03758e50cbd2bf7 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 23 Jun 2021 17:31:41 +0200 Subject: [PATCH 089/101] RuntimeSettingParser --- .../src/System.Net.Http.csproj | 1 + .../src/System/Net/Http/DiagnosticsHandler.cs | 20 +- .../HttpConnectionSettings.cs | 56 +----- .../RuntimeSettingParser.cs | 65 +++++++ .../UnitTests/RuntimeSettingParserTest.cs | 174 ++++++++++++++++++ .../System.Net.Http.Unit.Tests.csproj | 5 +- 6 files changed, 251 insertions(+), 70 deletions(-) create mode 100644 src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RuntimeSettingParser.cs create mode 100644 src/libraries/System.Net.Http/tests/UnitTests/RuntimeSettingParserTest.cs diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index 7c4fc53c635e79..61e7a75229811f 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -184,6 +184,7 @@ + diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs index 4e4d730c4c188a..2119da9d724978 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs @@ -276,25 +276,7 @@ private static class Settings public static readonly bool s_activityPropagationEnabled = GetEnableActivityPropagationValue(); - private static bool GetEnableActivityPropagationValue() - { - // First check for the AppContext switch, giving it priority over the environment variable. - if (AppContext.TryGetSwitch(EnableActivityPropagationAppCtxSettingName, out bool enableActivityPropagation)) - { - return enableActivityPropagation; - } - - // AppContext switch wasn't used. Check the environment variable to determine which handler should be used. - string? envVar = Environment.GetEnvironmentVariable(EnableActivityPropagationEnvironmentVariableSettingName); - if (envVar != null && (envVar.Equals("false", StringComparison.OrdinalIgnoreCase) || envVar.Equals("0"))) - { - // Suppress Activity propagation. - return false; - } - - // Defaults to enabling Activity propagation. - return true; - } + private static bool GetEnableActivityPropagationValue() => RuntimeSettingParser.QueryRuntimeSettingSwitch(EnableActivityPropagationAppCtxSettingName, EnableActivityPropagationEnvironmentVariableSettingName, true); public static readonly DiagnosticListener s_diagnosticListener = new DiagnosticListener(DiagnosticsHandlerLoggingStrings.DiagnosticListenerName); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index d45d1e6b188be7..9cf26ee2471bc1 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -143,57 +143,13 @@ public HttpConnectionSettings CloneAndNormalize() return settings; } - private static bool AllowHttp2 - { - get - { - // Default to allowing HTTP/2, but enable that to be overridden by an - // AppContext switch, or by an environment variable being set to false/0. - - // First check for the AppContext switch, giving it priority over the environment variable. - if (AppContext.TryGetSwitch(Http2SupportAppCtxSettingName, out bool allowHttp2)) - { - return allowHttp2; - } - - // AppContext switch wasn't used. Check the environment variable. - string? envVar = Environment.GetEnvironmentVariable(Http2SupportEnvironmentVariableSettingName); - if (envVar != null && (envVar.Equals("false", StringComparison.OrdinalIgnoreCase) || envVar.Equals("0"))) - { - // Disallow HTTP/2 protocol. - return false; - } - - // Default to a maximum of HTTP/2. - return true; - } - } + // Default to allowing HTTP/2, but enable that to be overridden by an + // AppContext switch, or by an environment variable being set to false/0. + private static bool AllowHttp2 => RuntimeSettingParser.QueryRuntimeSettingSwitch(Http2SupportAppCtxSettingName, Http2SupportEnvironmentVariableSettingName, true); - private static bool AllowDraftHttp3 - { - get - { - // Default to allowing draft HTTP/3, but enable that to be overridden - // by an AppContext switch, or by an environment variable being set to false/0. - - // First check for the AppContext switch, giving it priority over the environment variable. - if (AppContext.TryGetSwitch(Http3DraftSupportAppCtxSettingName, out bool allowHttp3)) - { - return allowHttp3; - } - - // AppContext switch wasn't used. Check the environment variable. - string? envVar = Environment.GetEnvironmentVariable(Http3DraftSupportEnvironmentVariableSettingName); - if (envVar != null && (envVar.Equals("false", StringComparison.OrdinalIgnoreCase) || envVar.Equals("0"))) - { - // Disallow HTTP/3 protocol for HTTP endpoints. - return false; - } - - // Default to allow. - return true; - } - } + // Default to allowing draft HTTP/3, but enable that to be overridden + // by an AppContext switch, or by an environment variable being set to false/0. + private static bool AllowDraftHttp3 => RuntimeSettingParser.QueryRuntimeSettingSwitch(Http3DraftSupportAppCtxSettingName, Http3DraftSupportEnvironmentVariableSettingName, true); public bool EnableMultipleHttp2Connections => _enableMultipleHttp2Connections; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RuntimeSettingParser.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RuntimeSettingParser.cs new file mode 100644 index 00000000000000..2fc23028f9b58c --- /dev/null +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RuntimeSettingParser.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; + +namespace System.Net.Http +{ + internal static class RuntimeSettingParser + { + /// + /// Parse a value from an AppContext switch or an environment variable. + /// + public static bool QueryRuntimeSettingSwitch(string appCtxSettingName, string environmentVariableSettingName, bool defaultValue) + { + bool value; + + // First check for the AppContext switch, giving it priority over the environment variable. + if (AppContext.TryGetSwitch(appCtxSettingName, out value)) + { + return value; + } + + // AppContext switch wasn't used. Check the environment variable. + string? envVar = Environment.GetEnvironmentVariable(environmentVariableSettingName); + + if (bool.TryParse(envVar, out value)) + { + return value; + } + else if (uint.TryParse(envVar, out uint intVal)) + { + return intVal != 0; + } + + return defaultValue; + } + + /// + /// Parse an environment variable for an value. + /// + public static int ParseInt32EnvironmentVariableValue(string environmentVariableSettingName, int defaultValue) + { + string? envVar = Environment.GetEnvironmentVariable(environmentVariableSettingName); + + if (int.TryParse(envVar, NumberStyles.Any, CultureInfo.InvariantCulture, out int value)) + { + return value; + } + return defaultValue; + } + + /// + /// Parse an environment variable for a value. + /// + public static double ParseDoubleEnvironmentVariableValue(string environmentVariableSettingName, double defaultValue) + { + string? envVar = Environment.GetEnvironmentVariable(environmentVariableSettingName); + if (double.TryParse(envVar, NumberStyles.Any, CultureInfo.InvariantCulture, out double value)) + { + return value; + } + return defaultValue; + } + } +} diff --git a/src/libraries/System.Net.Http/tests/UnitTests/RuntimeSettingParserTest.cs b/src/libraries/System.Net.Http/tests/UnitTests/RuntimeSettingParserTest.cs new file mode 100644 index 00000000000000..d1aba5fde7c509 --- /dev/null +++ b/src/libraries/System.Net.Http/tests/UnitTests/RuntimeSettingParserTest.cs @@ -0,0 +1,174 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.DotNet.RemoteExecutor; +using Xunit; + +namespace System.Net.Http.Tests +{ + public class RuntimeSettingParserTest + { + public static bool SupportsRemoteExecutor = RemoteExecutor.IsSupported; + + [ConditionalTheory(nameof(SupportsRemoteExecutor))] + [InlineData(false)] + [InlineData(true)] + public void QueryRuntimeSettingSwitch_WhenNotSet_DefaultIsUsed(bool defaultValue) + { + static void RunTest(string defaultValueStr) + { + bool expected = bool.Parse(defaultValueStr); + bool actual = RuntimeSettingParser.QueryRuntimeSettingSwitch("Foo.Bar", "FOO_BAR", expected); + Assert.Equal(expected, actual); + } + + RemoteExecutor.Invoke(RunTest, defaultValue.ToString()).Dispose(); + } + + [ConditionalFact(nameof(SupportsRemoteExecutor))] + public void QueryRuntimeSettingSwitch_AppContextHasPriority() + { + static void RunTest() + { + AppContext.SetSwitch("Foo.Bar", false); + bool actual = RuntimeSettingParser.QueryRuntimeSettingSwitch("Foo.Bar", "FOO_BAR", true); + Assert.False(actual); + } + RemoteInvokeOptions options = new RemoteInvokeOptions() + { + StartInfo = new Diagnostics.ProcessStartInfo() + }; + options.StartInfo.EnvironmentVariables["FOO_BAR"] = "true"; + + RemoteExecutor.Invoke(RunTest, options).Dispose(); + } + + [ConditionalFact(nameof(SupportsRemoteExecutor))] + public void QueryRuntimeSettingSwitch_EnvironmentVariable() + { + static void RunTest() + { + bool actual = RuntimeSettingParser.QueryRuntimeSettingSwitch("Foo.Bar", "FOO_BAR", true); + Assert.False(actual); + } + RemoteInvokeOptions options = new RemoteInvokeOptions() + { + StartInfo = new Diagnostics.ProcessStartInfo() + }; + options.StartInfo.EnvironmentVariables["FOO_BAR"] = "false"; + + RemoteExecutor.Invoke(RunTest, options).Dispose(); + } + + [ConditionalFact(nameof(SupportsRemoteExecutor))] + public void QueryRuntimeSettingSwitch_InvalidValue_FallbackToDefault() + { + static void RunTest() + { + bool actual = RuntimeSettingParser.QueryRuntimeSettingSwitch("Foo.Bar", "FOO_BAR", true); + Assert.True(actual); + } + RemoteInvokeOptions options = new RemoteInvokeOptions() + { + StartInfo = new Diagnostics.ProcessStartInfo() + }; + options.StartInfo.EnvironmentVariables["FOO_BAR"] = "cheese"; + + RemoteExecutor.Invoke(RunTest, options).Dispose(); + } + + [ConditionalFact(nameof(SupportsRemoteExecutor))] + public void ParseInt32EnvironmentVariableValue_WhenNotSet_DefaultIsUsed() + { + static void RunTest() + { + int actual = RuntimeSettingParser.ParseInt32EnvironmentVariableValue("FOO_BAR", -42); + Assert.Equal(-42, actual); + } + RemoteExecutor.Invoke(RunTest).Dispose(); + } + + [ConditionalFact(nameof(SupportsRemoteExecutor))] + public void ParseInt32EnvironmentVariableValue_ValidValue() + { + static void RunTest() + { + int actual = RuntimeSettingParser.ParseInt32EnvironmentVariableValue("FOO_BAR", -42); + Assert.Equal(84, actual); + } + + RemoteInvokeOptions options = new RemoteInvokeOptions() + { + StartInfo = new Diagnostics.ProcessStartInfo() + }; + options.StartInfo.EnvironmentVariables["FOO_BAR"] = "84"; + + RemoteExecutor.Invoke(RunTest, options).Dispose(); + } + + [ConditionalFact(nameof(SupportsRemoteExecutor))] + public void ParseInt32EnvironmentVariableValue_InvalidValue_FallbackToDefault() + { + static void RunTest() + { + int actual = RuntimeSettingParser.ParseInt32EnvironmentVariableValue("FOO_BAR", -42); + Assert.Equal(-42, actual); + } + + RemoteInvokeOptions options = new RemoteInvokeOptions() + { + StartInfo = new Diagnostics.ProcessStartInfo() + }; + options.StartInfo.EnvironmentVariables["FOO_BAR"] = "-~4!"; + + RemoteExecutor.Invoke(RunTest, options).Dispose(); + } + + [ConditionalFact(nameof(SupportsRemoteExecutor))] + public void ParseDoubleEnvironmentVariableValue_WhenNotSet_DefaultIsUsed() + { + static void RunTest() + { + double actual = RuntimeSettingParser.ParseDoubleEnvironmentVariableValue("FOO_BAR", -0.42); + Assert.Equal(-0.42, actual); + } + RemoteExecutor.Invoke(RunTest).Dispose(); + } + + [ConditionalFact(nameof(SupportsRemoteExecutor))] + public void ParseDoubleEnvironmentVariableValue_ValidValue() + { + static void RunTest() + { + double actual = RuntimeSettingParser.ParseDoubleEnvironmentVariableValue("FOO_BAR", -0.42); + Assert.Equal(0.84, actual); + } + + RemoteInvokeOptions options = new RemoteInvokeOptions() + { + StartInfo = new Diagnostics.ProcessStartInfo() + }; + options.StartInfo.EnvironmentVariables["FOO_BAR"] = "0.84"; + + RemoteExecutor.Invoke(RunTest, options).Dispose(); + } + + [ConditionalFact(nameof(SupportsRemoteExecutor))] + public void ParseDoubleEnvironmentVariableValue_InvalidValue_FallbackToDefault() + { + static void RunTest() + { + double actual = RuntimeSettingParser.ParseDoubleEnvironmentVariableValue("FOO_BAR", -0.42); + Assert.Equal(-0.42, actual); + } + + RemoteInvokeOptions options = new RemoteInvokeOptions() + { + StartInfo = new Diagnostics.ProcessStartInfo() + }; + options.StartInfo.EnvironmentVariables["FOO_BAR"] = "-~4!"; + + RemoteExecutor.Invoke(RunTest, options).Dispose(); + } + } +} diff --git a/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj b/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj index 186f1cbd888081..97e3c38b9263df 100644 --- a/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj @@ -1,4 +1,4 @@ - + ../../src/Resources/Strings.resx true @@ -236,6 +236,8 @@ Link="ProductionCode\System\Net\Http\StreamToStreamCopy.cs" /> + + From b75827c77466aa49a8f66b1c4be155f482cee07f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 23 Jun 2021 19:22:55 +0200 Subject: [PATCH 090/101] final API & runtime switches --- .../System/Net/Http/HttpHandlerDefaults.cs | 2 + .../System.Net.Http/ref/System.Net.Http.cs | 4 - .../src/System.Net.Http.csproj | 4 +- .../BrowserHttpHandler/SocketsHttpHandler.cs | 18 ----- .../SocketsHttpHandler/Http2Connection.cs | 5 +- .../Http/SocketsHttpHandler/Http2Stream.cs | 6 +- .../Http2StreamWindowManager.cs | 31 ++------ .../HttpConnectionSettings.cs | 73 +++++++++++++++---- .../SocketsHttpHandler/SocketsHttpHandler.cs | 18 ----- .../FunctionalTests/_LargeFileBenchmark.cs | 58 +++++++++------ .../System.Net.Http.Unit.Tests.csproj | 4 +- 11 files changed, 112 insertions(+), 111 deletions(-) diff --git a/src/libraries/Common/src/System/Net/Http/HttpHandlerDefaults.cs b/src/libraries/Common/src/System/Net/Http/HttpHandlerDefaults.cs index 3bf335ec33cea4..c3af562389eab8 100644 --- a/src/libraries/Common/src/System/Net/Http/HttpHandlerDefaults.cs +++ b/src/libraries/Common/src/System/Net/Http/HttpHandlerDefaults.cs @@ -28,5 +28,7 @@ internal static partial class HttpHandlerDefaults public static readonly TimeSpan DefaultPooledConnectionIdleTimeout = TimeSpan.FromMinutes(1); public static readonly TimeSpan DefaultExpect100ContinueTimeout = TimeSpan.FromSeconds(1); public static readonly TimeSpan DefaultConnectTimeout = Timeout.InfiniteTimeSpan; + public const int DefaultHttp2MaxStreamWindowSize = 16 * 1024 * 1024; + public const double DefaultHttp2StreamWindowScaleThresholdMultiplier = 1.0; } } diff --git a/src/libraries/System.Net.Http/ref/System.Net.Http.cs b/src/libraries/System.Net.Http/ref/System.Net.Http.cs index 43c868a2ffb805..1087699920f169 100644 --- a/src/libraries/System.Net.Http/ref/System.Net.Http.cs +++ b/src/libraries/System.Net.Http/ref/System.Net.Http.cs @@ -353,12 +353,8 @@ protected override void SerializeToStream(System.IO.Stream stream, System.Net.Tr public sealed partial class SocketsHttpHandler : System.Net.Http.HttpMessageHandler { public SocketsHttpHandler() { } - [System.Runtime.Versioning.UnsupportedOSPlatformGuardAttribute("browser")] public int InitialHttp2StreamWindowSize { get { throw null; } set { } } public static bool IsSupported { get { throw null; } } - public bool EnableDynamicHttp2StreamWindowSizing { get { throw null; } set { } } - public int StreamWindowUpdateRatio { get { throw null; } set { } } - public int StreamWindowThresholdMultiplier { 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 { } } diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index 61e7a75229811f..80bbce5df23f40 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -1,4 +1,4 @@ - + win true @@ -62,6 +62,7 @@ + @@ -184,7 +185,6 @@ - diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs index 5d0b6bc18e8fdc..aeafd2db47e22b 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/SocketsHttpHandler.cs @@ -25,30 +25,12 @@ public bool UseCookies set => throw new PlatformNotSupportedException(); } - public bool EnableDynamicHttp2StreamWindowSizing - { - get => throw new PlatformNotSupportedException(); - set => throw new PlatformNotSupportedException(); - } - public int InitialHttp2StreamWindowSize { get => throw new PlatformNotSupportedException(); set => throw new PlatformNotSupportedException(); } - public int StreamWindowUpdateRatio - { - get => throw new PlatformNotSupportedException(); - set => throw new PlatformNotSupportedException(); - } - - public int StreamWindowThresholdMultiplier - { - get => throw new PlatformNotSupportedException(); - set => throw new PlatformNotSupportedException(); - } - [AllowNull] public CookieContainer CookieContainer { diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index b1b048c97f8333..5a70d36f0dc6bd 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -134,8 +134,9 @@ public Http2Connection(HttpConnectionPool pool, Stream stream) _connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialWindowSize); _concurrentStreams = new CreditManager(this, nameof(_concurrentStreams), InitialMaxConcurrentStreams); InitialClientStreamWindowSize = pool.Settings._initialHttp2StreamWindowSize; - _rttEstimator = pool.Settings._enableDynamicHttp2StreamWindowSizing ? - new RttEstimator(this, pool.Settings._fakeRtt) : null; + _rttEstimator = pool.Settings._disableDynamicHttp2WindowSizing ? + null : + new RttEstimator(this); _writeChannel = Channel.CreateUnbounded(s_channelOptions); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index 89bcee034f20c1..baedfa840688e8 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -97,9 +97,9 @@ public Http2Stream(HttpRequestMessage request, Http2Connection connection) _responseBuffer = new MultiArrayBuffer(InitialStreamBufferSize); - _windowManager = connection._pool.Settings._enableDynamicHttp2StreamWindowSizing ? - new DynamicHttp2StreamWindowManager(connection, this) : - new Http2StreamWindowManager(connection, this); + _windowManager = connection._pool.Settings._disableDynamicHttp2WindowSizing ? + new Http2StreamWindowManager(connection, this) : + new DynamicHttp2StreamWindowManager(connection, this); Trace($"_windowManager: {_windowManager.GetType().Name}"); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 57d09340507c94..8399fade9da05d 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -15,8 +15,7 @@ internal sealed partial class Http2Connection private class Http2StreamWindowManager { - // See comment on ConnectionWindowThreshold. - protected int _streamWindowUpdateRatio; + public const int StreamWindowUpdateRatio = 8; protected int _delivered; protected int _streamWindowSize; @@ -29,13 +28,12 @@ public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) _stream = stream; _streamWindowSize = connection.InitialClientStreamWindowSize; - _streamWindowUpdateRatio = _connection._pool.Settings._streamWindowUpdateRatio; - _stream.TraceFlowControl($"StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}, streamWindowUpdateRatio: {_streamWindowUpdateRatio}"); + _stream.TraceFlowControl($"InitialClientStreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); } internal int StreamWindowSize => _streamWindowSize; - internal int StreamWindowThreshold => _streamWindowSize / _streamWindowUpdateRatio; + internal int StreamWindowThreshold => _streamWindowSize / StreamWindowUpdateRatio; public virtual void AdjustWindow(int bytesConsumed) { @@ -69,13 +67,13 @@ private class DynamicHttp2StreamWindowManager : Http2StreamWindowManager private long _lastWindowUpdate; - private int _streamWindowThresholdMultiplier = 1; + private double _windowScaleThresholdMultiplier; public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream stream) : base(connection, stream) { - _streamWindowThresholdMultiplier = connection._pool.Settings._streamWindowThresholdMultiplier; - _stream.TraceFlowControl($" _streamWindowThresholdMultiplier:{_streamWindowThresholdMultiplier} | Stopwatch: IsHighResolution={Stopwatch.IsHighResolution}, Frequency={Stopwatch.Frequency}"); + _windowScaleThresholdMultiplier = connection._pool.Settings._http2StreamWindowScaleThresholdMultiplier; + _stream.TraceFlowControl($" http2StreamWindowScaleThresholdMultiplier: {_windowScaleThresholdMultiplier}"); _lastWindowUpdate = Stopwatch.GetTimestamp(); } @@ -95,7 +93,7 @@ public override void AdjustWindow(int bytesConsumed) TimeSpan rtt = _connection._rttEstimator.MinRtt; TimeSpan dt = StopwatchTicksToTimeSpan(currentTime - _lastWindowUpdate); - if (_delivered * rtt.Ticks > StreamWindowThreshold * dt.Ticks * _streamWindowThresholdMultiplier) + if (_delivered * rtt.Ticks > _streamWindowSize * dt.Ticks * _windowScaleThresholdMultiplier) { windowSizeIncrement += _streamWindowSize; _streamWindowSize *= 2; @@ -140,29 +138,19 @@ private enum Status public TimeSpan MinRtt { get; private set; } - private readonly TimeSpan? _staticRtt; - - public RttEstimator(Http2Connection connection, TimeSpan? staticRtt) + public RttEstimator(Http2Connection connection) { _connection = connection; - _staticRtt = staticRtt; - if (_staticRtt.HasValue) - { - MinRtt = _staticRtt.Value; - _connection.TraceFlowControl($"Using static RTT: {MinRtt.TotalMilliseconds} ms"); - } } internal void OnInitialSettingsSent() { - if (_staticRtt.HasValue) return; _connection.TraceFlowControl("Initial SETTINGS sent"); _pingSentTimestamp = Stopwatch.GetTimestamp(); } internal void OnInitialSettingsAckReceived() { - if (_staticRtt.HasValue) return; _connection.TraceFlowControl("Initial SETTINGS ACK received"); RefreshRtt(); _status = Status.Waiting; @@ -170,8 +158,6 @@ internal void OnInitialSettingsAckReceived() internal void OnDataOrHeadersReceived() { - if (_staticRtt.HasValue) return; - if (_status == Status.Waiting) { long now = Stopwatch.GetTimestamp(); @@ -193,7 +179,6 @@ internal void OnDataOrHeadersReceived() internal void OnPingAckReceived(long payload) { Debug.Assert(payload < 0); - if (_staticRtt.HasValue) return; if (_status != Status.PingSent) return; if (Interlocked.Read(ref _pingCounter) != payload) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index 9cf26ee2471bc1..30cb7478b3dd9a 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -15,11 +15,6 @@ namespace System.Net.Http /// Provides a state bag of settings for configuring HTTP connections. internal sealed class HttpConnectionSettings { - private const string Http2SupportEnvironmentVariableSettingName = "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2SUPPORT"; - private const string Http2SupportAppCtxSettingName = "System.Net.Http.SocketsHttpHandler.Http2Support"; - private const string Http3DraftSupportEnvironmentVariableSettingName = "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP3DRAFTSUPPORT"; - private const string Http3DraftSupportAppCtxSettingName = "System.Net.SocketsHttpHandler.Http3DraftSupport"; - internal DecompressionMethods _automaticDecompression = HttpHandlerDefaults.DefaultAutomaticDecompression; internal bool _useCookies = HttpHandlerDefaults.DefaultUseCookies; @@ -67,12 +62,11 @@ internal sealed class HttpConnectionSettings internal IDictionary? _properties; - internal TimeSpan? _fakeRtt; - internal bool _enableDynamicHttp2StreamWindowSizing = true; - + // Http2 flow control settings: + internal bool _disableDynamicHttp2WindowSizing = DisableDynamicHttp2WindowSizing; + internal int _maxHttp2StreamWindowSize = MaxHttp2StreamWindowSize; + internal double _http2StreamWindowScaleThresholdMultiplier = Http2StreamWindowScaleThresholdMultiplier; internal int _initialHttp2StreamWindowSize = Http2Connection.DefaultInitialWindowSize; - internal int _streamWindowUpdateRatio = 8; - internal int _streamWindowThresholdMultiplier = 1; public HttpConnectionSettings() { @@ -127,11 +121,10 @@ public HttpConnectionSettings CloneAndNormalize() _enableMultipleHttp2Connections = _enableMultipleHttp2Connections, _connectCallback = _connectCallback, _plaintextStreamFilter = _plaintextStreamFilter, - _fakeRtt = _fakeRtt, + _disableDynamicHttp2WindowSizing = _disableDynamicHttp2WindowSizing, + _maxHttp2StreamWindowSize = _maxHttp2StreamWindowSize, + _http2StreamWindowScaleThresholdMultiplier = _http2StreamWindowScaleThresholdMultiplier, _initialHttp2StreamWindowSize = _initialHttp2StreamWindowSize, - _streamWindowUpdateRatio = _streamWindowUpdateRatio, - _streamWindowThresholdMultiplier = _streamWindowThresholdMultiplier, - _enableDynamicHttp2StreamWindowSizing = _enableDynamicHttp2StreamWindowSizing }; // TODO: Remove if/when QuicImplementationProvider is removed from System.Net.Quic. @@ -145,11 +138,59 @@ public HttpConnectionSettings CloneAndNormalize() // Default to allowing HTTP/2, but enable that to be overridden by an // AppContext switch, or by an environment variable being set to false/0. - private static bool AllowHttp2 => RuntimeSettingParser.QueryRuntimeSettingSwitch(Http2SupportAppCtxSettingName, Http2SupportEnvironmentVariableSettingName, true); + private static bool AllowHttp2 => RuntimeSettingParser.QueryRuntimeSettingSwitch( + "System.Net.Http.SocketsHttpHandler.Http2Support", + "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2SUPPORT", + true); // Default to allowing draft HTTP/3, but enable that to be overridden // by an AppContext switch, or by an environment variable being set to false/0. - private static bool AllowDraftHttp3 => RuntimeSettingParser.QueryRuntimeSettingSwitch(Http3DraftSupportAppCtxSettingName, Http3DraftSupportEnvironmentVariableSettingName, true); + private static bool AllowDraftHttp3 => RuntimeSettingParser.QueryRuntimeSettingSwitch( + "System.Net.SocketsHttpHandler.Http3DraftSupport", + "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP3DRAFTSUPPORT", + true); + + // Switch to disable the HTTP/2 dynamic window scaling algorithm. Enabled by default. + private static bool DisableDynamicHttp2WindowSizing => RuntimeSettingParser.QueryRuntimeSettingSwitch( + "System.Net.SocketsHttpHandler.Http2FlowControl.DisableDynamic2WindowSizing", + "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2FLOWCONTROL_DISABLEDYNAMICWINDOWSIZING", + false); + + // The maximum size of the HTTP/2 stream receive window. Defaults to 16 MB. + private static int MaxHttp2StreamWindowSize + { + get + { + int value = RuntimeSettingParser.ParseInt32EnvironmentVariableValue( + "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_FLOWCONTROL_MAXSTREAMWINDOWSIZE", + HttpHandlerDefaults.DefaultHttp2MaxStreamWindowSize); + + // Disallow small values: + if (value < Http2Connection.DefaultInitialWindowSize) + { + value = Http2Connection.DefaultInitialWindowSize; + } + return value; + } + } + + // Defaults to 1.0. Higher values result in shorter window, but slower downloads. + private static double Http2StreamWindowScaleThresholdMultiplier + { + get + { + double value = RuntimeSettingParser.ParseDoubleEnvironmentVariableValue( + "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_FLOWCONTROL_STREAMWINDOWSCALETHRESHOLDMULTIPLIER", + HttpHandlerDefaults.DefaultHttp2StreamWindowScaleThresholdMultiplier); + + // Disallow negative values: + if (value < 0) + { + value = HttpHandlerDefaults.DefaultHttp2StreamWindowScaleThresholdMultiplier; + } + return value; + } + } public bool EnableMultipleHttp2Connections => _enableMultipleHttp2Connections; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index b56421a96396b7..6e183e77c99faf 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -55,12 +55,6 @@ public bool UseCookies } } - public bool EnableDynamicHttp2StreamWindowSizing - { - get => _settings._enableDynamicHttp2StreamWindowSizing; - set => _settings._enableDynamicHttp2StreamWindowSizing = value; - } - public int InitialHttp2StreamWindowSize { get => _settings._initialHttp2StreamWindowSize; @@ -71,18 +65,6 @@ public int InitialHttp2StreamWindowSize } } - public int StreamWindowUpdateRatio - { - get => _settings._streamWindowUpdateRatio; - set => _settings._streamWindowUpdateRatio = value; - } - - public int StreamWindowThresholdMultiplier - { - get => _settings._streamWindowThresholdMultiplier; - set => _settings._streamWindowThresholdMultiplier = value; - } - [AllowNull] public CookieContainer CookieContainer { diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 4e4e8b80e6612f..27b2b11fdea6cc 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Net.NetworkInformation; using System.Net.Sockets; +using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading; @@ -25,8 +26,8 @@ public class LargeFileBenchmark_ShouldNotBeParallell { } public class LargeFileBenchmark : IDisposable { #pragma warning disable xUnit1004 // Test methods should not be skipped - //public const string SkipSwitch = null; - public const string SkipSwitch = "Local benchmark"; + public const string SkipSwitch = null; + //public const string SkipSwitch = "Local benchmark"; private readonly ITestOutputHelper _output; private LogHttpEventListener _listener; @@ -55,7 +56,7 @@ public LargeFileBenchmark(ITestOutputHelper output) //private const string ReportDir = @"C:\_dev\r6r\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; //private const string ReportDir = @"C:\Users\anfirszo\dev\dotnet\6.0\runtime\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; - private const string ReportDir = @"C:\_dev\r6r\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Release\TestResults"; + private const string ReportDir = @"C:\_dev\r6d\artifacts\bin\System.Net.Http.Functional.Tests\net6.0-windows-Debug\TestResults"; [Theory(Skip = SkipSwitch)] [InlineData(BenchmarkServer)] @@ -80,13 +81,14 @@ private Task Download20_SpecificWindow(string hostName, int initialWindowKbytes) { SocketsHttpHandler handler = new SocketsHttpHandler() { - EnableDynamicHttp2StreamWindowSizing = false, InitialHttp2StreamWindowSize = initialWindowKbytes * 1024 }; + ChangeSettingValue(handler, "_disableDynamicHttp2WindowSizing", true); string details = $"SpecificWindow({initialWindowKbytes})"; return TestHandler($"SocketsHttpHandler HTTP 2.0 - W: {initialWindowKbytes} KB", hostName, true, LengthMb, handler, details); } + public static TheoryData Download20_Data = new TheoryData { { BenchmarkServer, 8, 1 }, @@ -139,13 +141,13 @@ private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, i _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); } - var handler = new SocketsHttpHandler() - { - StreamWindowUpdateRatio = ratio, - StreamWindowThresholdMultiplier = correction - }; + var handler = new SocketsHttpHandler(); + + double multiplier = (double)ratio / correction; + ChangeSettingValue(handler, "_http2StreamWindowScaleThresholdMultiplier", multiplier); + string details = $"Dynamic_R({ratio})_C({correction})"; - await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} ratio={ratio} correction={handler.StreamWindowThresholdMultiplier}", + await TestHandler($"SocketsHttpHandler HTTP 2.0 Dynamic single stream | host:{hostName} multiplier: {multiplier}", hostName, true, LengthMb, handler, details); } @@ -159,11 +161,7 @@ public async Task Download20_Dynamic_MultiStream(string hostName, int streamCoun _listener.Filter = m => m.Contains("[FlowControl]") && m.Contains("Updated"); string info = $"SocketsHttpHandler HTTP 2.0 Dynamic {streamCount} concurrent streams R=8 D=8"; - var handler = new SocketsHttpHandler() - { - StreamWindowUpdateRatio = 8, - StreamWindowThresholdMultiplier = 8 - }; + var handler = new SocketsHttpHandler(); string details = $"SC({streamCount})"; @@ -304,14 +302,14 @@ async Task SendRequestAsync(int i) private static SocketsHttpHandler CopyHandler(SocketsHttpHandler h) { - return new SocketsHttpHandler() - { - EnableDynamicHttp2StreamWindowSizing = h.EnableDynamicHttp2StreamWindowSizing, - InitialHttp2StreamWindowSize = h.InitialHttp2StreamWindowSize, - StreamWindowUpdateRatio = h.StreamWindowUpdateRatio, - StreamWindowThresholdMultiplier = h.StreamWindowThresholdMultiplier, - ConnectCallback = h.ConnectCallback - }; + var clone = new SocketsHttpHandler(); + + FieldInfo fieldInfo = h.GetType().GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance); + object settings = fieldInfo.GetValue(h); + MethodInfo m = settings.GetType().GetMethod("CloneAndNormalize", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + fieldInfo.SetValue(clone, m.Invoke(settings, Array.Empty())); + return clone; } private static async ValueTask CustomConnect(SocketsHttpConnectionContext ctx, CancellationToken cancellationToken) @@ -359,6 +357,20 @@ static HttpRequestMessage GenerateRequestMessage(string hostName, bool http2, do return msg; } + + + private static object GetInnerSettings(SocketsHttpHandler handler) + { + FieldInfo fieldInfo = handler.GetType().GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance); + return fieldInfo.GetValue(handler); + } + + private static void ChangeSettingValue(SocketsHttpHandler handler, string name, object value) + { + object settings = GetInnerSettings(handler); + FieldInfo field = settings.GetType().GetField(name, BindingFlags.NonPublic | BindingFlags.Instance); + field.SetValue(settings, value); + } } public sealed class LogHttpEventListener : EventListener diff --git a/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj b/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj index 97e3c38b9263df..47638d59fbef35 100644 --- a/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj @@ -237,7 +237,7 @@ + Link="ProductionCode\System\Net\Http\SocketsHttpHandler\RuntimeSettingParser.cs" /> - + From 5e388f41d82bb05a11b79efa9ef451fe01784827 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 23 Jun 2021 19:29:19 +0200 Subject: [PATCH 091/101] fix _LargeFileBenchmark --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 27b2b11fdea6cc..c4523938adcaea 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -143,7 +143,7 @@ private async Task Download20_Dynamic_SingleStream(string hostName, int ratio, i var handler = new SocketsHttpHandler(); - double multiplier = (double)ratio / correction; + double multiplier = (double)correction / ratio; ChangeSettingValue(handler, "_http2StreamWindowScaleThresholdMultiplier", multiplier); string details = $"Dynamic_R({ratio})_C({correction})"; From 1050b153fb66537c948536e93bab65ccd82d8c1e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 23 Jun 2021 19:29:52 +0200 Subject: [PATCH 092/101] disable _LargeFileBenchmark for CI --- .../tests/FunctionalTests/_LargeFileBenchmark.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index c4523938adcaea..1f25d420ff8b71 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -1,15 +1,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.Tracing; using System.IO; using System.Linq; -using System.Net.NetworkInformation; using System.Net.Sockets; using System.Reflection; -using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Channels; @@ -26,8 +23,8 @@ public class LargeFileBenchmark_ShouldNotBeParallell { } public class LargeFileBenchmark : IDisposable { #pragma warning disable xUnit1004 // Test methods should not be skipped - public const string SkipSwitch = null; - //public const string SkipSwitch = "Local benchmark"; + //public const string SkipSwitch = null; + public const string SkipSwitch = "Local benchmark"; private readonly ITestOutputHelper _output; private LogHttpEventListener _listener; From e03d3a1f33779711a91ce4490bfa2821d7fef0ca Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 24 Jun 2021 16:13:17 +0200 Subject: [PATCH 093/101] InitialHttp2StreamWindowSize argument validation --- .../src/Resources/Strings.resx | 3 + .../SocketsHttpHandler/SocketsHttpHandler.cs | 6 + ...SocketsHttpHandlerTest.Http2FlowControl.cs | 169 ++++++++++++++++++ .../FunctionalTests/SocketsHttpHandlerTest.cs | 23 +++ .../System.Net.Http.Functional.Tests.csproj | 1 + 5 files changed, 202 insertions(+) create mode 100644 src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs diff --git a/src/libraries/System.Net.Http/src/Resources/Strings.resx b/src/libraries/System.Net.Http/src/Resources/Strings.resx index 0f725d661b20db..35ce6f88feb339 100644 --- a/src/libraries/System.Net.Http/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Http/src/Resources/Strings.resx @@ -429,6 +429,9 @@ An HTTP/2 connection could not be established because the server did not complete the HTTP/2 handshake. + + The initial HTTP/2 stream window size must be between {0} and {1}. + This method is not implemented by this class. diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs index 6e183e77c99faf..34f349fb79589e 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs @@ -60,6 +60,12 @@ public int InitialHttp2StreamWindowSize get => _settings._initialHttp2StreamWindowSize; set { + if (value < Http2Connection.DefaultInitialWindowSize || value > _settings._maxHttp2StreamWindowSize) + { + throw new ArgumentOutOfRangeException( + nameof(InitialHttp2StreamWindowSize), + SR.Format(SR.net_http_http2_invalidinitialstreamwindowsize, Http2Connection.DefaultInitialWindowSize, _settings._maxHttp2StreamWindowSize)); + } CheckDisposedOrStarted(); _settings._initialHttp2StreamWindowSize = value; } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs new file mode 100644 index 00000000000000..04e3c5d6bed474 --- /dev/null +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs @@ -0,0 +1,169 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Quic; +using System.Net.Quic.Implementations; +using System.Net.Security; +using System.Net.Sockets; +using System.Net.Test.Common; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.DotNet.RemoteExecutor; +using Xunit; +using Xunit.Abstractions; + +namespace System.Net.Http.Functional.Tests +{ + [CollectionDefinition(nameof(NonParallelTestCollection), DisableParallelization = true)] + public class NonParallelTestCollection + { + } + + // This test class contains tests which are strongly timing-dependant. + // There are two mitigations avoid flaky behavior on CI: + // - The tests are executed in a non-parallel manner + // - The timing-dependent behavior is pushed to the extremes, making it very unlikely to fail. + [Collection(nameof(NonParallelTestCollection))] + [ConditionalClass(typeof(SocketsHttpHandler_Http2FlowControl_Test), nameof(IsSupported))] + public sealed class SocketsHttpHandler_Http2FlowControl_Test : HttpClientHandlerTestBase + { + public static readonly bool IsSupported = PlatformDetection.SupportsAlpn && PlatformDetection.IsNotBrowser; + + const int DefaultInitialWindowSize = 65535; + + protected override Version UseVersion => HttpVersion20.Value; + + public SocketsHttpHandler_Http2FlowControl_Test(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public async Task Http2_FlowControl_HighBandwidthDelayProduct_ClientStreamReceiveWindowWindowScalesUp() + { + int maxCredit = await TestClientWindowScalingAsync( + TimeSpan.FromMilliseconds(30), + TimeSpan.Zero, + 2 * 1024 * 1024); + + // Expect the client receive window to grow over 1MB: + Assert.True(maxCredit > 1024 * 1024); + } + + [Fact] + public async Task Http2_FlowControl_LowBandwidthDelayProduct_ClientStreamReceiveWindowStopsScaling() + { + int maxCredit = await TestClientWindowScalingAsync( + TimeSpan.Zero, + TimeSpan.FromMilliseconds(15), + 2 * 1024 * 1024); + + // Expect the client receive window to stay below 1MB: + Assert.True(maxCredit < 1024 * 1024); + } + + private async Task TestClientWindowScalingAsync(TimeSpan networkDelay, TimeSpan slowBandwidthSimDelay, int bytesToDownload) + { + TimeSpan timeout = TimeSpan.FromSeconds(30); + + using Http2LoopbackServer server = Http2LoopbackServer.CreateServer(); + using HttpClient client = CreateHttpClient(); + + Task clientTask = client.GetAsync(server.Address); + Http2LoopbackConnection connection = await server.AcceptConnectionAsync().ConfigureAwait(false); + SettingsFrame clientSettingsFrame = await connection.ReadSettingsAsync().ConfigureAwait(false); + + // send server SETTINGS: + await connection.WriteFrameAsync(new SettingsFrame()).ConfigureAwait(false); + + // Initial client SETTINGS also works as a PING. Do not send ACK immediately to avoid low RTT estimation + await Task.Delay(networkDelay); + await connection.WriteFrameAsync(new SettingsFrame(FrameFlags.Ack, new SettingsEntry[0])); + + // Expect SETTINGS ACK from client: + await connection.ExpectSettingsAckAsync(); + + int maxCredit = (int)clientSettingsFrame.Entries.SingleOrDefault(e => e.SettingId == SettingId.InitialWindowSize).Value; + if (maxCredit == default) maxCredit = DefaultInitialWindowSize; + int credit = maxCredit; + + int streamId = await connection.ReadRequestHeaderAsync(); + // Write the response. + await connection.SendDefaultResponseHeadersAsync(streamId); + + using SemaphoreSlim creditReceivedSemaphore = new SemaphoreSlim(0); + using SemaphoreSlim writeSemaphore = new SemaphoreSlim(1); + int remainingBytes = bytesToDownload; + _ = Task.Run(ProcessIncomingFramesAsync); + byte[] buffer = new byte[16384]; + + while (remainingBytes > 0) + { + Wait(slowBandwidthSimDelay); + while (credit == 0) await creditReceivedSemaphore.WaitAsync(timeout); + int bytesToSend = Math.Min(Math.Min(buffer.Length, credit), remainingBytes); + + Memory responseData = buffer.AsMemory(0, bytesToSend); + + int nextRemainingBytes = remainingBytes - bytesToSend; + bool endStream = nextRemainingBytes == 0; + + await writeSemaphore.WaitAsync(); + await connection.SendResponseDataAsync(streamId, responseData, endStream); + writeSemaphore.Release(); + + credit -= bytesToSend; + + remainingBytes = nextRemainingBytes; + } + + using HttpResponseMessage response = await clientTask; + int dataReceived = (await response.Content.ReadAsByteArrayAsync()).Length; + Assert.Equal(bytesToDownload, dataReceived); + + return maxCredit; + + async Task ProcessIncomingFramesAsync() + { + while (remainingBytes > 0) + { + Frame frame = await connection.ReadFrameAsync(timeout); + + if (frame is PingFrame pingFrame) + { + // Simulate network delay for RTT PING + Wait(networkDelay); + + await writeSemaphore.WaitAsync(); + await connection.SendPingAckAsync(pingFrame.Data); + writeSemaphore.Release(); + } + else if (frame is WindowUpdateFrame windowUpdateFrame) + { + // Ignore connection window: + if (windowUpdateFrame.StreamId != streamId) continue; + + credit += windowUpdateFrame.UpdateSize; + maxCredit = Math.Max(credit, maxCredit); // Detect if client grows the window + _output.WriteLine("MaxCredit: " + maxCredit); + creditReceivedSemaphore.Release(); + } + else if (frame is not null) + { + throw new Exception("Unexpected frame: " + frame); + } + } + } + + static void Wait(TimeSpan dt) { if (dt != TimeSpan.Zero) Thread.Sleep(dt); } + } + } +} diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index d48ede7b0aaa2c..2624f637e4af5b 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -1933,6 +1933,27 @@ public void PlaintextStreamFilter_GetSet_Roundtrips() } } + [Theory] + [InlineData(65535)] + [InlineData(1048576)] + public void InitialHttp2StreamWindowSize_Roundtrips(int value) + { + using var handler = new SocketsHttpHandler(); + handler.InitialHttp2StreamWindowSize = value; + Assert.Equal(value, handler.InitialHttp2StreamWindowSize); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(65534)] + [InlineData(32 * 1024 * 1024)] + public void InitialHttp2StreamWindowSize_InvalidValue_ThrowsArgumentOutOfRangeException(int value) + { + using var handler = new SocketsHttpHandler(); + Assert.Throws(() => handler.InitialHttp2StreamWindowSize = value); + } + [Theory] [InlineData(false)] [InlineData(true)] @@ -1972,6 +1993,7 @@ await Assert.ThrowsAnyAsync(() => Assert.True(handler.UseProxy); Assert.Null(handler.ConnectCallback); Assert.Null(handler.PlaintextStreamFilter); + Assert.Equal(65535, handler.InitialHttp2StreamWindowSize); Assert.Throws(expectedExceptionType, () => handler.AllowAutoRedirect = false); Assert.Throws(expectedExceptionType, () => handler.AutomaticDecompression = DecompressionMethods.GZip); @@ -1993,6 +2015,7 @@ await Assert.ThrowsAnyAsync(() => Assert.Throws(expectedExceptionType, () => handler.KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests); Assert.Throws(expectedExceptionType, () => handler.ConnectCallback = (context, token) => default); Assert.Throws(expectedExceptionType, () => handler.PlaintextStreamFilter = (context, token) => default); + Assert.Throws(expectedExceptionType, () => handler.InitialHttp2StreamWindowSize = 128 * 1024); } } } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj index fb45326a50964f..d9a87490fc0d62 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj @@ -126,6 +126,7 @@ Link="Common\System\Net\Http\HttpClientHandlerTest.DefaultProxyCredentials.cs" /> + From 857b8ac9fb91ee2ba8945f671bbe3ca909254ff1 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 24 Jun 2021 17:10:22 +0200 Subject: [PATCH 094/101] more flow control tests --- .../Net/Http/HttpClientHandlerTestBase.cs | 2 + .../HttpClientHandlerTest.Http2.cs | 2 - ...SocketsHttpHandlerTest.Http2FlowControl.cs | 83 +++++++++++++++++-- .../FunctionalTests/SocketsHttpHandlerTest.cs | 4 +- 4 files changed, 78 insertions(+), 13 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs index 26a084b969f200..7d56c4ce48838b 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs @@ -50,6 +50,8 @@ protected static HttpClient CreateHttpClient(HttpMessageHandler handler, string #endif }; + public const int DefaultInitialWindowSize = 65535; + public static readonly bool[] BoolValues = new[] { true, false }; // For use by remote server tests diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs index e007343b3419ea..602922a86fe204 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs @@ -1467,8 +1467,6 @@ private static async Task ReadToEndOfStream(Http2LoopbackConnection connect return bytesReceived; } - const int DefaultInitialWindowSize = 65535; - [ConditionalFact(nameof(SupportsAlpn))] public async Task Http2_FlowControl_HighBandwidthDelayProduct_ClientStreamReceiveWindowWindowScalesUp() { diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs index 04e3c5d6bed474..3773df7e27c3da 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs @@ -38,8 +38,6 @@ public sealed class SocketsHttpHandler_Http2FlowControl_Test : HttpClientHandler { public static readonly bool IsSupported = PlatformDetection.SupportsAlpn && PlatformDetection.IsNotBrowser; - const int DefaultInitialWindowSize = 65535; - protected override Version UseVersion => HttpVersion20.Value; public SocketsHttpHandler_Http2FlowControl_Test(ITestOutputHelper output) : base(output) @@ -47,35 +45,102 @@ public SocketsHttpHandler_Http2FlowControl_Test(ITestOutputHelper output) : base } [Fact] - public async Task Http2_FlowControl_HighBandwidthDelayProduct_ClientStreamReceiveWindowWindowScalesUp() + public async Task InitialHttp2StreamWindowSize_SentInSettingsFrame() + { + const int WindowSize = 123456; + using Http2LoopbackServer server = Http2LoopbackServer.CreateServer(); + using var handler = CreateHttpClientHandler(); + GetUnderlyingSocketsHttpHandler(handler).InitialHttp2StreamWindowSize = WindowSize; + using HttpClient client = CreateHttpClient(handler); + + Task clientTask = client.GetAsync(server.Address); + Http2LoopbackConnection connection = await server.AcceptConnectionAsync().ConfigureAwait(false); + SettingsFrame clientSettingsFrame = await connection.ReadSettingsAsync().ConfigureAwait(false); + SettingsEntry entry = clientSettingsFrame.Entries.First(e => e.SettingId == SettingId.InitialWindowSize); + + Assert.Equal(WindowSize, (int)entry.Value); + } + + [Fact] + public void DisableDynamicWindowScaling_HighBandwidthDelayProduct_WindowRemainsConstant() + { + static async Task RunTest() + { + AppContext.SetSwitch("System.Net.SocketsHttpHandler.Http2FlowControl.DisableDynamic2WindowSizing", true); + + int maxCredit = await TestClientWindowScalingAsync( + TimeSpan.FromMilliseconds(30), + TimeSpan.Zero, + 2 * 1024 * 1024, + null); + + Assert.Equal(DefaultInitialWindowSize, maxCredit); + } + + RemoteExecutor.Invoke(RunTest).Dispose(); + } + + [Fact] + public void MaxHttp2StreamWindowSize_HighBandwidthDelayProduct_WindowStopsAtMaxValue() + { + const int MaxWindow = 524288; + + static async Task RunTest() + { + int maxCredit = await TestClientWindowScalingAsync( + TimeSpan.FromMilliseconds(30), + TimeSpan.Zero, + 2 * 1024 * 1024, + null); + + Assert.True(maxCredit <= MaxWindow); + } + + RemoteInvokeOptions options = new RemoteInvokeOptions(); + options.StartInfo.EnvironmentVariables["DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_FLOWCONTROL_MAXSTREAMWINDOWSIZE"] = MaxWindow.ToString(); + + RemoteExecutor.Invoke(RunTest, options).Dispose(); + } + + [Fact] + public async Task HighBandwidthDelayProduct_ClientStreamReceiveWindowWindowScalesUp() { int maxCredit = await TestClientWindowScalingAsync( TimeSpan.FromMilliseconds(30), TimeSpan.Zero, - 2 * 1024 * 1024); + 2 * 1024 * 1024, + _output); // Expect the client receive window to grow over 1MB: Assert.True(maxCredit > 1024 * 1024); } [Fact] - public async Task Http2_FlowControl_LowBandwidthDelayProduct_ClientStreamReceiveWindowStopsScaling() + public async Task LowBandwidthDelayProduct_ClientStreamReceiveWindowStopsScaling() { int maxCredit = await TestClientWindowScalingAsync( TimeSpan.Zero, TimeSpan.FromMilliseconds(15), - 2 * 1024 * 1024); + 2 * 1024 * 1024, + _output); // Expect the client receive window to stay below 1MB: Assert.True(maxCredit < 1024 * 1024); } - private async Task TestClientWindowScalingAsync(TimeSpan networkDelay, TimeSpan slowBandwidthSimDelay, int bytesToDownload) + private static async Task TestClientWindowScalingAsync( + TimeSpan networkDelay, + TimeSpan slowBandwidthSimDelay, + int bytesToDownload, + ITestOutputHelper output) { TimeSpan timeout = TimeSpan.FromSeconds(30); + HttpClientHandler handler = CreateHttpClientHandler(HttpVersion20.Value); + using Http2LoopbackServer server = Http2LoopbackServer.CreateServer(); - using HttpClient client = CreateHttpClient(); + using HttpClient client = new HttpClient(handler, true); + client.DefaultRequestVersion = HttpVersion20.Value; Task clientTask = client.GetAsync(server.Address); Http2LoopbackConnection connection = await server.AcceptConnectionAsync().ConfigureAwait(false); @@ -153,7 +218,7 @@ async Task ProcessIncomingFramesAsync() credit += windowUpdateFrame.UpdateSize; maxCredit = Math.Max(credit, maxCredit); // Detect if client grows the window - _output.WriteLine("MaxCredit: " + maxCredit); + output?.WriteLine("MaxCredit: " + maxCredit); creditReceivedSemaphore.Release(); } else if (frame is not null) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index 2624f637e4af5b..d827167d00cba0 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -1934,7 +1934,7 @@ public void PlaintextStreamFilter_GetSet_Roundtrips() } [Theory] - [InlineData(65535)] + [InlineData(HttpClientHandlerTestBase.DefaultInitialWindowSize)] [InlineData(1048576)] public void InitialHttp2StreamWindowSize_Roundtrips(int value) { @@ -1993,7 +1993,7 @@ await Assert.ThrowsAnyAsync(() => Assert.True(handler.UseProxy); Assert.Null(handler.ConnectCallback); Assert.Null(handler.PlaintextStreamFilter); - Assert.Equal(65535, handler.InitialHttp2StreamWindowSize); + Assert.Equal(HttpClientHandlerTestBase.DefaultInitialWindowSize, handler.InitialHttp2StreamWindowSize); Assert.Throws(expectedExceptionType, () => handler.AllowAutoRedirect = false); Assert.Throws(expectedExceptionType, () => handler.AutomaticDecompression = DecompressionMethods.GZip); From a3f0dde9b2b5c78cef756d356110c0e560444259 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 24 Jun 2021 20:37:11 +0200 Subject: [PATCH 095/101] refactor --- .../SocketsHttpHandler/Http2Connection.cs | 8 +- .../Http/SocketsHttpHandler/Http2Stream.cs | 4 +- .../Http2StreamWindowManager.cs | 114 ++++++++++-------- ...SocketsHttpHandlerTest.Http2FlowControl.cs | 44 +++++-- .../FunctionalTests/_LargeFileBenchmark.cs | 2 + 5 files changed, 108 insertions(+), 64 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 5a70d36f0dc6bd..3c8e6d0a018116 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -133,10 +133,8 @@ public Http2Connection(HttpConnectionPool pool, Stream stream) _connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialWindowSize); _concurrentStreams = new CreditManager(this, nameof(_concurrentStreams), InitialMaxConcurrentStreams); - InitialClientStreamWindowSize = pool.Settings._initialHttp2StreamWindowSize; - _rttEstimator = pool.Settings._disableDynamicHttp2WindowSizing ? - null : - new RttEstimator(this); + + _rttEstimator = pool.Settings._disableDynamicHttp2WindowSizing ? null : new RttEstimator(this); _writeChannel = Channel.CreateUnbounded(s_channelOptions); @@ -194,7 +192,7 @@ public async ValueTask SetupAsync() _outgoingBuffer.Commit(4); BinaryPrimitives.WriteUInt16BigEndian(_outgoingBuffer.AvailableSpan, (ushort)SettingId.InitialWindowSize); _outgoingBuffer.Commit(2); - BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, (uint)InitialClientStreamWindowSize); + BinaryPrimitives.WriteUInt32BigEndian(_outgoingBuffer.AvailableSpan, (uint)_pool.Settings._initialHttp2StreamWindowSize); _outgoingBuffer.Commit(4); // The connection-level window size can not be initialized by SETTINGS frames: diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index baedfa840688e8..9371d6ae67a2a8 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -97,9 +97,7 @@ public Http2Stream(HttpRequestMessage request, Http2Connection connection) _responseBuffer = new MultiArrayBuffer(InitialStreamBufferSize); - _windowManager = connection._pool.Settings._disableDynamicHttp2WindowSizing ? - new Http2StreamWindowManager(connection, this) : - new DynamicHttp2StreamWindowManager(connection, this); + _windowManager = new Http2StreamWindowManager(connection, this); Trace($"_windowManager: {_windowManager.GetType().Name}"); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 8399fade9da05d..9cccab40afaee3 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -11,24 +11,34 @@ namespace System.Net.Http { internal sealed partial class Http2Connection { - private int InitialClientStreamWindowSize { get; } = 65535; - private class Http2StreamWindowManager { public const int StreamWindowUpdateRatio = 8; - protected int _delivered; - protected int _streamWindowSize; + private static readonly double StopWatchToTimesSpan = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; - protected readonly Http2Connection _connection; - protected readonly Http2Stream _stream; + private readonly Http2Connection _connection; + private readonly Http2Stream _stream; + + private readonly double _windowScaleThresholdMultiplier; + private readonly int _maxStreamWindowSize; + private bool _windowScalingEnabled; + + private int _deliveredBytes; + private int _streamWindowSize; + private long _lastWindowUpdate; public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) { _connection = connection; - _stream = stream; - _streamWindowSize = connection.InitialClientStreamWindowSize; - _stream.TraceFlowControl($"InitialClientStreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); + HttpConnectionSettings settings = connection._pool.Settings; + _streamWindowSize = settings._initialHttp2StreamWindowSize; + _windowScalingEnabled = !settings._disableDynamicHttp2WindowSizing; + _maxStreamWindowSize = settings._maxHttp2StreamWindowSize; + _windowScaleThresholdMultiplier = settings._http2StreamWindowScaleThresholdMultiplier; + _lastWindowUpdate = Stopwatch.GetTimestamp(); + + _stream.TraceFlowControl($"InitialClientStreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}, WindowScaleThresholdMultiplier: {_windowScaleThresholdMultiplier}"); } internal int StreamWindowSize => _streamWindowSize; @@ -38,7 +48,7 @@ public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) public virtual void AdjustWindow(int bytesConsumed) { Debug.Assert(bytesConsumed > 0); - Debug.Assert(_delivered < StreamWindowThreshold); + Debug.Assert(_deliveredBytes < StreamWindowThreshold); if (!_stream.ExpectResponseData) { @@ -47,45 +57,43 @@ public virtual void AdjustWindow(int bytesConsumed) return; } - _delivered += bytesConsumed; - if (_delivered < StreamWindowThreshold) + if (_windowScalingEnabled) + { + AdjustWindowDynamic(bytesConsumed); + } + else + { + AjdustWindowStatic(bytesConsumed); + } + } + + private void AjdustWindowStatic(int bytesConsumed) + { + _deliveredBytes += bytesConsumed; + if (_deliveredBytes < StreamWindowThreshold) { return; } - int windowUpdateSize = _delivered; - _delivered = 0; + int windowUpdateIncrement = _deliveredBytes; + _deliveredBytes = 0; - Task sendWindowUpdateTask = _connection.SendWindowUpdateAsync(_stream.StreamId, windowUpdateSize); + _stream.TraceFlowControl($"Sending WINDOW_UPDATE of increment {windowUpdateIncrement}"); + Task sendWindowUpdateTask = _connection.SendWindowUpdateAsync(_stream.StreamId, windowUpdateIncrement); _connection.LogExceptions(sendWindowUpdateTask); } - } - - private class DynamicHttp2StreamWindowManager : Http2StreamWindowManager - { - private static readonly double StopWatchToTimesSpan = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; - - private long _lastWindowUpdate; - private double _windowScaleThresholdMultiplier; - - public DynamicHttp2StreamWindowManager(Http2Connection connection, Http2Stream stream) - : base(connection, stream) + private void AdjustWindowDynamic(int bytesConsumed) { - _windowScaleThresholdMultiplier = connection._pool.Settings._http2StreamWindowScaleThresholdMultiplier; - _stream.TraceFlowControl($" http2StreamWindowScaleThresholdMultiplier: {_windowScaleThresholdMultiplier}"); - _lastWindowUpdate = Stopwatch.GetTimestamp(); - } + _deliveredBytes += bytesConsumed; - public override void AdjustWindow(int bytesConsumed) - { - _delivered += bytesConsumed; - if (_delivered < StreamWindowThreshold) + _stream.TraceFlowControl($"Received {bytesConsumed}, _deliveredBytes: {_deliveredBytes}"); + if (_deliveredBytes < StreamWindowThreshold) { return; } - int windowSizeIncrement = _delivered; + int windowUpdateIncrement = _deliveredBytes; long currentTime = Stopwatch.GetTimestamp(); if (_connection._rttEstimator!.MinRtt > TimeSpan.Zero) @@ -93,19 +101,29 @@ public override void AdjustWindow(int bytesConsumed) TimeSpan rtt = _connection._rttEstimator.MinRtt; TimeSpan dt = StopwatchTicksToTimeSpan(currentTime - _lastWindowUpdate); - if (_delivered * rtt.Ticks > _streamWindowSize * dt.Ticks * _windowScaleThresholdMultiplier) + if (_deliveredBytes * rtt.Ticks > _streamWindowSize * dt.Ticks * _windowScaleThresholdMultiplier) { - windowSizeIncrement += _streamWindowSize; - _streamWindowSize *= 2; + int extendedWindowSize = Math.Min(_maxStreamWindowSize, _streamWindowSize * 2); + windowUpdateIncrement += extendedWindowSize - _streamWindowSize; + _streamWindowSize = extendedWindowSize; _stream.TraceFlowControl($"Updated Stream Window. StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}"); + + Debug.Assert(_streamWindowSize <= _maxStreamWindowSize); + if (_streamWindowSize == _maxStreamWindowSize) + { + _stream.TraceFlowControl($"StreamWindowSize reached the configured maximum of {_maxStreamWindowSize}."); + _windowScalingEnabled = false; + } } } - Task sendWindowUpdateTask = _connection.SendWindowUpdateAsync(_stream.StreamId, windowSizeIncrement); + _deliveredBytes = 0; + + _stream.TraceFlowControl($"Sending WINDOW_UPDATE of increment {windowUpdateIncrement}"); + Task sendWindowUpdateTask = _connection.SendWindowUpdateAsync(_stream.StreamId, windowUpdateIncrement); _connection.LogExceptions(sendWindowUpdateTask); - _delivered = 0; _lastWindowUpdate = currentTime; } @@ -118,7 +136,7 @@ private static TimeSpan StopwatchTicksToTimeSpan(long stopwatchTicks) private class RttEstimator { - private enum Status + private enum State { Init, Waiting, @@ -131,7 +149,7 @@ private enum Status private Http2Connection _connection; - private Status _status; + private State _state; private long _pingSentTimestamp; private long _pingCounter = -1; private int _initialBurst = 4; @@ -153,12 +171,12 @@ internal void OnInitialSettingsAckReceived() { _connection.TraceFlowControl("Initial SETTINGS ACK received"); RefreshRtt(); - _status = Status.Waiting; + _state = State.Waiting; } internal void OnDataOrHeadersReceived() { - if (_status == Status.Waiting) + if (_state == State.Waiting) { long now = Stopwatch.GetTimestamp(); bool initial = Interlocked.Decrement(ref _initialBurst) >= 0; @@ -171,7 +189,7 @@ internal void OnDataOrHeadersReceived() _connection.TraceFlowControl("Sending PING in response to DATA."); _connection.LogExceptions(_connection.SendPingAsync(payload, isAck: false)); _pingSentTimestamp = now; - _status = Status.PingSent; + _state = State.PingSent; } } } @@ -179,17 +197,17 @@ internal void OnDataOrHeadersReceived() internal void OnPingAckReceived(long payload) { Debug.Assert(payload < 0); - if (_status != Status.PingSent) return; + if (_state != State.PingSent) return; if (Interlocked.Read(ref _pingCounter) != payload) ThrowProtocolError(); RefreshRtt(); - _status = Status.Waiting; + _state = State.Waiting; } internal void OnGoAwayReceived() { - _status = Status.Terminating; + _state = State.Terminating; } private void RefreshRtt() diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs index 3773df7e27c3da..7b392b859c2784 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs @@ -78,12 +78,31 @@ static async Task RunTest() } RemoteExecutor.Invoke(RunTest).Dispose(); + //RunTest().GetAwaiter().GetResult(); + } + + [Fact] + public async Task Hobluj() + { + using var listener = new LogHttpEventListener(_output); + listener.Enabled = true; + listener.Filter = m => m.Contains("[FlowControl]") || m.Contains("SERVER"); + + int maxCredit = await TestClientWindowScalingAsync( + TimeSpan.FromMilliseconds(30), + TimeSpan.Zero, + 8 * 1024 * 1024, + _output, + listener); + + _output.WriteLine("maxCredit: " + maxCredit); } [Fact] public void MaxHttp2StreamWindowSize_HighBandwidthDelayProduct_WindowStopsAtMaxValue() { - const int MaxWindow = 524288; + //const int MaxWindow = 654321; + const int MaxWindow = 123456; static async Task RunTest() { @@ -93,7 +112,8 @@ static async Task RunTest() 2 * 1024 * 1024, null); - Assert.True(maxCredit <= MaxWindow); + //Assert.True(maxCredit <= MaxWindow); + Assert.Equal(MaxWindow, maxCredit); } RemoteInvokeOptions options = new RemoteInvokeOptions(); @@ -132,7 +152,8 @@ private static async Task TestClientWindowScalingAsync( TimeSpan networkDelay, TimeSpan slowBandwidthSimDelay, int bytesToDownload, - ITestOutputHelper output) + ITestOutputHelper output, + LogHttpEventListener listener = null) { TimeSpan timeout = TimeSpan.FromSeconds(30); @@ -182,10 +203,10 @@ private static async Task TestClientWindowScalingAsync( bool endStream = nextRemainingBytes == 0; await writeSemaphore.WaitAsync(); + Interlocked.Add(ref credit, -bytesToSend); await connection.SendResponseDataAsync(streamId, responseData, endStream); writeSemaphore.Release(); - - credit -= bytesToSend; + await Log($"Sent {bytesToSend}, credit reduced: {credit}"); remainingBytes = nextRemainingBytes; } @@ -196,6 +217,12 @@ private static async Task TestClientWindowScalingAsync( return maxCredit; + async Task Log(string msg) + { + if (listener != null) await listener.WriteAsync(" ## SERVER " + msg); + else output?.WriteLine(msg); + } + async Task ProcessIncomingFramesAsync() { while (remainingBytes > 0) @@ -216,10 +243,11 @@ async Task ProcessIncomingFramesAsync() // Ignore connection window: if (windowUpdateFrame.StreamId != streamId) continue; - credit += windowUpdateFrame.UpdateSize; - maxCredit = Math.Max(credit, maxCredit); // Detect if client grows the window - output?.WriteLine("MaxCredit: " + maxCredit); + int currentCredit = Interlocked.Add(ref credit, windowUpdateFrame.UpdateSize); + maxCredit = Math.Max(currentCredit, maxCredit); // Detect if client grows the window creditReceivedSemaphore.Release(); + + await Log($">> UpdateSize:{windowUpdateFrame.UpdateSize} currentCredit:{currentCredit} MaxCredit: {maxCredit}"); } else if (frame is not null) { diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs index 1f25d420ff8b71..e0afbf5e4d7426 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/_LargeFileBenchmark.cs @@ -420,6 +420,8 @@ private async Task ProcessMessagesAsync() } } + public ValueTask WriteAsync(string message) => _messagesChannel.Writer.WriteAsync(message); + protected override async void OnEventWritten(EventWrittenEventArgs eventData) { if (!Enabled) return; From 03d70ca7fa04d2e4f35a7890cf9cac62a6e16e61 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 24 Jun 2021 20:40:04 +0200 Subject: [PATCH 096/101] remove sandboxing --- .../Http2StreamWindowManager.cs | 3 -- ...SocketsHttpHandlerTest.Http2FlowControl.cs | 34 +++---------------- 2 files changed, 4 insertions(+), 33 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 9cccab40afaee3..af8e3cde17238a 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -78,7 +78,6 @@ private void AjdustWindowStatic(int bytesConsumed) int windowUpdateIncrement = _deliveredBytes; _deliveredBytes = 0; - _stream.TraceFlowControl($"Sending WINDOW_UPDATE of increment {windowUpdateIncrement}"); Task sendWindowUpdateTask = _connection.SendWindowUpdateAsync(_stream.StreamId, windowUpdateIncrement); _connection.LogExceptions(sendWindowUpdateTask); } @@ -87,7 +86,6 @@ private void AdjustWindowDynamic(int bytesConsumed) { _deliveredBytes += bytesConsumed; - _stream.TraceFlowControl($"Received {bytesConsumed}, _deliveredBytes: {_deliveredBytes}"); if (_deliveredBytes < StreamWindowThreshold) { return; @@ -120,7 +118,6 @@ private void AdjustWindowDynamic(int bytesConsumed) _deliveredBytes = 0; - _stream.TraceFlowControl($"Sending WINDOW_UPDATE of increment {windowUpdateIncrement}"); Task sendWindowUpdateTask = _connection.SendWindowUpdateAsync(_stream.StreamId, windowUpdateIncrement); _connection.LogExceptions(sendWindowUpdateTask); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs index 7b392b859c2784..9e257fbf77b1bd 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs @@ -78,24 +78,6 @@ static async Task RunTest() } RemoteExecutor.Invoke(RunTest).Dispose(); - //RunTest().GetAwaiter().GetResult(); - } - - [Fact] - public async Task Hobluj() - { - using var listener = new LogHttpEventListener(_output); - listener.Enabled = true; - listener.Filter = m => m.Contains("[FlowControl]") || m.Contains("SERVER"); - - int maxCredit = await TestClientWindowScalingAsync( - TimeSpan.FromMilliseconds(30), - TimeSpan.Zero, - 8 * 1024 * 1024, - _output, - listener); - - _output.WriteLine("maxCredit: " + maxCredit); } [Fact] @@ -112,8 +94,7 @@ static async Task RunTest() 2 * 1024 * 1024, null); - //Assert.True(maxCredit <= MaxWindow); - Assert.Equal(MaxWindow, maxCredit); + Assert.True(maxCredit <= MaxWindow); } RemoteInvokeOptions options = new RemoteInvokeOptions(); @@ -152,8 +133,7 @@ private static async Task TestClientWindowScalingAsync( TimeSpan networkDelay, TimeSpan slowBandwidthSimDelay, int bytesToDownload, - ITestOutputHelper output, - LogHttpEventListener listener = null) + ITestOutputHelper output) { TimeSpan timeout = TimeSpan.FromSeconds(30); @@ -206,7 +186,7 @@ private static async Task TestClientWindowScalingAsync( Interlocked.Add(ref credit, -bytesToSend); await connection.SendResponseDataAsync(streamId, responseData, endStream); writeSemaphore.Release(); - await Log($"Sent {bytesToSend}, credit reduced: {credit}"); + output?.WriteLine($"Sent {bytesToSend}, credit reduced to: {credit}"); remainingBytes = nextRemainingBytes; } @@ -217,12 +197,6 @@ private static async Task TestClientWindowScalingAsync( return maxCredit; - async Task Log(string msg) - { - if (listener != null) await listener.WriteAsync(" ## SERVER " + msg); - else output?.WriteLine(msg); - } - async Task ProcessIncomingFramesAsync() { while (remainingBytes > 0) @@ -247,7 +221,7 @@ async Task ProcessIncomingFramesAsync() maxCredit = Math.Max(currentCredit, maxCredit); // Detect if client grows the window creditReceivedSemaphore.Release(); - await Log($">> UpdateSize:{windowUpdateFrame.UpdateSize} currentCredit:{currentCredit} MaxCredit: {maxCredit}"); + output?.WriteLine($"UpdateSize:{windowUpdateFrame.UpdateSize} currentCredit:{currentCredit} MaxCredit: {maxCredit}"); } else if (frame is not null) { From 0f9f0656ce1a5e13b80c70af37e02bc502f646cf Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 24 Jun 2021 21:06:38 +0200 Subject: [PATCH 097/101] StreamWindowScaleThresholdMultiplier tests --- ...SocketsHttpHandlerTest.Http2FlowControl.cs | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs index 9e257fbf77b1bd..0d3566df12d908 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.Http2FlowControl.cs @@ -81,10 +81,9 @@ static async Task RunTest() } [Fact] - public void MaxHttp2StreamWindowSize_HighBandwidthDelayProduct_WindowStopsAtMaxValue() + public void MaxStreamWindowSize_HighBandwidthDelayProduct_WindowStopsAtMaxValue() { - //const int MaxWindow = 654321; - const int MaxWindow = 123456; + const int MaxWindow = 654321; static async Task RunTest() { @@ -129,6 +128,46 @@ public async Task LowBandwidthDelayProduct_ClientStreamReceiveWindowStopsScaling Assert.True(maxCredit < 1024 * 1024); } + [Fact] + public void StreamWindowScaleThresholdMultiplier_HighValue_WindowScalesSlower() + { + static async Task RunTest() + { + int maxCredit = await TestClientWindowScalingAsync( + TimeSpan.FromMilliseconds(30), + TimeSpan.Zero, + 2 * 1024 * 1024, + null); + + Assert.True(maxCredit <= 128 * 1024); + } + + RemoteInvokeOptions options = new RemoteInvokeOptions(); + options.StartInfo.EnvironmentVariables["DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_FLOWCONTROL_STREAMWINDOWSCALETHRESHOLDMULTIPLIER"] = "1000"; // Extreme value + + RemoteExecutor.Invoke(RunTest, options).Dispose(); + } + + [Fact] + public void StreamWindowScaleThresholdMultiplier_LowValue_WindowScalesFaster() + { + static async Task RunTest() + { + int maxCredit = await TestClientWindowScalingAsync( + TimeSpan.Zero, + TimeSpan.FromMilliseconds(15), // Low bandwidth * delay product + 2 * 1024 * 1024, + null); + + Assert.True(maxCredit >= 256 * 1024); + } + + RemoteInvokeOptions options = new RemoteInvokeOptions(); + options.StartInfo.EnvironmentVariables["DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_FLOWCONTROL_STREAMWINDOWSCALETHRESHOLDMULTIPLIER"] = "0.00001"; // Extreme value + + RemoteExecutor.Invoke(RunTest, options).Dispose(); + } + private static async Task TestClientWindowScalingAsync( TimeSpan networkDelay, TimeSpan slowBandwidthSimDelay, From 0485d6344cdf3079da6ad82d087b4d4fb2e00fd3 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 24 Jun 2021 21:11:47 +0200 Subject: [PATCH 098/101] clean-up tracing --- .../Net/Http/SocketsHttpHandler/Http2Connection.cs | 3 --- .../System/Net/Http/SocketsHttpHandler/Http2Stream.cs | 3 --- .../SocketsHttpHandler/Http2StreamWindowManager.cs | 11 ++++------- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 3c8e6d0a018116..7ed6b6edf23ea6 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -2064,9 +2064,6 @@ internal void Trace(int streamId, string message, [CallerMemberName] string? mem memberName, // method name message); // message - public void TraceFlowControl(string message, [CallerMemberName] string? memberName = null) => - Trace("[FlowControl] " + message, memberName); - [DoesNotReturn] private static void ThrowRetry(string message, Exception innerException) => throw new HttpRequestException(message, innerException, allowRetry: RequestRetryType.RetryOnConnectionFailure); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index 9371d6ae67a2a8..0c5d8e9e275336 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -1351,9 +1351,6 @@ private ValueTask WaitForDataAsync(CancellationToken cancellationToken) public void Trace(string message, [CallerMemberName] string? memberName = null) => _connection.Trace(StreamId, message, memberName); - public void TraceFlowControl(string message, [CallerMemberName] string? memberName = null) => - Trace("[FlowControl] " + message, memberName); - private enum ResponseProtocolState : byte { ExpectingStatus, diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index af8e3cde17238a..385a1887a69d0f 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -38,7 +38,7 @@ public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) _windowScaleThresholdMultiplier = settings._http2StreamWindowScaleThresholdMultiplier; _lastWindowUpdate = Stopwatch.GetTimestamp(); - _stream.TraceFlowControl($"InitialClientStreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}, WindowScaleThresholdMultiplier: {_windowScaleThresholdMultiplier}"); + if (NetEventSource.Log.IsEnabled()) _stream.Trace($"[FlowControl] InitialClientStreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}, WindowScaleThresholdMultiplier: {_windowScaleThresholdMultiplier}"); } internal int StreamWindowSize => _streamWindowSize; @@ -105,12 +105,12 @@ private void AdjustWindowDynamic(int bytesConsumed) windowUpdateIncrement += extendedWindowSize - _streamWindowSize; _streamWindowSize = extendedWindowSize; - _stream.TraceFlowControl($"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) { - _stream.TraceFlowControl($"StreamWindowSize reached the configured maximum of {_maxStreamWindowSize}."); + if (NetEventSource.Log.IsEnabled()) _stream.Trace($"[FlowControl] StreamWindowSize reached the configured maximum of {_maxStreamWindowSize}."); _windowScalingEnabled = false; } } @@ -160,13 +160,11 @@ public RttEstimator(Http2Connection connection) internal void OnInitialSettingsSent() { - _connection.TraceFlowControl("Initial SETTINGS sent"); _pingSentTimestamp = Stopwatch.GetTimestamp(); } internal void OnInitialSettingsAckReceived() { - _connection.TraceFlowControl("Initial SETTINGS ACK received"); RefreshRtt(); _state = State.Waiting; } @@ -183,7 +181,6 @@ internal void OnDataOrHeadersReceived() // Send a PING long payload = Interlocked.Decrement(ref _pingCounter); - _connection.TraceFlowControl("Sending PING in response to DATA."); _connection.LogExceptions(_connection.SendPingAsync(payload, isAck: false)); _pingSentTimestamp = now; _state = State.PingSent; @@ -213,7 +210,7 @@ private void RefreshRtt() TimeSpan prevRtt = MinRtt == TimeSpan.Zero ? TimeSpan.MaxValue : MinRtt; TimeSpan currentRtt = TimeSpan.FromSeconds(elapsedTicks / (double)Stopwatch.Frequency); MinRtt = new TimeSpan(Math.Min(prevRtt.Ticks, currentRtt.Ticks)); - _connection.TraceFlowControl($"Updated MinRtt: {MinRtt.TotalMilliseconds} ms || prevRtt:{prevRtt.TotalMilliseconds} ms, currentRtt:{currentRtt.TotalMilliseconds} ms)"); + if (NetEventSource.Log.IsEnabled()) _connection.Trace($"[FlowControl] Updated MinRtt: {MinRtt.TotalMilliseconds} ms"); } } } From bd1b1777744ab0d5fc140fa93224332c33401e6c Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 24 Jun 2021 21:46:17 +0200 Subject: [PATCH 099/101] make Http2StreamWindowManager struct --- .../Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 385a1887a69d0f..64e828cdc78fca 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -11,7 +11,7 @@ namespace System.Net.Http { internal sealed partial class Http2Connection { - private class Http2StreamWindowManager + private struct Http2StreamWindowManager { public const int StreamWindowUpdateRatio = 8; private static readonly double StopWatchToTimesSpan = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; @@ -37,6 +37,7 @@ public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) _maxStreamWindowSize = settings._maxHttp2StreamWindowSize; _windowScaleThresholdMultiplier = settings._http2StreamWindowScaleThresholdMultiplier; _lastWindowUpdate = Stopwatch.GetTimestamp(); + _deliveredBytes = 0; if (NetEventSource.Log.IsEnabled()) _stream.Trace($"[FlowControl] InitialClientStreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}, WindowScaleThresholdMultiplier: {_windowScaleThresholdMultiplier}"); } @@ -45,7 +46,7 @@ public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream) internal int StreamWindowThreshold => _streamWindowSize / StreamWindowUpdateRatio; - public virtual void AdjustWindow(int bytesConsumed) + public void AdjustWindow(int bytesConsumed) { Debug.Assert(bytesConsumed > 0); Debug.Assert(_deliveredBytes < StreamWindowThreshold); From 201641e8fbfc24830612a47ac3fabcbb9c8a7fc5 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 24 Jun 2021 21:58:02 +0200 Subject: [PATCH 100/101] make RttEstimator non-nullable --- .../SocketsHttpHandler/Http2Connection.cs | 16 +++++----- .../Http2StreamWindowManager.cs | 32 +++++++++++-------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 7ed6b6edf23ea6..8a4b0bafd04f4e 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -38,7 +38,7 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable private readonly CreditManager _connectionWindow; private readonly CreditManager _concurrentStreams; - private readonly RttEstimator? _rttEstimator; + private readonly RttEstimator _rttEstimator; private int _nextStream; private bool _expectingSettingsAck; @@ -134,7 +134,7 @@ public Http2Connection(HttpConnectionPool pool, Stream stream) _connectionWindow = new CreditManager(this, nameof(_connectionWindow), DefaultInitialWindowSize); _concurrentStreams = new CreditManager(this, nameof(_concurrentStreams), InitialMaxConcurrentStreams); - _rttEstimator = pool.Settings._disableDynamicHttp2WindowSizing ? null : new RttEstimator(this); + _rttEstimator = new RttEstimator(this); _writeChannel = Channel.CreateUnbounded(s_channelOptions); @@ -206,7 +206,7 @@ public async ValueTask SetupAsync() _outgoingBuffer.Commit(4); await _stream.WriteAsync(_outgoingBuffer.ActiveMemory).ConfigureAwait(false); - _rttEstimator?.OnInitialSettingsSent(); + _rttEstimator.OnInitialSettingsSent(); _outgoingBuffer.Discard(_outgoingBuffer.ActiveLength); _expectingSettingsAck = true; @@ -453,7 +453,7 @@ private async ValueTask ProcessHeadersFrame(FrameHeader frameHeader) if (http2Stream != null) { http2Stream.OnHeadersStart(); - _rttEstimator?.OnDataOrHeadersReceived(); + _rttEstimator.OnDataOrHeadersReceived(); headersHandler = http2Stream; } else @@ -589,7 +589,7 @@ private void ProcessDataFrame(FrameHeader frameHeader) if (!endStream && frameData.Length > 0) { - _rttEstimator?.OnDataOrHeadersReceived(); + _rttEstimator.OnDataOrHeadersReceived(); } } @@ -625,7 +625,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(); } else { @@ -1689,7 +1689,7 @@ private void StartTerminatingConnection(int lastValidStream, Exception abortExce _pool.InvalidateHttp2Connection(this); // There is no point sending more PING frames for RTT estimation: - _rttEstimator?.OnGoAwayReceived(); + _rttEstimator.OnGoAwayReceived(); List streamsToAbort = new List(); @@ -2002,7 +2002,7 @@ private void ProcessPingAck(long payload) { if (payload < 0) // RTT ping { - _rttEstimator?.OnPingAckReceived(payload); + _rttEstimator.OnPingAckReceived(payload); return; } else // Keepalive ping diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index 64e828cdc78fca..ead789096e4a6a 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -95,7 +95,7 @@ private void AdjustWindowDynamic(int bytesConsumed) int windowUpdateIncrement = _deliveredBytes; long currentTime = Stopwatch.GetTimestamp(); - if (_connection._rttEstimator!.MinRtt > TimeSpan.Zero) + if (_connection._rttEstimator.MinRtt > TimeSpan.Zero) { TimeSpan rtt = _connection._rttEstimator.MinRtt; TimeSpan dt = StopwatchTicksToTimeSpan(currentTime - _lastWindowUpdate); @@ -136,6 +136,7 @@ private class RttEstimator { private enum State { + Disabled, Init, Waiting, PingSent, @@ -157,42 +158,44 @@ private enum State public RttEstimator(Http2Connection connection) { _connection = connection; + _state = connection._pool.Settings._disableDynamicHttp2WindowSizing ? State.Disabled : State.Init; } internal void OnInitialSettingsSent() { + if (_state == State.Disabled) return; _pingSentTimestamp = Stopwatch.GetTimestamp(); } internal void OnInitialSettingsAckReceived() { + if (_state == State.Disabled) return; RefreshRtt(); _state = State.Waiting; } internal void OnDataOrHeadersReceived() { - if (_state == State.Waiting) + if (_state != State.Waiting) return; + + long now = Stopwatch.GetTimestamp(); + bool initial = Interlocked.Decrement(ref _initialBurst) >= 0; + if (initial || now - _pingSentTimestamp > PingIntervalInTicks) { - long now = Stopwatch.GetTimestamp(); - bool initial = Interlocked.Decrement(ref _initialBurst) >= 0; - if (initial || now - _pingSentTimestamp > PingIntervalInTicks) - { - if (_initialBurst > 0) Interlocked.Decrement(ref _initialBurst); + if (_initialBurst > 0) Interlocked.Decrement(ref _initialBurst); - // Send a PING - long payload = Interlocked.Decrement(ref _pingCounter); - _connection.LogExceptions(_connection.SendPingAsync(payload, isAck: false)); - _pingSentTimestamp = now; - _state = State.PingSent; - } + // Send a PING + long payload = Interlocked.Decrement(ref _pingCounter); + _connection.LogExceptions(_connection.SendPingAsync(payload, isAck: false)); + _pingSentTimestamp = now; + _state = State.PingSent; } } internal void OnPingAckReceived(long payload) { - Debug.Assert(payload < 0); if (_state != State.PingSent) return; + Debug.Assert(payload < 0); if (Interlocked.Read(ref _pingCounter) != payload) ThrowProtocolError(); @@ -202,6 +205,7 @@ internal void OnPingAckReceived(long payload) internal void OnGoAwayReceived() { + if (_state == State.Disabled) return; _state = State.Terminating; } From 071676ad53fa074cb54d084f4fffa2ea37dc0319 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 24 Jun 2021 22:02:32 +0200 Subject: [PATCH 101/101] make RttEstimator a struct --- .../Net/Http/SocketsHttpHandler/Http2Connection.cs | 2 +- .../SocketsHttpHandler/Http2StreamWindowManager.cs | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 8a4b0bafd04f4e..da6b3be86ef1fc 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -38,7 +38,7 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable private readonly CreditManager _connectionWindow; private readonly CreditManager _concurrentStreams; - private readonly RttEstimator _rttEstimator; + private RttEstimator _rttEstimator; private int _nextStream; private bool _expectingSettingsAck; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs index ead789096e4a6a..18c6437ae3e3b0 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2StreamWindowManager.cs @@ -132,7 +132,7 @@ private static TimeSpan StopwatchTicksToTimeSpan(long stopwatchTicks) } } - private class RttEstimator + private struct RttEstimator { private enum State { @@ -150,15 +150,19 @@ private enum State private State _state; private long _pingSentTimestamp; - private long _pingCounter = -1; - private int _initialBurst = 4; + private long _pingCounter; + private int _initialBurst; - public TimeSpan MinRtt { get; private set; } + public TimeSpan MinRtt; public RttEstimator(Http2Connection connection) { _connection = connection; _state = connection._pool.Settings._disableDynamicHttp2WindowSizing ? State.Disabled : State.Init; + _pingCounter = -1; + _initialBurst = 4; + _pingSentTimestamp = default; + MinRtt = default; } internal void OnInitialSettingsSent()