Skip to content
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

Merged
merged 20 commits into from
Apr 23, 2018

Conversation

abhishkk
Copy link
Contributor

@abhishkk abhishkk commented Apr 16, 2018

Issue:
#1540

Changes done:

  1. If cancellation/abort happens before custom host launch, TestPlatform will cancel waiting for IDE. In this case, IDE will get an error level message that Could not connect to test host process.
  2. Cancel and abort request will not keep on waiting on same lock as of execution request, so as to respect cancel/abort quickly.
  3. On cancellation, data collector will not send any attachments (that happens today also) and quickly cancel.
  4. Methods are updated in ITestRequestSender API so that we can cancel waiting for test host to get connected to vstest.console.
  5. With every request sent by console to test host, now has a pre-check if cancellation requested or not.
  6. AfterTestRunEnd request to data collectors will be called only once when test run completes. (Currently in case of cancellation, we were sending AfterTestRunEnd 2 times, once just as soon as cancellation comes and second when test run completes.)
  7. ITestHostLauncher API now has one more overriden LaunchTestHost method which takes CancellationToken. Any custom runtime provider needs to call this overriden method to respect cancellation request.
  8. TestRequestManager is no longer holding cancel and abort request. Till now they were holding cancel/abort request for five seconds in case execution request is active.

@AbhitejJohn Please verify IDE side changes considering above changes. I have done validations from my end. Let me know if there are any issues.

@mayankbansal018 Regarding # 7, do we need make any changes in UWP runtime provider to respect cancellation?

@@ -383,7 +383,7 @@ private bool LaunchHost(TestProcessStartInfo testHostStartInfo, CancellationToke
}
else
{
int processId = this.customTestHostLauncher.LaunchTestHost(testHostStartInfo);
int processId = this.customTestHostLauncher.LaunchTestHost(testHostStartInfo, cancellationToken);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are breaking the API here. Is customTestHostLauncher used by others also? Interface ITesHostLauncher is in ObjectModel.
If its used by others, we need to introduce something called IsCancellable in ITestHostLauncher which will be used here to switch between overriden calls of customTestHostLauncher.LaunchTestHost.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will it be used in UWP sceanrio ..?


In reply to: 181616862 [](ancestors = 181616862)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I confirmed from Mayank. It will not be breaking change.

@@ -5,6 +5,7 @@ namespace Microsoft.VisualStudio.TestPlatform.Client.DesignMode
{
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces;
using System.Threading;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Threading [](start = 17, length = 9)

nitpick: sort using..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -265,7 +275,7 @@ public void Abort()
{
EqtTrace.Verbose("TestRunRequest.Abort: Aborting.");

lock (this.syncObject)
lock (this.abortSyncObject)
Copy link
Contributor

@cltshivash cltshivash Apr 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

abortSyncObject [](start = 23, length = 15)

isn't the usage of the sync object to synchronize the access to the executionmanager ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially reuse the same object for cancel and abort then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isCancelled [](start = 49, length = 11)

won't we fail to receive data (testruncomplete )

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

@@ -260,13 +270,19 @@ public void InitializeExecution(IEnumerable<string> pathToAdditionalExtensions)

/// <inheritdoc />
public void StartTestRun(TestRunCriteriaWithSources runCriteria, ITestRunEventsHandler eventHandler)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StartTestRun [](start = 20, length = 12)

why do we need two signatures..? TP is an internal interface..?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

/// <param name="runCriteria">RunCriteria for test run</param>
/// <param name="eventHandler">EventHandler for test run events</param>
/// <param name="cancellationToken">Cancellation token</param>
void StartTestRun(TestRunCriteriaWithTests runCriteria, ITestRunEventsHandler eventHandler, CancellationToken cancellationToken);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StartTestRun [](start = 13, length = 12)

same question.. is this a public interface ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Modified existing methods.

Copy link
Contributor

@cltshivash cltshivash left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕐

@abhishkk abhishkk self-assigned this Apr 18, 2018
@@ -383,7 +383,7 @@ private bool LaunchHost(TestProcessStartInfo testHostStartInfo, CancellationToke
}
else
{
int processId = this.customTestHostLauncher.LaunchTestHost(testHostStartInfo);
int processId = this.customTestHostLauncher.LaunchTestHost(testHostStartInfo, cancellationToken);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add this is dotnet runtime

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor

@mayankbansal018 mayankbansal018 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@@ -250,6 +253,9 @@ public int LaunchCustomHost(TestProcessStartInfo testProcessStartInfo)
waitHandle.Set();
};

// Registering cancellationToken to set waitHandle (whenever request is cancelled).
var cancellationTokenRegistration = cancellationToken.Register(() => waitHandle.Set());
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, should this be the first condition then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)

Copy link
Contributor

Choose a reason for hiding this comment

The 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...

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

@@ -184,6 +189,24 @@ public virtual bool SetupChannel(IEnumerable<string> sources, CancellationToken
return true;
}

private bool LaunchTestHostAsync(TestProcessStartInfo testHostStartInfo, CancellationToken token)
{
this.CancellationTokenSource.Token.ThrowIfCancellationRequested();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be called out in the protocol doc? Looks like a cancel is going to be an abrupt stop to a request no matter where in the sequence we are currently.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are cancelling abruptly only before the test execution is actually started and then sending HandleTestRunComplete.
Once test execution is started by test host, we send cancel request to test host and which passes the cancel request to adapters.
@cltshivash @singhsarab Should this be documented?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, got that but most clients would not expect a TestRunComplete when negotiating protocol unless told otherwise.... Was a separate message for cancellation considered for scenarios like these? Might be a bigger change but worth a fluid protocol.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestRequestManager today waits for completion in all scenarios. I will check if this is already documented and if not, will discuss with the team and document it.

/// <param name="defaultTestHostStartInfo">Default TestHost Process Info</param>
/// <param name="cancellationToken">The cancellation Token.</param>
/// <returns>Process id of the launched test host</returns>
int LaunchTestHost(TestProcessStartInfo defaultTestHostStartInfo, CancellationToken cancellationToken);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will require a corresponding change in TPV1 OM. TE implements this for debugging which would need a change as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface is used by run time provider only for launching the custom host via custom host launcher.
Why changes are required in TPV1 OM?

Even though if TE uses it as an interface for their internal purpose, there will no breaking change here (as TPv1 OM is used both while build and run time. TPV2 OM is not loaded.) Please correct me if i am missing something here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fare, but shouldn't we be consistent? Bare in mind that the Console wrapper and interfaces assemblies are loaded in VS and this could result in inadvertent runtime failures.

Copy link
Contributor Author

@abhishkk abhishkk Apr 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cltshivash @singhsarab @AbhitejJohn
Am I missing something here? AFIAK ITestHostLauncher is meant only for runtime provider and it is loaded in vstest console only. Neither translation layer, nor test host or any other process from TP side uses this except vstest.console.

If TE is using this interface for their internal purpose, then it is using TPV1 OM and loading TPV1 OM. So how the behavior is inconsistent? I don't feel comfortable changing TPV1 OM unless it's a breaking change.

@mayankbansal018
Copy link
Contributor

Do validate that UWP does work with these changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants