diff --git a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Configuration.cs b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Configuration.cs index baba7d94d919c5..e73e0704be164e 100644 --- a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Configuration.cs +++ b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Configuration.cs @@ -45,6 +45,7 @@ public class Configuration public bool UseHttpSys { get; set; } public bool LogAspNet { get; set; } public bool Trace { get; set; } + public bool? TrackUnobservedExceptions { get; set; } public int? ServerMaxConcurrentStreams { get; set; } public int? ServerMaxFrameSize { get; set; } public int? ServerInitialConnectionWindowSize { get; set; } diff --git a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Program.cs b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Program.cs index d5962dfba4d096..8a93a43f20ddef 100644 --- a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Program.cs +++ b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Program.cs @@ -30,6 +30,8 @@ public enum ExitCode { Success = 0, StressError = 1, CliError = 2 }; public static readonly bool IsQuicSupported = QuicListener.IsSupported && QuicConnection.IsSupported; + private static readonly Dictionary s_unobservedExceptions = new Dictionary(); + public static async Task Main(string[] args) { if (!TryParseCli(args, out Configuration? config)) @@ -69,6 +71,7 @@ private static bool TryParseCli(string[] args, [NotNullWhen(true)] out Configura cmd.AddOption(new Option("-serverMaxFrameSize", "Overrides kestrel max frame size setting.") { Argument = new Argument("bytes", null) }); cmd.AddOption(new Option("-serverInitialConnectionWindowSize", "Overrides kestrel initial connection window size setting.") { Argument = new Argument("bytes", null) }); cmd.AddOption(new Option("-serverMaxRequestHeaderFieldSize", "Overrides kestrel max request header field size.") { Argument = new Argument("bytes", null) }); + cmd.AddOption(new Option("-unobservedEx", "Enable tracking unobserved exceptions.") { Argument = new Argument("enable", null) }); ParseResult cmdline = cmd.Parse(args); if (cmdline.Errors.Count > 0) @@ -109,6 +112,7 @@ private static bool TryParseCli(string[] args, [NotNullWhen(true)] out Configura UseHttpSys = cmdline.ValueForOption("-httpSys"), LogAspNet = cmdline.ValueForOption("-aspnetlog"), Trace = cmdline.ValueForOption("-trace"), + TrackUnobservedExceptions = cmdline.ValueForOption("-unobservedEx"), ServerMaxConcurrentStreams = cmdline.ValueForOption("-serverMaxConcurrentStreams"), ServerMaxFrameSize = cmdline.ValueForOption("-serverMaxFrameSize"), ServerInitialConnectionWindowSize = cmdline.ValueForOption("-serverInitialConnectionWindowSize"), @@ -164,6 +168,9 @@ private static async Task Run(Configuration config) Type msQuicApiType = Type.GetType("System.Net.Quic.MsQuicApi, System.Net.Quic")!; string msQuicLibraryVersion = (string)msQuicApiType.GetProperty("MsQuicLibraryVersion", BindingFlags.NonPublic | BindingFlags.Static)!.GetGetMethod(true)!.Invoke(null, Array.Empty())!; + bool trackUnobservedExceptions = config.TrackUnobservedExceptions.HasValue + ? config.TrackUnobservedExceptions.Value + : config.RunMode.HasFlag(RunMode.client); Console.WriteLine(" .NET Core: " + GetAssemblyInfo(typeof(object).Assembly)); Console.WriteLine(" ASP.NET Core: " + GetAssemblyInfo(typeof(WebHost).Assembly)); @@ -184,8 +191,21 @@ private static async Task Run(Configuration config) Console.WriteLine(" Cancellation: " + 100 * config.CancellationProbability + "%"); Console.WriteLine("Max Content Size: " + config.MaxContentLength); Console.WriteLine("Query Parameters: " + config.MaxParameters); + Console.WriteLine(" Unobserved Ex: " + (trackUnobservedExceptions ? "Tracked" : "Not tracked")); Console.WriteLine(); + if (trackUnobservedExceptions) + { + TaskScheduler.UnobservedTaskException += (_, e) => + { + lock (s_unobservedExceptions) + { + string text = e.Exception.ToString(); + s_unobservedExceptions[text] = s_unobservedExceptions.GetValueOrDefault(text) + 1; + } + }; + } + StressServer? server = null; if (config.RunMode.HasFlag(RunMode.server)) { @@ -210,10 +230,28 @@ private static async Task Run(Configuration config) client?.Stop(); client?.PrintFinalReport(); + if (trackUnobservedExceptions) + { + PrintUnobservedExceptions(); + } + // return nonzero status code if there are stress errors return client?.TotalErrorCount == 0 ? ExitCode.Success : ExitCode.StressError; } + private static void PrintUnobservedExceptions() + { + Console.WriteLine($"Detected {s_unobservedExceptions.Count} unobserved exceptions:"); + + int i = 1; + foreach (KeyValuePair kv in s_unobservedExceptions.OrderByDescending(p => p.Value)) + { + Console.WriteLine($"Exception type {i++}/{s_unobservedExceptions.Count} (hit {kv.Value} times):"); + Console.WriteLine(kv.Key); + Console.WriteLine(); + } + } + private static async Task WaitUntilMaxExecutionTimeElapsedOrKeyboardInterrupt(TimeSpan? maxExecutionTime = null) { var tcs = new TaskCompletionSource();