@@ -20,6 +20,7 @@ internal sealed class TestApplication(
2020 TerminalTestReporter output ,
2121 Action < CommandLineOptionMessages > onHelpRequested ) : IDisposable
2222{
23+ private readonly Lock _requestLock = new ( ) ;
2324 private readonly BuildOptions _buildOptions = buildOptions ;
2425 private readonly Action < CommandLineOptionMessages > _onHelpRequested = onHelpRequested ;
2526 private readonly TestApplicationHandler _handler = new ( output , module , testOptions ) ;
@@ -199,63 +200,69 @@ private async Task WaitConnectionAsync(CancellationToken token)
199200
200201 private Task < IResponse > OnRequest ( NamedPipeServer server , IRequest request )
201202 {
202- try
203+ // We need to lock as we might be called concurrently when test app child processes all communicate with us.
204+ // For example, in a case of a sharding extension, we could get test result messages concurrently.
205+ // To be the most safe, we lock the whole OnRequest.
206+ lock ( _requestLock )
203207 {
204- switch ( request )
208+ try
205209 {
206- case HandshakeMessage handshakeMessage :
207- _handshakes . Add ( server , handshakeMessage ) ;
208- string negotiatedVersion = GetSupportedProtocolVersion ( handshakeMessage ) ;
209- OnHandshakeMessage ( handshakeMessage , negotiatedVersion . Length > 0 ) ;
210- return Task . FromResult ( ( IResponse ) CreateHandshakeMessage ( negotiatedVersion ) ) ;
211-
212- case CommandLineOptionMessages commandLineOptionMessages :
213- OnCommandLineOptionMessages ( commandLineOptionMessages ) ;
214- break ;
215-
216- case DiscoveredTestMessages discoveredTestMessages :
217- OnDiscoveredTestMessages ( discoveredTestMessages ) ;
218- break ;
219-
220- case TestResultMessages testResultMessages :
221- OnTestResultMessages ( testResultMessages ) ;
222- break ;
223-
224- case FileArtifactMessages fileArtifactMessages :
225- OnFileArtifactMessages ( fileArtifactMessages ) ;
226- break ;
227-
228- case TestSessionEvent sessionEvent :
229- OnSessionEvent ( sessionEvent ) ;
230- break ;
231-
232- // If we don't recognize the message, log and skip it
233- case UnknownMessage unknownMessage :
234- Logger . LogTrace ( $ "Request '{ request . GetType ( ) } ' with Serializer ID = { unknownMessage . SerializerId } is unsupported.") ;
235- return Task . FromResult ( ( IResponse ) VoidResponse . CachedInstance ) ;
236-
237- default :
238- // If it doesn't match any of the above, throw an exception
239- throw new NotSupportedException ( string . Format ( CliCommandStrings . CmdUnsupportedMessageRequestTypeException , request . GetType ( ) ) ) ;
210+ switch ( request )
211+ {
212+ case HandshakeMessage handshakeMessage :
213+ _handshakes . Add ( server , handshakeMessage ) ;
214+ string negotiatedVersion = GetSupportedProtocolVersion ( handshakeMessage ) ;
215+ OnHandshakeMessage ( handshakeMessage , negotiatedVersion . Length > 0 ) ;
216+ return Task . FromResult ( ( IResponse ) CreateHandshakeMessage ( negotiatedVersion ) ) ;
217+
218+ case CommandLineOptionMessages commandLineOptionMessages :
219+ OnCommandLineOptionMessages ( commandLineOptionMessages ) ;
220+ break ;
221+
222+ case DiscoveredTestMessages discoveredTestMessages :
223+ OnDiscoveredTestMessages ( discoveredTestMessages ) ;
224+ break ;
225+
226+ case TestResultMessages testResultMessages :
227+ OnTestResultMessages ( testResultMessages ) ;
228+ break ;
229+
230+ case FileArtifactMessages fileArtifactMessages :
231+ OnFileArtifactMessages ( fileArtifactMessages ) ;
232+ break ;
233+
234+ case TestSessionEvent sessionEvent :
235+ OnSessionEvent ( sessionEvent ) ;
236+ break ;
237+
238+ // If we don't recognize the message, log and skip it
239+ case UnknownMessage unknownMessage :
240+ Logger . LogTrace ( $ "Request '{ request . GetType ( ) } ' with Serializer ID = { unknownMessage . SerializerId } is unsupported.") ;
241+ return Task . FromResult ( ( IResponse ) VoidResponse . CachedInstance ) ;
242+
243+ default :
244+ // If it doesn't match any of the above, throw an exception
245+ throw new NotSupportedException ( string . Format ( CliCommandStrings . CmdUnsupportedMessageRequestTypeException , request . GetType ( ) ) ) ;
246+ }
247+ }
248+ catch ( Exception ex )
249+ {
250+ // BE CAREFUL:
251+ // When handling some of the messages, we may throw an exception in unexpected state.
252+ // (e.g, OnSessionEvent may throw if we receive TestSessionEnd without TestSessionStart).
253+ // (or if we receive help-related messages when not in help mode)
254+ // In that case, we FailFast.
255+ // The lack of FailFast *might* have unintended consequences, such as breaking the internal loop of pipe server.
256+ // In that case, maybe MTP app will continue waiting for response, but we don't send the response and are waiting for
257+ // MTP app process exit (which doesn't happen).
258+ // So, we explicitly FailFast here.
259+ string exAsString = ex . ToString ( ) ;
260+ Logger . LogTrace ( exAsString ) ;
261+ Environment . FailFast ( exAsString ) ;
240262 }
241- }
242- catch ( Exception ex )
243- {
244- // BE CAREFUL:
245- // When handling some of the messages, we may throw an exception in unexpected state.
246- // (e.g, OnSessionEvent may throw if we receive TestSessionEnd without TestSessionStart).
247- // (or if we receive help-related messages when not in help mode)
248- // In that case, we FailFast.
249- // The lack of FailFast *might* have unintended consequences, such as breaking the internal loop of pipe server.
250- // In that case, maybe MTP app will continue waiting for response, but we don't send the response and are waiting for
251- // MTP app process exit (which doesn't happen).
252- // So, we explicitly FailFast here.
253- string exAsString = ex . ToString ( ) ;
254- Logger . LogTrace ( exAsString ) ;
255- Environment . FailFast ( exAsString ) ;
256- }
257263
258- return Task . FromResult ( ( IResponse ) VoidResponse . CachedInstance ) ;
264+ return Task . FromResult ( ( IResponse ) VoidResponse . CachedInstance ) ;
265+ }
259266 }
260267
261268 private static string GetSupportedProtocolVersion ( HandshakeMessage handshakeMessage )
0 commit comments