-
Notifications
You must be signed in to change notification settings - Fork 323
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
Cancel/abort not honored when sent before custom host launch #1543
Changes from 6 commits
1f41e27
1b09512
60b74d8
51bcc10
c3dc452
7674f35
eb079c6
046c86a
e206964
653047a
3015b3e
4513b14
3a77114
07f8abd
b11ac54
145ef38
93923a2
10fa006
24bd933
08975c6
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 |
---|---|---|
|
@@ -41,6 +41,16 @@ public class TestRunRequest : ITestRunRequest, ITestRunEventsHandler | |
/// </summary> | ||
private object syncObject = new Object(); | ||
|
||
/// <summary> | ||
/// Sync object for cancel operation | ||
/// </summary> | ||
private object cancelSyncObject = new Object(); | ||
|
||
/// <summary> | ||
/// Sync object for abort operation | ||
/// </summary> | ||
private object abortSyncObject = new Object(); | ||
|
||
/// <summary> | ||
/// The run completion event which will be signalled on completion of test run. | ||
/// </summary> | ||
|
@@ -236,7 +246,7 @@ public void CancelAsync() | |
{ | ||
EqtTrace.Verbose("TestRunRequest.CancelAsync: Canceling."); | ||
|
||
lock (this.syncObject) | ||
lock (this.cancelSyncObject) | ||
{ | ||
if (this.disposed) | ||
{ | ||
|
@@ -265,7 +275,7 @@ public void Abort() | |
{ | ||
EqtTrace.Verbose("TestRunRequest.Abort: Aborting."); | ||
|
||
lock (this.syncObject) | ||
lock (this.abortSyncObject) | ||
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.
isn't the usage of the sync object to synchronize the access to the executionmanager ? 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. All execution related requests, like ExecuteAsync, HandleTestRunComplete, HandleTestRunStatsChange, HandleLogMessage are under same sync object. But we want to respect cancel, abort and don't want to wait if some of the above request are going on. Thus separate lock objects for them 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. Potentially reuse the same object for cancel and abort then? 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. done. |
||
{ | ||
if (this.disposed) | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -160,7 +160,7 @@ public Collection<AttachmentSet> SendAfterTestRunStartAndGetResult(ITestMessageE | |
|
||
// Cycle through the messages that the datacollector sends. | ||
// Currently each of the operations are not separate tasks since they should not each take much time. This is just a notification. | ||
while (!isDataCollectionComplete) | ||
while (!isDataCollectionComplete && !isCancelled) | ||
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.
won't we fail to receive data (testruncomplete ) 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. In case of cancellation and abort, we dont get any attachments from data collectors and simply cancels the run. So adding isCancelled will do the same thing. For non-cancelled run, we will be able to successfully able to receive data |
||
{ | ||
var message = this.communicationManager.ReceiveMessage(); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces | |
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
|
||
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; | ||
|
@@ -29,8 +30,9 @@ public interface ITestRequestSender : IDisposable | |
/// Waits for Request Handler to be connected | ||
/// </summary> | ||
/// <param name="connectionTimeout">Time to wait for connection</param> | ||
/// <param name="cancellationToken">Cancellation token</param> | ||
/// <returns>True, if Handler is connected</returns> | ||
bool WaitForRequestHandlerConnection(int connectionTimeout); | ||
bool WaitForRequestHandlerConnection(int connectionTimeout, CancellationToken cancellationToken); | ||
|
||
/// <summary> | ||
/// Close the Sender | ||
|
@@ -61,14 +63,16 @@ public interface ITestRequestSender : IDisposable | |
/// </summary> | ||
/// <param name="runCriteria">RunCriteria for test run</param> | ||
/// <param name="eventHandler">EventHandler for test run events</param> | ||
void StartTestRun(TestRunCriteriaWithSources runCriteria, ITestRunEventsHandler eventHandler); | ||
/// <param name="cancellationToken">Cancellation token</param> | ||
void StartTestRun(TestRunCriteriaWithSources runCriteria, ITestRunEventsHandler eventHandler, CancellationToken cancellationToken); | ||
|
||
/// <summary> | ||
/// Starts the TestRun with given test cases and criteria | ||
/// </summary> | ||
/// <param name="runCriteria">RunCriteria for test run</param> | ||
/// <param name="eventHandler">EventHandler for test run events</param> | ||
void StartTestRun(TestRunCriteriaWithTests runCriteria, ITestRunEventsHandler eventHandler); | ||
/// <param name="cancellationToken">Cancellation token</param> | ||
void StartTestRun(TestRunCriteriaWithTests runCriteria, ITestRunEventsHandler eventHandler, CancellationToken cancellationToken); | ||
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.
same question.. is this a public interface ? 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. Done. Modified existing methods. |
||
|
||
/// <summary> | ||
/// Ends the Session | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -135,14 +135,18 @@ public int InitializeCommunication() | |
} | ||
|
||
/// <inheritdoc /> | ||
public bool WaitForRequestHandlerConnection(int connectionTimeout) | ||
public bool WaitForRequestHandlerConnection(int connectionTimeout, CancellationToken cancellationToken) | ||
{ | ||
var cancellationTokenRegistration = cancellationToken.Register(() => this.connected.Set()); | ||
if (EqtTrace.IsVerboseEnabled) | ||
{ | ||
EqtTrace.Verbose("TestRequestSender.WaitForRequestHandlerConnection: waiting for connection with timeout: {0}", connectionTimeout); | ||
} | ||
|
||
return this.connected.Wait(connectionTimeout); | ||
var waitSuccess = this.connected.Wait(connectionTimeout) && !cancellationToken.IsCancellationRequested; | ||
cancellationTokenRegistration.Dispose(); | ||
|
||
return waitSuccess; | ||
} | ||
|
||
/// <inheritdoc /> | ||
|
@@ -259,14 +263,14 @@ public void InitializeExecution(IEnumerable<string> pathToAdditionalExtensions) | |
} | ||
|
||
/// <inheritdoc /> | ||
public void StartTestRun(TestRunCriteriaWithSources runCriteria, ITestRunEventsHandler eventHandler) | ||
public void StartTestRun(TestRunCriteriaWithSources runCriteria, ITestRunEventsHandler eventHandler, CancellationToken cancellationToken) | ||
{ | ||
this.messageEventHandler = eventHandler; | ||
this.onDisconnected = (disconnectedEventArgs) => | ||
{ | ||
this.OnTestRunAbort(eventHandler, disconnectedEventArgs.Error, true); | ||
this.OnTestRunAbort(eventHandler, disconnectedEventArgs.Error, true, cancellationToken); | ||
}; | ||
this.onMessageReceived = (sender, args) => this.OnExecutionMessageReceived(sender, args, eventHandler); | ||
this.onMessageReceived = (sender, args) => this.OnExecutionMessageReceived(sender, args, eventHandler, cancellationToken); | ||
this.channel.MessageReceived += this.onMessageReceived; | ||
|
||
var message = this.dataSerializer.SerializePayload( | ||
|
@@ -283,14 +287,14 @@ public void StartTestRun(TestRunCriteriaWithSources runCriteria, ITestRunEventsH | |
} | ||
|
||
/// <inheritdoc /> | ||
public void StartTestRun(TestRunCriteriaWithTests runCriteria, ITestRunEventsHandler eventHandler) | ||
public void StartTestRun(TestRunCriteriaWithTests runCriteria, ITestRunEventsHandler eventHandler, CancellationToken cancellationToken) | ||
{ | ||
this.messageEventHandler = eventHandler; | ||
this.onDisconnected = (disconnectedEventArgs) => | ||
{ | ||
this.OnTestRunAbort(eventHandler, disconnectedEventArgs.Error, true); | ||
this.OnTestRunAbort(eventHandler, disconnectedEventArgs.Error, true, cancellationToken); | ||
}; | ||
this.onMessageReceived = (sender, args) => this.OnExecutionMessageReceived(sender, args, eventHandler); | ||
this.onMessageReceived = (sender, args) => this.OnExecutionMessageReceived(sender, args, eventHandler, cancellationToken); | ||
this.channel.MessageReceived += this.onMessageReceived; | ||
|
||
var message = this.dataSerializer.SerializePayload( | ||
|
@@ -379,7 +383,7 @@ public void Dispose() | |
this.communicationEndpoint.Stop(); | ||
} | ||
|
||
private void OnExecutionMessageReceived(object sender, MessageReceivedEventArgs messageReceived, ITestRunEventsHandler testRunEventsHandler) | ||
private void OnExecutionMessageReceived(object sender, MessageReceivedEventArgs messageReceived, ITestRunEventsHandler testRunEventsHandler, CancellationToken cancellationToken) | ||
{ | ||
try | ||
{ | ||
|
@@ -434,7 +438,7 @@ private void OnExecutionMessageReceived(object sender, MessageReceivedEventArgs | |
} | ||
catch (Exception exception) | ||
{ | ||
this.OnTestRunAbort(testRunEventsHandler, exception, false); | ||
this.OnTestRunAbort(testRunEventsHandler, exception, false, cancellationToken); | ||
} | ||
} | ||
|
||
|
@@ -485,7 +489,7 @@ private void OnDiscoveryMessageReceived(ITestDiscoveryEventsHandler2 discoveryEv | |
} | ||
} | ||
|
||
private void OnTestRunAbort(ITestRunEventsHandler testRunEventsHandler, Exception exception, bool getClientError) | ||
private void OnTestRunAbort(ITestRunEventsHandler testRunEventsHandler, Exception exception, bool getClientError, CancellationToken cancellationToken) | ||
{ | ||
if (this.IsOperationComplete()) | ||
{ | ||
|
@@ -496,7 +500,7 @@ private void OnTestRunAbort(ITestRunEventsHandler testRunEventsHandler, Exceptio | |
EqtTrace.Verbose("TestRequestSender: OnTestRunAbort: Set operation complete."); | ||
this.SetOperationComplete(); | ||
|
||
var reason = this.GetAbortErrorMessage(exception, getClientError); | ||
var reason = this.GetAbortErrorMessage(exception, getClientError, cancellationToken); | ||
EqtTrace.Error("TestRequestSender: Aborting test run because {0}", reason); | ||
this.LogErrorMessage(string.Format(CommonResources.AbortedTestRun, reason)); | ||
|
||
|
@@ -522,7 +526,7 @@ private void OnDiscoveryAbort(ITestDiscoveryEventsHandler2 eventHandler, Excepti | |
this.SetOperationComplete(); | ||
|
||
var discoveryCompleteEventArgs = new DiscoveryCompleteEventArgs(-1, true); | ||
var reason = this.GetAbortErrorMessage(exception, getClientError); | ||
var reason = this.GetAbortErrorMessage(exception, getClientError, CancellationToken.None); | ||
EqtTrace.Error("TestRequestSender: Aborting test discovery because {0}", reason); | ||
this.LogErrorMessage(string.Format(CommonResources.AbortedTestDiscovery, reason)); | ||
|
||
|
@@ -540,8 +544,9 @@ private void OnDiscoveryAbort(ITestDiscoveryEventsHandler2 eventHandler, Excepti | |
eventHandler.HandleDiscoveryComplete(discoveryCompleteEventArgs, null); | ||
} | ||
|
||
private string GetAbortErrorMessage(Exception exception, bool getClientError) | ||
private string GetAbortErrorMessage(Exception exception, bool getClientError, CancellationToken cancellationToken) | ||
{ | ||
var cancellationTokenRegistration = cancellationToken.Register(() => this.clientExited.Set()); | ||
EqtTrace.Verbose("TestRequestSender: GetAbortErrorMessage: Exception: " + exception); | ||
|
||
// It is also possible for an operation to abort even if client has not | ||
|
@@ -554,15 +559,18 @@ private string GetAbortErrorMessage(Exception exception, bool getClientError) | |
|
||
// Set a default message and wait for test host to exit for a moment | ||
reason = CommonResources.UnableToCommunicateToTestHost; | ||
if (this.clientExited.Wait(this.clientExitedWaitTime)) | ||
if (this.clientExited.Wait(this.clientExitedWaitTime) && !cancellationToken.IsCancellationRequested) | ||
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. hmm, should this be the first condition then? 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. No. If cancellation flag is checked first and it returns false, and then while waiting cancellation occurs, then waitHandle will be set because of cancellation and thus enter in if condition (which is wrong as wait is set by cancel) 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. I was thinking the other way round where we actually end up waiting for a while, figure out its cancelled and head to the else part which seems like a wasted wait... 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. We are already doing that in this PR. Earlier I did a CancellationToken.Register(() => this.clientExited) which I latter changed to this.clientExited.Wait(this.clientExitedWaitTime, CancellationToken) on your recommendation. In either case, clientExited will return immediately if cancellation happened. Let me know if i am understanding it incorrectly. |
||
{ | ||
EqtTrace.Info("TestRequestSender: GetAbortErrorMessage: Received test host error message."); | ||
reason = this.clientExitErrorMessage; | ||
} | ||
|
||
EqtTrace.Info("TestRequestSender: GetAbortErrorMessage: Timed out waiting for test host error message."); | ||
else | ||
{ | ||
EqtTrace.Info("TestRequestSender: GetAbortErrorMessage: Timed out waiting for test host error message."); | ||
} | ||
} | ||
|
||
cancellationTokenRegistration.Dispose(); | ||
return reason; | ||
} | ||
|
||
|
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.
nit: would something like this be more direct? Msdn usage here
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.
done.