@@ -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