diff --git a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs
index d95891c425..f93857b671 100644
--- a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs
+++ b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs
@@ -78,6 +78,13 @@ internal class TestRequestManager : ITestRequestManager
/// Assumption: There can only be one active discovery request.
///
private IDiscoveryRequest? _currentDiscoveryRequest;
+ ///
+ /// Guards cancellation of the current discovery request, by resetting when the request is received,
+ /// because the request needs time to setup and populate the _currentTestRunRequest. This might take a relatively
+ /// long time when the machine is slow, because the setup is called as an async task, so it needs to be processed by thread pool
+ /// and there might be a queue of existing tasks.
+ ///
+ private readonly ManualResetEvent _discoveryStarting = new(true);
///
/// Maintains the current active test run attachments processing cancellation token source.
@@ -163,106 +170,119 @@ public void DiscoverTests(
ITestDiscoveryEventsRegistrar discoveryEventsRegistrar,
ProtocolConfig protocolConfig)
{
- EqtTrace.Info("TestRequestManager.DiscoverTests: Discovery tests started.");
-
- // TODO: Normalize rest of the data on the request as well
- discoveryPayload.Sources = KnownPlatformSourceFilter.FilterKnownPlatformSources(discoveryPayload.Sources?.Distinct().ToList());
- discoveryPayload.RunSettings ??= "";
-
- var runsettings = discoveryPayload.RunSettings;
-
- if (discoveryPayload.TestPlatformOptions != null)
+ try
{
- _telemetryOptedIn = discoveryPayload.TestPlatformOptions.CollectMetrics;
- }
+ // Flag that that discovery is being initialized, so all requests to cancel discovery will wait till we set the discovery up.
+ _discoveryStarting.Reset();
- var requestData = GetRequestData(protocolConfig);
- if (UpdateRunSettingsIfRequired(
- runsettings,
- discoveryPayload.Sources.ToList(),
- discoveryEventsRegistrar,
- isDiscovery: true,
- out string updatedRunsettings,
- out IDictionary sourceToArchitectureMap,
- out IDictionary sourceToFrameworkMap))
- {
- runsettings = updatedRunsettings;
- }
+ // Make sure to run the run request inside a lock as the below section is not thread-safe.
+ // There can be only one discovery or execution request at a given point in time.
+ lock (_syncObject)
+ {
+ EqtTrace.Info("TestRequestManager.DiscoverTests: Discovery tests started.");
- var sourceToSourceDetailMap = discoveryPayload.Sources.Select(source => new SourceDetail
- {
- Source = source,
- Architecture = sourceToArchitectureMap[source],
- Framework = sourceToFrameworkMap[source],
- }).ToDictionary(k => k.Source!);
+ // TODO: Normalize rest of the data on the request as well
+ discoveryPayload.Sources = KnownPlatformSourceFilter.FilterKnownPlatformSources(discoveryPayload.Sources?.Distinct().ToList());
+ discoveryPayload.RunSettings ??= "";
- var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(runsettings);
- var batchSize = runConfiguration.BatchSize;
- var testCaseFilterFromRunsettings = runConfiguration.TestCaseFilter;
+ var runsettings = discoveryPayload.RunSettings;
- if (requestData.IsTelemetryOptedIn)
- {
- // Collect metrics.
- CollectMetrics(requestData, runConfiguration);
+ if (discoveryPayload.TestPlatformOptions != null)
+ {
+ _telemetryOptedIn = discoveryPayload.TestPlatformOptions.CollectMetrics;
+ }
- // Collect commands.
- LogCommandsTelemetryPoints(requestData);
- }
+ var requestData = GetRequestData(protocolConfig);
+ if (UpdateRunSettingsIfRequired(
+ runsettings,
+ discoveryPayload.Sources.ToList(),
+ discoveryEventsRegistrar,
+ isDiscovery: true,
+ out string updatedRunsettings,
+ out IDictionary sourceToArchitectureMap,
+ out IDictionary sourceToFrameworkMap))
+ {
+ runsettings = updatedRunsettings;
+ }
- // Create discovery request.
- var criteria = new DiscoveryCriteria(
- discoveryPayload.Sources,
- batchSize,
- _commandLineOptions.TestStatsEventTimeout,
- runsettings,
- discoveryPayload.TestSessionInfo)
- {
- TestCaseFilter = _commandLineOptions.TestCaseFilterValue
- ?? testCaseFilterFromRunsettings
- };
+ var sourceToSourceDetailMap = discoveryPayload.Sources.Select(source => new SourceDetail
+ {
+ Source = source,
+ Architecture = sourceToArchitectureMap[source],
+ Framework = sourceToFrameworkMap[source],
+ }).ToDictionary(k => k.Source!);
- // Make sure to run the run request inside a lock as the below section is not thread-safe.
- // There can be only one discovery or execution request at a given point in time.
- lock (_syncObject)
- {
- try
- {
- EqtTrace.Info("TestRequestManager.DiscoverTests: Synchronization context taken");
+ var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(runsettings);
+ var batchSize = runConfiguration.BatchSize;
+ var testCaseFilterFromRunsettings = runConfiguration.TestCaseFilter;
- _currentDiscoveryRequest = _testPlatform.CreateDiscoveryRequest(
- requestData,
- criteria,
- discoveryPayload.TestPlatformOptions,
- sourceToSourceDetailMap,
- new EventRegistrarToWarningLoggerAdapter(discoveryEventsRegistrar));
- discoveryEventsRegistrar?.RegisterDiscoveryEvents(_currentDiscoveryRequest);
+ if (requestData.IsTelemetryOptedIn)
+ {
+ // Collect metrics.
+ CollectMetrics(requestData, runConfiguration);
- // Notify start of discovery start.
- _testPlatformEventSource.DiscoveryRequestStart();
+ // Collect commands.
+ LogCommandsTelemetryPoints(requestData);
+ }
- // Start the discovery of tests and wait for completion.
- _currentDiscoveryRequest.DiscoverAsync();
- _currentDiscoveryRequest.WaitForCompletion();
- }
- finally
- {
- if (_currentDiscoveryRequest != null)
+ // Create discovery request.
+ var criteria = new DiscoveryCriteria(
+ discoveryPayload.Sources,
+ batchSize,
+ _commandLineOptions.TestStatsEventTimeout,
+ runsettings,
+ discoveryPayload.TestSessionInfo)
{
- // Dispose the discovery request and unregister for events.
- discoveryEventsRegistrar?.UnregisterDiscoveryEvents(_currentDiscoveryRequest);
- _currentDiscoveryRequest.Dispose();
- _currentDiscoveryRequest = null;
- }
+ TestCaseFilter = _commandLineOptions.TestCaseFilterValue
+ ?? testCaseFilterFromRunsettings
+ };
- EqtTrace.Info("TestRequestManager.DiscoverTests: Discovery tests completed.");
- _testPlatformEventSource.DiscoveryRequestStop();
- // Posts the discovery complete event.
- _metricsPublisher.Result.PublishMetrics(
- TelemetryDataConstants.TestDiscoveryCompleteEvent,
- requestData.MetricsCollection.Metrics!);
+ try
+ {
+ EqtTrace.Info("TestRequestManager.DiscoverTests: Synchronization context taken");
+
+ _currentDiscoveryRequest = _testPlatform.CreateDiscoveryRequest(
+ requestData,
+ criteria,
+ discoveryPayload.TestPlatformOptions,
+ sourceToSourceDetailMap,
+ new EventRegistrarToWarningLoggerAdapter(discoveryEventsRegistrar));
+ // Discovery started, allow cancellations to proceed.
+ _currentDiscoveryRequest.OnDiscoveryStart += (s, e) => _discoveryStarting.Set();
+ discoveryEventsRegistrar?.RegisterDiscoveryEvents(_currentDiscoveryRequest);
+
+ // Notify start of discovery start.
+ _testPlatformEventSource.DiscoveryRequestStart();
+
+ // Start the discovery of tests and wait for completion.
+ _currentDiscoveryRequest.DiscoverAsync();
+ _currentDiscoveryRequest.WaitForCompletion();
+ }
+ finally
+ {
+ if (_currentDiscoveryRequest != null)
+ {
+ // Dispose the discovery request and unregister for events.
+ discoveryEventsRegistrar?.UnregisterDiscoveryEvents(_currentDiscoveryRequest);
+ _currentDiscoveryRequest.Dispose();
+ _currentDiscoveryRequest = null;
+ }
+
+ EqtTrace.Info("TestRequestManager.DiscoverTests: Discovery tests completed.");
+ _testPlatformEventSource.DiscoveryRequestStop();
+
+ // Posts the discovery complete event.
+ _metricsPublisher.Result.PublishMetrics(
+ TelemetryDataConstants.TestDiscoveryCompleteEvent,
+ requestData.MetricsCollection.Metrics!);
+ }
}
}
+ finally
+ {
+ _discoveryStarting.Set();
+ }
}
///
@@ -624,6 +644,12 @@ public void CancelTestRun()
public void CancelDiscovery()
{
EqtTrace.Info("TestRequestManager.CancelDiscovery: Sending cancel request.");
+
+ // Wait for discovery request to initialize, before cancelling it, otherwise the
+ // _currentDiscoveryRequest might be null, because discovery did not have enough time to
+ // initialize and did not manage to populate _currentDiscoveryRequest yet, leading to hanging run
+ // that "ignores" the cancellation.
+ _discoveryStarting.WaitOne(3000);
_currentDiscoveryRequest?.Abort();
}