From a6c1fb448be88f4fa0c188ca9c227d5845d0c736 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Thu, 19 Aug 2021 19:25:34 +0200 Subject: [PATCH 1/8] spread WS based timers over next 6 minutes to prevent heavy throttling --- .../System.Net.WebSockets.Client.sln | 41 ++-- .../tests/wasm/BrowserTimerThrottlingTest.cs | 179 ++++++++++++++++++ ...em.Net.WebSockets.Client.Wasm.Tests.csproj | 66 +++++++ ...ystem.Net.WebSockets.Client.Wasm.Tests.sln | 25 +++ src/mono/wasm/runtime/binding_support.js | 15 +- src/mono/wasm/runtime/library_mono.js | 30 ++- 6 files changed, 334 insertions(+), 22 deletions(-) create mode 100644 src/libraries/System.Net.WebSockets.Client/tests/wasm/BrowserTimerThrottlingTest.cs create mode 100644 src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.csproj create mode 100644 src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.sln diff --git a/src/libraries/System.Net.WebSockets.Client/System.Net.WebSockets.Client.sln b/src/libraries/System.Net.WebSockets.Client/System.Net.WebSockets.Client.sln index 6c64c7da37d5f..1af1a766ae197 100644 --- a/src/libraries/System.Net.WebSockets.Client/System.Net.WebSockets.Client.sln +++ b/src/libraries/System.Net.WebSockets.Client/System.Net.WebSockets.Client.sln @@ -1,4 +1,8 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31521.260 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{B615DEB1-354C-4357-987A-BBA921E5A712}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Net.WebSockets.Client", "ref\System.Net.WebSockets.Client.csproj", "{BEA5BC2C-12D1-4D01-8D2C-5029578BD066}" @@ -19,16 +23,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{A0314AC5-E49 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6F9A42A0-A04B-4CD0-B8C9-9A728274C851}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Net.WebSockets.Client.Wasm.Tests", "tests\wasm\System.Net.WebSockets.Client.Wasm.Tests.csproj", "{CA20532A-33B3-4DC0-92D2-EA6D7987D59F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Debug|Any CPU.Build.0 = Debug|Any CPU - {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Release|Any CPU.ActiveCfg = Release|Any CPU - {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Release|Any CPU.Build.0 = Release|Any CPU + {B615DEB1-354C-4357-987A-BBA921E5A712}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B615DEB1-354C-4357-987A-BBA921E5A712}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B615DEB1-354C-4357-987A-BBA921E5A712}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B615DEB1-354C-4357-987A-BBA921E5A712}.Release|Any CPU.Build.0 = Release|Any CPU {BEA5BC2C-12D1-4D01-8D2C-5029578BD066}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BEA5BC2C-12D1-4D01-8D2C-5029578BD066}.Debug|Any CPU.Build.0 = Debug|Any CPU {BEA5BC2C-12D1-4D01-8D2C-5029578BD066}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -37,34 +43,39 @@ Global {0CD4C24D-7746-46F0-8D47-A396882B5468}.Debug|Any CPU.Build.0 = Debug|Any CPU {0CD4C24D-7746-46F0-8D47-A396882B5468}.Release|Any CPU.ActiveCfg = Release|Any CPU {0CD4C24D-7746-46F0-8D47-A396882B5468}.Release|Any CPU.Build.0 = Release|Any CPU - {B615DEB1-354C-4357-987A-BBA921E5A712}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B615DEB1-354C-4357-987A-BBA921E5A712}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B615DEB1-354C-4357-987A-BBA921E5A712}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B615DEB1-354C-4357-987A-BBA921E5A712}.Release|Any CPU.Build.0 = Release|Any CPU + {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Release|Any CPU.Build.0 = Release|Any CPU {8CD4D190-F656-4970-9AE9-4A9F8B30A2F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8CD4D190-F656-4970-9AE9-4A9F8B30A2F8}.Debug|Any CPU.Build.0 = Debug|Any CPU {8CD4D190-F656-4970-9AE9-4A9F8B30A2F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {8CD4D190-F656-4970-9AE9-4A9F8B30A2F8}.Release|Any CPU.Build.0 = Release|Any CPU - {59A23CAB-D098-495F-A467-74C7553FF5BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {59A23CAB-D098-495F-A467-74C7553FF5BB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {59A23CAB-D098-495F-A467-74C7553FF5BB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {59A23CAB-D098-495F-A467-74C7553FF5BB}.Release|Any CPU.Build.0 = Release|Any CPU {6B9721B2-00D0-41EC-96B9-6428CF1830CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6B9721B2-00D0-41EC-96B9-6428CF1830CF}.Debug|Any CPU.Build.0 = Debug|Any CPU {6B9721B2-00D0-41EC-96B9-6428CF1830CF}.Release|Any CPU.ActiveCfg = Release|Any CPU {6B9721B2-00D0-41EC-96B9-6428CF1830CF}.Release|Any CPU.Build.0 = Release|Any CPU + {59A23CAB-D098-495F-A467-74C7553FF5BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59A23CAB-D098-495F-A467-74C7553FF5BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59A23CAB-D098-495F-A467-74C7553FF5BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59A23CAB-D098-495F-A467-74C7553FF5BB}.Release|Any CPU.Build.0 = Release|Any CPU + {CA20532A-33B3-4DC0-92D2-EA6D7987D59F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA20532A-33B3-4DC0-92D2-EA6D7987D59F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA20532A-33B3-4DC0-92D2-EA6D7987D59F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA20532A-33B3-4DC0-92D2-EA6D7987D59F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {90E8DA45-66F3-491E-B408-82AB85EEAB76} = {BEE2F256-0489-4809-AB20-27ADB2D0E10C} {B615DEB1-354C-4357-987A-BBA921E5A712} = {BEE2F256-0489-4809-AB20-27ADB2D0E10C} {BEA5BC2C-12D1-4D01-8D2C-5029578BD066} = {A0314AC5-E490-4A6A-B946-8B9A21A2FA05} - {6B9721B2-00D0-41EC-96B9-6428CF1830CF} = {A0314AC5-E490-4A6A-B946-8B9A21A2FA05} {0CD4C24D-7746-46F0-8D47-A396882B5468} = {6F9A42A0-A04B-4CD0-B8C9-9A728274C851} + {90E8DA45-66F3-491E-B408-82AB85EEAB76} = {BEE2F256-0489-4809-AB20-27ADB2D0E10C} {8CD4D190-F656-4970-9AE9-4A9F8B30A2F8} = {6F9A42A0-A04B-4CD0-B8C9-9A728274C851} + {6B9721B2-00D0-41EC-96B9-6428CF1830CF} = {A0314AC5-E490-4A6A-B946-8B9A21A2FA05} {59A23CAB-D098-495F-A467-74C7553FF5BB} = {6F9A42A0-A04B-4CD0-B8C9-9A728274C851} + {CA20532A-33B3-4DC0-92D2-EA6D7987D59F} = {BEE2F256-0489-4809-AB20-27ADB2D0E10C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D91D7DC5-24CC-4716-A357-8170C4EB1C32} diff --git a/src/libraries/System.Net.WebSockets.Client/tests/wasm/BrowserTimerThrottlingTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/wasm/BrowserTimerThrottlingTest.cs new file mode 100644 index 0000000000000..09e564eda879d --- /dev/null +++ b/src/libraries/System.Net.WebSockets.Client/tests/wasm/BrowserTimerThrottlingTest.cs @@ -0,0 +1,179 @@ +// 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.Linq; +using System.Net.WebSockets.Client.Tests; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace System.Net.WebSockets.Client.Wasm.Tests +{ + // https://developer.chrome.com/blog/timer-throttling-in-chrome-88/ + // https://docs.google.com/document/d/11FhKHRcABGS4SWPFGwoL6g0ALMqrFKapCk5ZTKKupEk/view + // requires chromium based browser + // requires minimized browser or browser tab out of focus, browser can't be headless + // requires --enable-features=IntensiveWakeUpThrottling:grace_period_seconds/1 chromeDriver flags + // doesn't work with --disable-background-timer-throttling + [TestCaseOrderer("System.Net.WebSockets.Client.Wasm.Tests.AlphabeticalOrderer", "System.Net.WebSockets.Client.Wasm.Tests")] + public class BrowserTimerThrottlingTest : ClientWebSocketTestBase + { + public static bool IsBrowser => RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")); + const double moreThanLightThrottlingThreshold = 1900; + const double detectLightThrottlingThreshold = 950; + + public BrowserTimerThrottlingTest(ITestOutputHelper output) : base(output) { } + + [ConditionalFact(nameof(PlatformDetection.IsBrowser))] + [OuterLoop] // involves long delay + // this test is influenced by usage of WS on the same browser tab in previous unit tests. we may need to wait long time for it to fizzle down + public async Task DotnetTimersAreHeavilyThrottledWithoutWebSocket() + { + double maxDelayMs = 0; + double maxLightDelayMs = 0; + DateTime start = DateTime.Now; + CancellationTokenSource cts = new CancellationTokenSource(); + + using (var timer = new Timers.Timer(100)) + { + DateTime last = DateTime.Now; + DateTime lastSent = DateTime.MinValue; + timer.AutoReset = true; + timer.Enabled = true; + timer.Elapsed += (object? source, Timers.ElapsedEventArgs? e) => + { + var ms = (e.SignalTime - last).TotalMilliseconds; + var msSent = (e.SignalTime - lastSent).TotalMilliseconds; + if (maxDelayMs < ms) + { + maxDelayMs = ms; + } + if (ms > moreThanLightThrottlingThreshold) + { +#if DEBUG + Console.WriteLine("Too slow tick " + ms); +#endif + // stop, we are throttled heavily, this is what we are looking for + cts.Cancel(); + } + else if (ms > detectLightThrottlingThreshold) + { + maxLightDelayMs = ms; + // we are lightly throttled +#if DEBUG + Console.WriteLine("Slow tick NO-WS " + ms); +#endif + } + if (msSent > 45000) + { + lastSent = DateTime.Now; + } + last = e.SignalTime; + }; + + // test it for 10 minutes + try { await Task.Delay(10 * 60 * 1000, cts.Token); } catch (Exception) { } + timer.Close(); + } + Assert.True(maxDelayMs > detectLightThrottlingThreshold, "Expect that it throttled lightly " + maxDelayMs); + Assert.True(maxDelayMs > moreThanLightThrottlingThreshold, "Expect that it was heavily throttled " + maxDelayMs); + } + + [ConditionalFact(nameof(WebSocketsSupported), nameof(PlatformDetection.IsBrowser))] + [OuterLoop] // involves long delay + public async Task WebSocketKeepsDotnetTimersOnlyLightlyThrottled() + { + double maxDelayMs = 0; + double maxLightDelayMs = 0; + DateTime start = DateTime.Now; + CancellationTokenSource cts = new CancellationTokenSource(); + + using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(Test.Common.Configuration.WebSockets.RemoteEchoServer, TimeOutMilliseconds, _output)) + { + using (var timer = new Timers.Timer(100)) + { + DateTime last = DateTime.Now; + DateTime lastSent = DateTime.MinValue; + timer.AutoReset = true; + timer.Enabled = true; + timer.Elapsed += async (object? source, Timers.ElapsedEventArgs? e) => + { + var ms = (e.SignalTime - last).TotalMilliseconds; + var msSent = (e.SignalTime - lastSent).TotalMilliseconds; + if (maxDelayMs < ms) + { + maxDelayMs = ms; + } + if (ms > moreThanLightThrottlingThreshold) + { + // fail fast, we are throttled heavily + Console.WriteLine("Too slow tick " + ms); + cts.Cancel(); + } + else if (ms > detectLightThrottlingThreshold) + { + maxLightDelayMs = ms; + // we are lightly throttled +#if DEBUG + Console.WriteLine("Slow tick WS " + ms); +#endif + } + if (msSent > 45000) + { + await SendAndReceive(cws, "test"); + lastSent = DateTime.Now; + } + last = e.SignalTime; + }; + + // test it for 10 minutes + try { await Task.Delay(1 * 60 * 1000, cts.Token); } catch (Exception) { } + timer.Close(); + } + await cws.CloseAsync(WebSocketCloseStatus.NormalClosure, "WebSocketKeepsDotnetTimersOnlyLightlyThrottled", CancellationToken.None); + } + Assert.True(maxDelayMs > detectLightThrottlingThreshold, "Expect that it throttled lightly " + maxDelayMs); + Assert.True(maxDelayMs < moreThanLightThrottlingThreshold, "Expect that it wasn't heavily throttled " + maxDelayMs); + } + + private async static Task SendAndReceive(ClientWebSocket cws, string message) + { + try + { + byte[] buffer = Encoding.UTF8.GetBytes(message); + await cws.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, CancellationToken.None); + + var receiveBuffer = new byte[100]; + var receiveSegment = new ArraySegment(receiveBuffer); + WebSocketReceiveResult recvRet = await cws.ReceiveAsync(receiveSegment, CancellationToken.None); +#if DEBUG + Console.WriteLine("SendAndReceive"); +#endif + } + catch (OperationCanceledException) + { + } + catch (Exception ex) + { + Console.WriteLine("SendAndReceive fail:" + ex); + } + } + } + + public class AlphabeticalOrderer : ITestCaseOrderer + { + public IEnumerable OrderTestCases(IEnumerable testCases) + where TTestCase : ITestCase + { + List result = testCases.ToList(); + result.Sort((x, y) => StringComparer.Ordinal.Compare(x.TestMethod.Method.Name, y.TestMethod.Method.Name)); + return result; + } + } +} diff --git a/src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.csproj b/src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.csproj new file mode 100644 index 0000000000000..754539cfe25ea --- /dev/null +++ b/src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.csproj @@ -0,0 +1,66 @@ + + + ../../src/Resources/Strings.resx + $(NetCoreAppCurrent)-Browser + $(DefineConstants);NETSTANDARD + + + + --background-throttling + WasmTestOnBrowser + $(TestArchiveRoot)browseronly/ + $(TestArchiveTestsRoot)$(OSPlatformConfig)/ + $(DefineConstants);TARGET_BROWSER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.sln b/src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.sln new file mode 100644 index 0000000000000..11f24affd5e1c --- /dev/null +++ b/src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31521.260 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Net.WebSockets.Client.Wasm.Tests", "System.Net.WebSockets.Client.Wasm.Tests.csproj", "{DA6DF153-169E-485D-90A4-EDBDA54B32A6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DA6DF153-169E-485D-90A4-EDBDA54B32A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA6DF153-169E-485D-90A4-EDBDA54B32A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA6DF153-169E-485D-90A4-EDBDA54B32A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA6DF153-169E-485D-90A4-EDBDA54B32A6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {627DB5DF-DE31-44B4-B5FD-E5666467145E} + EndGlobalSection +EndGlobal diff --git a/src/mono/wasm/runtime/binding_support.js b/src/mono/wasm/runtime/binding_support.js index 6e2fb3c508d42..771b76630c374 100644 --- a/src/mono/wasm/runtime/binding_support.js +++ b/src/mono/wasm/runtime/binding_support.js @@ -372,7 +372,7 @@ var BindingSupportLib = { return this._wrap_delegate_gc_handle_as_function(gc_handle); }, - _wrap_delegate_gc_handle_as_function: function (gc_handle) { + _wrap_delegate_gc_handle_as_function: function (gc_handle, call_after_listener) { this.bindings_lazy_init (); // see if we have js owned instance for this gc_handle already @@ -384,7 +384,11 @@ var BindingSupportLib = { result = function() { const delegateRoot = MONO.mono_wasm_new_root (BINDING.get_js_owned_object_by_gc_handle(gc_handle)); try { - return BINDING.call_method (result[BINDING.delegate_invoke_symbol], delegateRoot.value, result[BINDING.delegate_invoke_signature_symbol], arguments); + const res = BINDING.call_method(result[BINDING.delegate_invoke_symbol], delegateRoot.value, result[BINDING.delegate_invoke_signature_symbol], arguments); + if (call_after_listener) { + call_after_listener(); + } + return res; } finally { delegateRoot.release(); } @@ -2042,7 +2046,12 @@ var BindingSupportLib = { var obj = BINDING.mono_wasm_get_jsobj_from_js_handle(objHandle); if (!obj) throw new Error("ERR09: Invalid JS object handle for '"+sName+"'"); - var listener = BINDING._wrap_delegate_gc_handle_as_function(listener_gc_handle); + + const prevent_timer_throttling = obj.constructor.name !== 'WebSocket' || !navigator.userAgent.includes("Chrome") + ? null + : () => MONO.prevent_timer_throttling(0); + + var listener = BINDING._wrap_delegate_gc_handle_as_function(listener_gc_handle, prevent_timer_throttling); if (!listener) throw new Error("ERR10: Invalid listener gc_handle"); diff --git a/src/mono/wasm/runtime/library_mono.js b/src/mono/wasm/runtime/library_mono.js index 8f8d4cbcfe537..6dd2013444013 100644 --- a/src/mono/wasm/runtime/library_mono.js +++ b/src/mono/wasm/runtime/library_mono.js @@ -52,6 +52,7 @@ var MonoSupportLib = { $MONO: { pump_count: 0, timeout_queue: [], + spread_timers_maximum:0, _vt_stack: [], mono_wasm_runtime_is_ready : false, mono_wasm_ignore_pdb_load_errors: true, @@ -74,6 +75,8 @@ var MonoSupportLib = { export_functions: function (module) { module ["pump_message"] = MONO.pump_message.bind(MONO); + module ["prevent_timer_throttling"] = MONO.prevent_timer_throttling.bind(MONO); + module ["mono_wasm_set_timeout_exec"] = MONO.mono_wasm_set_timeout_exec.bind(MONO); module ["mono_load_runtime_and_bcl"] = MONO.mono_load_runtime_and_bcl.bind(MONO); module ["mono_load_runtime_and_bcl_args"] = MONO.mono_load_runtime_and_bcl_args.bind(MONO); module ["mono_wasm_load_bytes_into_heap"] = MONO.mono_wasm_load_bytes_into_heap.bind(MONO); @@ -1457,6 +1460,27 @@ var MonoSupportLib = { } finally { Module.removeRunDependency(configFilePath); } + }, + mono_wasm_set_timeout_exec: function(id){ + if (!this.mono_set_timeout_exec) + this.mono_set_timeout_exec = Module.cwrap ("mono_set_timeout_exec", null, [ 'number' ]); + this.mono_set_timeout_exec (id); + }, + prevent_timer_throttling: function () { + // this will schedule timers every second for next 6 minutes, it should be called from WebSocket event, to make it work + // on next call, it would only extend the timers to cover yet uncovered future + let now = new Date().valueOf(); + const desired_reach_time = now + (1000 * 60 * 6); + const next_reach_time = Math.max(now + 1000, this.spread_timers_maximum); + for (var schedule = next_reach_time; schedule < desired_reach_time; schedule += 1000) { + const delay = schedule - now; + setTimeout(() => { + this.mono_wasm_set_timeout_exec(0); + MONO.pump_count++; + MONO.pump_message(); + }, delay); + } + this.spread_timers_maximum = desired_reach_time; } }, schedule_background_exec: function () { @@ -1467,17 +1491,15 @@ var MonoSupportLib = { }, mono_set_timeout: function (timeout, id) { - if (!this.mono_set_timeout_exec) - this.mono_set_timeout_exec = Module.cwrap ("mono_set_timeout_exec", null, [ 'number' ]); if (typeof globalThis.setTimeout === 'function') { globalThis.setTimeout (function () { - this.mono_set_timeout_exec (id); + MONO.mono_wasm_set_timeout_exec (id); }, timeout); } else { ++MONO.pump_count; MONO.timeout_queue.push(function() { - this.mono_set_timeout_exec (id); + MONO.mono_wasm_set_timeout_exec (id); }) } }, From 42fc2ea450e7fd8efc30bbae6005933d9a1f2cc2 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Tue, 24 Aug 2021 14:28:39 +0200 Subject: [PATCH 2/8] wip --- src/mono/wasm/runtime/binding_support.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/binding_support.js b/src/mono/wasm/runtime/binding_support.js index 771b76630c374..6bf971fb62e65 100644 --- a/src/mono/wasm/runtime/binding_support.js +++ b/src/mono/wasm/runtime/binding_support.js @@ -160,6 +160,7 @@ var BindingSupportLib = { return Promise.resolve(js_obj) === js_obj || ((typeof js_obj === "object" || typeof js_obj === "function") && typeof js_obj.then === "function") }; + this.isChromium = navigator && navigator.userAgent && navigator.userAgent.includes("Chrome"); this._empty_string = ""; this._empty_string_ptr = 0; @@ -2047,7 +2048,7 @@ var BindingSupportLib = { if (!obj) throw new Error("ERR09: Invalid JS object handle for '"+sName+"'"); - const prevent_timer_throttling = obj.constructor.name !== 'WebSocket' || !navigator.userAgent.includes("Chrome") + const prevent_timer_throttling = !BINDING.isChromium || obj.constructor.name !== 'WebSocket' ? null : () => MONO.prevent_timer_throttling(0); From 148812d7efdc328a25d02188db5768c0328bd0da Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 25 Aug 2021 13:27:55 +0200 Subject: [PATCH 3/8] wip --- .../tests/wasm/BrowserTimerThrottlingTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Net.WebSockets.Client/tests/wasm/BrowserTimerThrottlingTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/wasm/BrowserTimerThrottlingTest.cs index 09e564eda879d..95dc4d29aa1f9 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/wasm/BrowserTimerThrottlingTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/wasm/BrowserTimerThrottlingTest.cs @@ -96,6 +96,7 @@ public async Task WebSocketKeepsDotnetTimersOnlyLightlyThrottled() using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(Test.Common.Configuration.WebSockets.RemoteEchoServer, TimeOutMilliseconds, _output)) { + await SendAndReceive(cws, "test"); using (var timer = new Timers.Timer(100)) { DateTime last = DateTime.Now; @@ -133,7 +134,7 @@ public async Task WebSocketKeepsDotnetTimersOnlyLightlyThrottled() }; // test it for 10 minutes - try { await Task.Delay(1 * 60 * 1000, cts.Token); } catch (Exception) { } + try { await Task.Delay(10 * 60 * 1000, cts.Token); } catch (Exception) { } timer.Close(); } await cws.CloseAsync(WebSocketCloseStatus.NormalClosure, "WebSocketKeepsDotnetTimersOnlyLightlyThrottled", CancellationToken.None); From 3c3d051949d22dee1865841a3b1eaa016036d655 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 25 Aug 2021 13:56:18 +0200 Subject: [PATCH 4/8] fix --- src/mono/wasm/runtime/binding_support.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/binding_support.js b/src/mono/wasm/runtime/binding_support.js index 6bf971fb62e65..0a2b13cb19663 100644 --- a/src/mono/wasm/runtime/binding_support.js +++ b/src/mono/wasm/runtime/binding_support.js @@ -160,7 +160,7 @@ var BindingSupportLib = { return Promise.resolve(js_obj) === js_obj || ((typeof js_obj === "object" || typeof js_obj === "function") && typeof js_obj.then === "function") }; - this.isChromium = navigator && navigator.userAgent && navigator.userAgent.includes("Chrome"); + this.isChromium = globalThis.navigator && globalThis.navigator.userAgent && globalThis.navigator.userAgent.includes("Chrome"); this._empty_string = ""; this._empty_string_ptr = 0; From 7959d022f71ca0ed7008820ce45e530126867ea7 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 25 Aug 2021 16:17:49 +0200 Subject: [PATCH 5/8] better assert description --- .../System.Net.WebSockets.Client/tests/SendReceiveTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs index f5d86dfff25ba..b8b4159e2db52 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs @@ -255,12 +255,12 @@ await SendAsync( "ReceiveAsync"), ex.Message); - Assert.Equal(WebSocketState.Aborted, cws.State); + Assert.True(WebSocketState.Aborted == cws.State, cws.State+" state when InvalidOperationException"); } else if (ex is WebSocketException) { // Multiple cases. - Assert.Equal(WebSocketState.Aborted, cws.State); + Assert.True(WebSocketState.Aborted == cws.State, cws.State + " state when WebSocketException"); WebSocketError errCode = (ex as WebSocketException).WebSocketErrorCode; Assert.True( @@ -269,7 +269,7 @@ await SendAsync( } else if (ex is OperationCanceledException) { - Assert.Equal(WebSocketState.Aborted, cws.State); + Assert.True(WebSocketState.Aborted == cws.State, cws.State + " state when OperationCanceledException"); } else { From 2e1fcba99de03ac9f79f7a41d62e7481bea60753 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 25 Aug 2021 19:34:35 +0200 Subject: [PATCH 6/8] exclude test from CI, because helix is headless --- src/libraries/tests.proj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index f32deab2d7b92..77355d635779b 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -227,6 +227,9 @@ + + + From f6e28f261fea9b9e8e87aee000fda59416df370b Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 25 Aug 2021 22:27:49 +0200 Subject: [PATCH 7/8] feedback --- .../System.Net.WebSockets.Client.sln | 30 ++++++++----------- .../tests/wasm/BrowserTimerThrottlingTest.cs | 21 ++++++------- src/mono/wasm/runtime/binding_support.js | 17 ++++++++--- src/mono/wasm/runtime/library_mono.js | 3 +- 4 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/libraries/System.Net.WebSockets.Client/System.Net.WebSockets.Client.sln b/src/libraries/System.Net.WebSockets.Client/System.Net.WebSockets.Client.sln index 1af1a766ae197..fbfeb98bd4062 100644 --- a/src/libraries/System.Net.WebSockets.Client/System.Net.WebSockets.Client.sln +++ b/src/libraries/System.Net.WebSockets.Client/System.Net.WebSockets.Client.sln @@ -1,8 +1,4 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31521.260 -MinimumVisualStudioVersion = 10.0.40219.1 +Microsoft Visual Studio Solution File, Format Version 12.00 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{B615DEB1-354C-4357-987A-BBA921E5A712}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Net.WebSockets.Client", "ref\System.Net.WebSockets.Client.csproj", "{BEA5BC2C-12D1-4D01-8D2C-5029578BD066}" @@ -31,10 +27,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B615DEB1-354C-4357-987A-BBA921E5A712}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B615DEB1-354C-4357-987A-BBA921E5A712}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B615DEB1-354C-4357-987A-BBA921E5A712}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B615DEB1-354C-4357-987A-BBA921E5A712}.Release|Any CPU.Build.0 = Release|Any CPU + {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Release|Any CPU.Build.0 = Release|Any CPU {BEA5BC2C-12D1-4D01-8D2C-5029578BD066}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BEA5BC2C-12D1-4D01-8D2C-5029578BD066}.Debug|Any CPU.Build.0 = Debug|Any CPU {BEA5BC2C-12D1-4D01-8D2C-5029578BD066}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -43,22 +39,22 @@ Global {0CD4C24D-7746-46F0-8D47-A396882B5468}.Debug|Any CPU.Build.0 = Debug|Any CPU {0CD4C24D-7746-46F0-8D47-A396882B5468}.Release|Any CPU.ActiveCfg = Release|Any CPU {0CD4C24D-7746-46F0-8D47-A396882B5468}.Release|Any CPU.Build.0 = Release|Any CPU - {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Debug|Any CPU.Build.0 = Debug|Any CPU - {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Release|Any CPU.ActiveCfg = Release|Any CPU - {90E8DA45-66F3-491E-B408-82AB85EEAB76}.Release|Any CPU.Build.0 = Release|Any CPU + {B615DEB1-354C-4357-987A-BBA921E5A712}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B615DEB1-354C-4357-987A-BBA921E5A712}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B615DEB1-354C-4357-987A-BBA921E5A712}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B615DEB1-354C-4357-987A-BBA921E5A712}.Release|Any CPU.Build.0 = Release|Any CPU {8CD4D190-F656-4970-9AE9-4A9F8B30A2F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8CD4D190-F656-4970-9AE9-4A9F8B30A2F8}.Debug|Any CPU.Build.0 = Debug|Any CPU {8CD4D190-F656-4970-9AE9-4A9F8B30A2F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {8CD4D190-F656-4970-9AE9-4A9F8B30A2F8}.Release|Any CPU.Build.0 = Release|Any CPU - {6B9721B2-00D0-41EC-96B9-6428CF1830CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6B9721B2-00D0-41EC-96B9-6428CF1830CF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6B9721B2-00D0-41EC-96B9-6428CF1830CF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6B9721B2-00D0-41EC-96B9-6428CF1830CF}.Release|Any CPU.Build.0 = Release|Any CPU {59A23CAB-D098-495F-A467-74C7553FF5BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {59A23CAB-D098-495F-A467-74C7553FF5BB}.Debug|Any CPU.Build.0 = Debug|Any CPU {59A23CAB-D098-495F-A467-74C7553FF5BB}.Release|Any CPU.ActiveCfg = Release|Any CPU {59A23CAB-D098-495F-A467-74C7553FF5BB}.Release|Any CPU.Build.0 = Release|Any CPU + {6B9721B2-00D0-41EC-96B9-6428CF1830CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B9721B2-00D0-41EC-96B9-6428CF1830CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B9721B2-00D0-41EC-96B9-6428CF1830CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B9721B2-00D0-41EC-96B9-6428CF1830CF}.Release|Any CPU.Build.0 = Release|Any CPU {CA20532A-33B3-4DC0-92D2-EA6D7987D59F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CA20532A-33B3-4DC0-92D2-EA6D7987D59F}.Debug|Any CPU.Build.0 = Debug|Any CPU {CA20532A-33B3-4DC0-92D2-EA6D7987D59F}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/libraries/System.Net.WebSockets.Client/tests/wasm/BrowserTimerThrottlingTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/wasm/BrowserTimerThrottlingTest.cs index 95dc4d29aa1f9..df6e5f1770590 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/wasm/BrowserTimerThrottlingTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/wasm/BrowserTimerThrottlingTest.cs @@ -26,7 +26,9 @@ public class BrowserTimerThrottlingTest : ClientWebSocketTestBase { public static bool IsBrowser => RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")); const double moreThanLightThrottlingThreshold = 1900; - const double detectLightThrottlingThreshold = 950; + const double detectLightThrottlingThreshold = 900; + const double webSocketMessageFrequency = 45000; + const double fastTimeoutFrequency = 100; public BrowserTimerThrottlingTest(ITestOutputHelper output) : base(output) { } @@ -40,16 +42,14 @@ public async Task DotnetTimersAreHeavilyThrottledWithoutWebSocket() DateTime start = DateTime.Now; CancellationTokenSource cts = new CancellationTokenSource(); - using (var timer = new Timers.Timer(100)) + using (var timer = new Timers.Timer(fastTimeoutFrequency)) { DateTime last = DateTime.Now; - DateTime lastSent = DateTime.MinValue; timer.AutoReset = true; timer.Enabled = true; timer.Elapsed += (object? source, Timers.ElapsedEventArgs? e) => { var ms = (e.SignalTime - last).TotalMilliseconds; - var msSent = (e.SignalTime - lastSent).TotalMilliseconds; if (maxDelayMs < ms) { maxDelayMs = ms; @@ -70,10 +70,6 @@ public async Task DotnetTimersAreHeavilyThrottledWithoutWebSocket() Console.WriteLine("Slow tick NO-WS " + ms); #endif } - if (msSent > 45000) - { - lastSent = DateTime.Now; - } last = e.SignalTime; }; @@ -97,7 +93,7 @@ public async Task WebSocketKeepsDotnetTimersOnlyLightlyThrottled() using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(Test.Common.Configuration.WebSockets.RemoteEchoServer, TimeOutMilliseconds, _output)) { await SendAndReceive(cws, "test"); - using (var timer = new Timers.Timer(100)) + using (var timer = new Timers.Timer(fastTimeoutFrequency)) { DateTime last = DateTime.Now; DateTime lastSent = DateTime.MinValue; @@ -114,7 +110,9 @@ public async Task WebSocketKeepsDotnetTimersOnlyLightlyThrottled() if (ms > moreThanLightThrottlingThreshold) { // fail fast, we are throttled heavily +#if DEBUG Console.WriteLine("Too slow tick " + ms); +#endif cts.Cancel(); } else if (ms > detectLightThrottlingThreshold) @@ -125,7 +123,7 @@ public async Task WebSocketKeepsDotnetTimersOnlyLightlyThrottled() Console.WriteLine("Slow tick WS " + ms); #endif } - if (msSent > 45000) + if (msSent > webSocketMessageFrequency) { await SendAndReceive(cws, "test"); lastSent = DateTime.Now; @@ -162,11 +160,14 @@ private async static Task SendAndReceive(ClientWebSocket cws, string message) } catch (Exception ex) { +#if DEBUG Console.WriteLine("SendAndReceive fail:" + ex); +#endif } } } + // this is just for convinience, as the second test has side-effect to running page, the first test would take longer if they are in another order public class AlphabeticalOrderer : ITestCaseOrderer { public IEnumerable OrderTestCases(IEnumerable testCases) diff --git a/src/mono/wasm/runtime/binding_support.js b/src/mono/wasm/runtime/binding_support.js index 0a2b13cb19663..11d50bfc9558b 100644 --- a/src/mono/wasm/runtime/binding_support.js +++ b/src/mono/wasm/runtime/binding_support.js @@ -160,7 +160,16 @@ var BindingSupportLib = { return Promise.resolve(js_obj) === js_obj || ((typeof js_obj === "object" || typeof js_obj === "function") && typeof js_obj.then === "function") }; - this.isChromium = globalThis.navigator && globalThis.navigator.userAgent && globalThis.navigator.userAgent.includes("Chrome"); + this.isChromium = false; + if (globalThis.navigator) { + var nav = globalThis.navigator; + if (nav.userAgentData && nav.userAgentData.brands) { + this.isChromium = nav.userAgentData.brands.some((i) => i.brand == 'Chromium'); + } + else if (globalThis.navigator.userAgent) { + this.isChromium = nav.userAgent.includes("Chrome"); + } + } this._empty_string = ""; this._empty_string_ptr = 0; @@ -373,7 +382,7 @@ var BindingSupportLib = { return this._wrap_delegate_gc_handle_as_function(gc_handle); }, - _wrap_delegate_gc_handle_as_function: function (gc_handle, call_after_listener) { + _wrap_delegate_gc_handle_as_function: function (gc_handle, after_listener_callback) { this.bindings_lazy_init (); // see if we have js owned instance for this gc_handle already @@ -386,8 +395,8 @@ var BindingSupportLib = { const delegateRoot = MONO.mono_wasm_new_root (BINDING.get_js_owned_object_by_gc_handle(gc_handle)); try { const res = BINDING.call_method(result[BINDING.delegate_invoke_symbol], delegateRoot.value, result[BINDING.delegate_invoke_signature_symbol], arguments); - if (call_after_listener) { - call_after_listener(); + if (after_listener_callback) { + after_listener_callback(); } return res; } finally { diff --git a/src/mono/wasm/runtime/library_mono.js b/src/mono/wasm/runtime/library_mono.js index 6dd2013444013..1e6fc31f047da 100644 --- a/src/mono/wasm/runtime/library_mono.js +++ b/src/mono/wasm/runtime/library_mono.js @@ -1472,7 +1472,8 @@ var MonoSupportLib = { let now = new Date().valueOf(); const desired_reach_time = now + (1000 * 60 * 6); const next_reach_time = Math.max(now + 1000, this.spread_timers_maximum); - for (var schedule = next_reach_time; schedule < desired_reach_time; schedule += 1000) { + const light_throttling_frequency = 1000; + for (var schedule = next_reach_time; schedule < desired_reach_time; schedule += light_throttling_frequency) { const delay = schedule - now; setTimeout(() => { this.mono_wasm_set_timeout_exec(0); From 13c8d77e7bd2f2796c6446b88e5a1c75a94bce8d Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 25 Aug 2021 22:31:29 +0200 Subject: [PATCH 8/8] more --- .../System.Net.WebSockets.Client.sln | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.WebSockets.Client/System.Net.WebSockets.Client.sln b/src/libraries/System.Net.WebSockets.Client/System.Net.WebSockets.Client.sln index fbfeb98bd4062..a0efb293da7af 100644 --- a/src/libraries/System.Net.WebSockets.Client/System.Net.WebSockets.Client.sln +++ b/src/libraries/System.Net.WebSockets.Client/System.Net.WebSockets.Client.sln @@ -64,12 +64,12 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution + {90E8DA45-66F3-491E-B408-82AB85EEAB76} = {BEE2F256-0489-4809-AB20-27ADB2D0E10C} {B615DEB1-354C-4357-987A-BBA921E5A712} = {BEE2F256-0489-4809-AB20-27ADB2D0E10C} {BEA5BC2C-12D1-4D01-8D2C-5029578BD066} = {A0314AC5-E490-4A6A-B946-8B9A21A2FA05} + {6B9721B2-00D0-41EC-96B9-6428CF1830CF} = {A0314AC5-E490-4A6A-B946-8B9A21A2FA05} {0CD4C24D-7746-46F0-8D47-A396882B5468} = {6F9A42A0-A04B-4CD0-B8C9-9A728274C851} - {90E8DA45-66F3-491E-B408-82AB85EEAB76} = {BEE2F256-0489-4809-AB20-27ADB2D0E10C} {8CD4D190-F656-4970-9AE9-4A9F8B30A2F8} = {6F9A42A0-A04B-4CD0-B8C9-9A728274C851} - {6B9721B2-00D0-41EC-96B9-6428CF1830CF} = {A0314AC5-E490-4A6A-B946-8B9A21A2FA05} {59A23CAB-D098-495F-A467-74C7553FF5BB} = {6F9A42A0-A04B-4CD0-B8C9-9A728274C851} {CA20532A-33B3-4DC0-92D2-EA6D7987D59F} = {BEE2F256-0489-4809-AB20-27ADB2D0E10C} EndGlobalSection