Skip to content

Commit ff18c30

Browse files
authored
Track unobserved exceptions in stress tests (#114225)
1 parent ebcbde1 commit ff18c30

File tree

2 files changed

+39
-0
lines changed

2 files changed

+39
-0
lines changed

src/libraries/System.Net.Http/tests/StressTests/HttpStress/Configuration.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class Configuration
4545
public bool UseHttpSys { get; set; }
4646
public bool LogAspNet { get; set; }
4747
public bool Trace { get; set; }
48+
public bool? TrackUnobservedExceptions { get; set; }
4849
public int? ServerMaxConcurrentStreams { get; set; }
4950
public int? ServerMaxFrameSize { get; set; }
5051
public int? ServerInitialConnectionWindowSize { get; set; }

src/libraries/System.Net.Http/tests/StressTests/HttpStress/Program.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public enum ExitCode { Success = 0, StressError = 1, CliError = 2 };
3030

3131
public static readonly bool IsQuicSupported = QuicListener.IsSupported && QuicConnection.IsSupported;
3232

33+
private static readonly Dictionary<string, int> s_unobservedExceptions = new Dictionary<string, int>();
34+
3335
public static async Task<int> Main(string[] args)
3436
{
3537
if (!TryParseCli(args, out Configuration? config))
@@ -69,6 +71,7 @@ private static bool TryParseCli(string[] args, [NotNullWhen(true)] out Configura
6971
cmd.AddOption(new Option("-serverMaxFrameSize", "Overrides kestrel max frame size setting.") { Argument = new Argument<int?>("bytes", null) });
7072
cmd.AddOption(new Option("-serverInitialConnectionWindowSize", "Overrides kestrel initial connection window size setting.") { Argument = new Argument<int?>("bytes", null) });
7173
cmd.AddOption(new Option("-serverMaxRequestHeaderFieldSize", "Overrides kestrel max request header field size.") { Argument = new Argument<int?>("bytes", null) });
74+
cmd.AddOption(new Option("-unobservedEx", "Enable tracking unobserved exceptions.") { Argument = new Argument<bool?>("enable", null) });
7275

7376
ParseResult cmdline = cmd.Parse(args);
7477
if (cmdline.Errors.Count > 0)
@@ -109,6 +112,7 @@ private static bool TryParseCli(string[] args, [NotNullWhen(true)] out Configura
109112
UseHttpSys = cmdline.ValueForOption<bool>("-httpSys"),
110113
LogAspNet = cmdline.ValueForOption<bool>("-aspnetlog"),
111114
Trace = cmdline.ValueForOption<bool>("-trace"),
115+
TrackUnobservedExceptions = cmdline.ValueForOption<bool?>("-unobservedEx"),
112116
ServerMaxConcurrentStreams = cmdline.ValueForOption<int?>("-serverMaxConcurrentStreams"),
113117
ServerMaxFrameSize = cmdline.ValueForOption<int?>("-serverMaxFrameSize"),
114118
ServerInitialConnectionWindowSize = cmdline.ValueForOption<int?>("-serverInitialConnectionWindowSize"),
@@ -164,6 +168,9 @@ private static async Task<ExitCode> Run(Configuration config)
164168

165169
Type msQuicApiType = Type.GetType("System.Net.Quic.MsQuicApi, System.Net.Quic")!;
166170
string msQuicLibraryVersion = (string)msQuicApiType.GetProperty("MsQuicLibraryVersion", BindingFlags.NonPublic | BindingFlags.Static)!.GetGetMethod(true)!.Invoke(null, Array.Empty<object?>())!;
171+
bool trackUnobservedExceptions = config.TrackUnobservedExceptions.HasValue
172+
? config.TrackUnobservedExceptions.Value
173+
: config.RunMode.HasFlag(RunMode.client);
167174

168175
Console.WriteLine(" .NET Core: " + GetAssemblyInfo(typeof(object).Assembly));
169176
Console.WriteLine(" ASP.NET Core: " + GetAssemblyInfo(typeof(WebHost).Assembly));
@@ -184,8 +191,21 @@ private static async Task<ExitCode> Run(Configuration config)
184191
Console.WriteLine(" Cancellation: " + 100 * config.CancellationProbability + "%");
185192
Console.WriteLine("Max Content Size: " + config.MaxContentLength);
186193
Console.WriteLine("Query Parameters: " + config.MaxParameters);
194+
Console.WriteLine(" Unobserved Ex: " + (trackUnobservedExceptions ? "Tracked" : "Not tracked"));
187195
Console.WriteLine();
188196

197+
if (trackUnobservedExceptions)
198+
{
199+
TaskScheduler.UnobservedTaskException += (_, e) =>
200+
{
201+
lock (s_unobservedExceptions)
202+
{
203+
string text = e.Exception.ToString();
204+
s_unobservedExceptions[text] = s_unobservedExceptions.GetValueOrDefault(text) + 1;
205+
}
206+
};
207+
}
208+
189209
StressServer? server = null;
190210
if (config.RunMode.HasFlag(RunMode.server))
191211
{
@@ -210,10 +230,28 @@ private static async Task<ExitCode> Run(Configuration config)
210230
client?.Stop();
211231
client?.PrintFinalReport();
212232

233+
if (trackUnobservedExceptions)
234+
{
235+
PrintUnobservedExceptions();
236+
}
237+
213238
// return nonzero status code if there are stress errors
214239
return client?.TotalErrorCount == 0 ? ExitCode.Success : ExitCode.StressError;
215240
}
216241

242+
private static void PrintUnobservedExceptions()
243+
{
244+
Console.WriteLine($"Detected {s_unobservedExceptions.Count} unobserved exceptions:");
245+
246+
int i = 1;
247+
foreach (KeyValuePair<string, int> kv in s_unobservedExceptions.OrderByDescending(p => p.Value))
248+
{
249+
Console.WriteLine($"Exception type {i++}/{s_unobservedExceptions.Count} (hit {kv.Value} times):");
250+
Console.WriteLine(kv.Key);
251+
Console.WriteLine();
252+
}
253+
}
254+
217255
private static async Task WaitUntilMaxExecutionTimeElapsedOrKeyboardInterrupt(TimeSpan? maxExecutionTime = null)
218256
{
219257
var tcs = new TaskCompletionSource<bool>();

0 commit comments

Comments
 (0)