-
Couldn't load subscription status.
- Fork 5.2k
[HttpStress] Track unobserved exceptions #114225
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<string, int> s_unobservedExceptions = new Dictionary<string, int>(); | ||
|
|
||
| public static async Task<int> 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<int?>("bytes", null) }); | ||
| cmd.AddOption(new Option("-serverInitialConnectionWindowSize", "Overrides kestrel initial connection window size setting.") { Argument = new Argument<int?>("bytes", null) }); | ||
| cmd.AddOption(new Option("-serverMaxRequestHeaderFieldSize", "Overrides kestrel max request header field size.") { Argument = new Argument<int?>("bytes", null) }); | ||
| cmd.AddOption(new Option("-unobservedEx", "Enable tracking unobserved exceptions.") { Argument = new Argument<bool?>("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<bool>("-httpSys"), | ||
| LogAspNet = cmdline.ValueForOption<bool>("-aspnetlog"), | ||
| Trace = cmdline.ValueForOption<bool>("-trace"), | ||
| TrackUnobservedExceptions = cmdline.ValueForOption<bool?>("-unobservedEx"), | ||
| ServerMaxConcurrentStreams = cmdline.ValueForOption<int?>("-serverMaxConcurrentStreams"), | ||
| ServerMaxFrameSize = cmdline.ValueForOption<int?>("-serverMaxFrameSize"), | ||
| ServerInitialConnectionWindowSize = cmdline.ValueForOption<int?>("-serverInitialConnectionWindowSize"), | ||
|
|
@@ -164,6 +168,9 @@ private static async Task<ExitCode> 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<object?>())!; | ||
| 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<ExitCode> 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<ExitCode> Run(Configuration config) | |
| client?.Stop(); | ||
| client?.PrintFinalReport(); | ||
|
|
||
| if (trackUnobservedExceptions) | ||
| { | ||
| PrintUnobservedExceptions(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we consider stress as failed if we see any? Just so we don't ignore them by accident. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My plan is to monitor the results after #114226 first to make sure we don't add too much noise here. |
||
| } | ||
|
|
||
| // 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<string, int> 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<bool>(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using a more concise identifier for exceptions (e.g. exception type and message) instead of the full output of ToString(), to avoid using overly verbose keys in the dictionary and potential memory overhead.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CI test run will show if the overhead is high. Even if that's the case, fixing #114128 should significantly reduce it.