-
Notifications
You must be signed in to change notification settings - Fork 268
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
Cancellation2 #5288
base: main
Are you sure you want to change the base?
Cancellation2 #5288
Changes from all commits
024c3d8
0e7051c
a4afd44
c422b39
77355ac
3ed9e8f
f90b912
1f6e707
f50e794
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 |
---|---|---|
|
@@ -58,7 +58,9 @@ public async Task ExecuteAsync(ITestFramework testFramework, ClientInfo client, | |
IMessageBus messageBus = ServiceProvider.GetMessageBus(); | ||
|
||
// Execute the test request | ||
await ExecuteRequestAsync(testFramework, request, messageBus, cancellationToken); | ||
var taskCompletionSource = new TaskCompletionSource(); | ||
using CancellationTokenRegistration r = cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()); | ||
await Task.WhenAny(ExecuteRequestAsync(testFramework, request, messageBus, cancellationToken), taskCompletionSource.Task); | ||
|
||
CloseTestSessionResult closeTestSessionResult = await testFramework.CloseTestSessionAsync(new(sessionId, client, cancellationToken)); | ||
await HandleTestSessionResultAsync(closeTestSessionResult.IsSuccess, closeTestSessionResult.WarningMessage, closeTestSessionResult.ErrorMessage); | ||
|
@@ -68,6 +70,8 @@ public async Task ExecuteAsync(ITestFramework testFramework, ClientInfo client, | |
|
||
public virtual async Task ExecuteRequestAsync(ITestFramework testFramework, TestExecutionRequest request, IMessageBus messageBus, CancellationToken cancellationToken) | ||
{ | ||
await Task.Yield(); | ||
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. here we ensure that this method will run asynchronously, so we can handle the Task.WhenAny above in that call stack. |
||
|
||
using SemaphoreSlim requestSemaphore = new(0, 1); | ||
await testFramework.ExecuteRequestAsync(new(request, messageBus, new SemaphoreSlimRequestCompleteNotifier(requestSemaphore), 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. Not sure why the semaphore slim is needed here, instead of just completing and awaiting the task. The semaphore is passed in via context and test frameworks are supposed to call Complete on it. 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. https://github.com/thomhurst/TUnit/blob/main/TUnit.Engine/Framework/TUnitTestFramework.cs#L170 this code does not do it, but in theory the ExecuteRequestAsync can complete and pass the context to something else, which will then call Complete when done. Seems rather equivalent to just awaiting the task. Because we will still await some completion. 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. Unless there is something I don't see, I think it could be reasonable to consider changing that, but probably in v2 as |
||
await requestSemaphore.WaitAsync(cancellationToken); | ||
|
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.
Here we ensure that the request run can be abandoned without completing when canelation token is cancelled (this token is test session cancellation token).
This will give the test framework a chance to run CloseTestSessionAsync https://github.com/microsoft/testfx/blob/main/src/Platform/Microsoft.Testing.Platform/TestFramework/ITestFramework.cs
which seems to align with the intent of the interface and the token.
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.
That makes it pointless to pass a cancellation token to framework authors right? Because now we will force-cancel on our side.
I'd expect framework authors to handle it. So, if we want to change something, I'd change in MSTest rather than MTP
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.
And thinking more, this is too risky. If a cancellation is requested, we may be proceeding to CloseTestSessionAsync while ExecuteRequestAsync is still running right? We just bypassed it by the WhenAny but it's still there and running
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.
I don't think it is pointless. We pass the same token within the whole test session via Services.TestSessionContext. This cancellation token is called abortRun.
The test framework has 3 callbacks, before test session, execute request, and after test session.
In TestHostFrameworkInvoker we pass the abortTestRun to all those 3 callbacks. And before each operation we check if we should do more work, or if cancellation was requested.
If it was requested, we abort, if not we continue.
Problem is when long running work starts in the testFramework.ExecuteRequestAsync, we don't have a way out, which is what this PR implements.
When executeRequestAsync is running long synchronous work, we cancel the token, we will ignore that running work and go directly to test session teardown that is provided by the framework.
Once the single item of work finishes in testframework.executerequestasync, no more work should be started because framework author should check for cancellation before starting any work. This is very similar to what we do internally in mstest when timeout for test is specified and the test times out (IMHO).
The risky point is what this PR is trying to achieve, so we don't have to rely on framework authors to not block the platform from completing. This is non-graceful teardown of the framework.
To handle this predictably, there needs to be a whole other layer (or two).
The testframework.ExecuteRequest should get a separate cancelation token that will cancel the run, and there should be a different gesture for Cancel of test run (which is more graceful) and Abort (which is less graceful but still not as aggressive as killing the process), which is what we now call Cancel.