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

Co-operatively cancel all RPC Calls #576

Merged

Conversation

nathanwoctopusdeploy
Copy link
Contributor

@nathanwoctopusdeploy nathanwoctopusdeploy commented Dec 5, 2023

This is a combination of the reverted PRs #525 and #558 combined and rebased

Background

[sc-58429]

Before Async Halibut was introduced, Halibut only supported cancellation of an RPC call when the request was in the connecting state, queued for a Polling Request and the TCP Connection being established for a Listening Request. Once the RPC was in flight, dequeued for a Polling Request, and the connection established for a Listening Request, cancellation was not supported.

As the cancellation of an in-flight request was not supported, a caller would only have the option to walk away from an RPC call and leave threads and IO running.

When Async Halibut was introduced, cooperative cancellation was introduced down to the socket for RPC calls to Listening Service and to the queue for Polling Services. Cancellation could be performed if the RPC call was connecting and in-flight. So as not to force clients to change there existing behaviour, two cancellation tokens were introduced, one to cancel when connecting and one to cancel when in-flight.

Results

This PR extends the previous cancellation support so that an in-flight Polling Request can be cancelled to the Socket. Terminating any in-flight Reads or Writes on the TCP Connection.

The two cancellation tokens have been combined into one to simplify the cancellation logic. The ability to cancel just when connecting is no longer required in TentacleClient. Instead, the desire is to co-operatively cancel an RPC call regardless of it's current state.

Tentacle Client does need to know if the cancelled RPC call was connecting or in flight to make the most efficient decisions about what it has to do next, so cancellation will throw specific errors based on the state of the RPC call when it was cancelled.

net48 does not support full cancellation due to the usage of a deflate stream, which uses APM Begin and End methods and loses the cancellation token. As TentacleClient only supports net6.0, this is not an issue.

How to review this PR

Quality ✔️

Pre-requisites

  • I have read How we use GitHub Issues for help deciding when and where it's appropriate to make an issue.
  • I have considered informing or consulting the right people, according to the ownership map.
  • I have considered appropriate testing for my change.

@nathanwoctopusdeploy nathanwoctopusdeploy changed the base branch from main to sast/test-exception-contracts December 5, 2023 11:05
cancellationTokenSource.Cancel();
await task;
})).And.Message.Should().Contain($"An error occurred when sending a request to 'https://20.5.79.31:10933/', after the request began: The operation was canceled.");
})).And.Message.Should().Contain($"An error occurred when sending a request to 'https://20.5.79.31:10933/', after the request began: The Request was cancelled while Connecting.");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exception contract change - The error message changes

(await AssertException.Throws<HalibutClientException>(async () => await client.SayHelloAsync("Hello", new(cancellationTokenSource.Token, CancellationToken.None))))
.And.Message.Should().Contain("An error occurred when sending a request to 'https://20.5.79.31:10933/', after the request began: The operation was canceled.");
(await AssertException.Throws<HalibutClientException>(async () => await client.SayHelloAsync("Hello", new(cancellationTokenSource.Token))))
.And.Message.Should().Contain("An error occurred when sending a request to 'https://20.5.79.31:10933/', after the request began: The Request was cancelled while Connecting.");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exception contract change - The error message changes

exception.Message.Should().Be($"An error occurred when sending a request to '{clientAndService.ServiceUri}', after the request began: The ReadAsync operation was cancelled.");
#endif
var exception = (await AssertException.Throws<HalibutClientException>(async () => await client.SayHelloAsync("Hello", new(cancellationTokenSource.Token)))).And;
exception.Message.Should().Be($"An error occurred when sending a request to '{clientAndService.ServiceUri}', after the request began: The Request was cancelled while Connecting.");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exception contract change - The error message changes

(await AssertException.Throws<HalibutClientException>(async () => await doSomeActionClient.ActionAsync(new(cancellationTokenSource.Token, cancellationTokenSource.Token))))
.And.Message.Should().Contain($"An error occurred when sending a request to '{clientAndService.ServiceUri}', after the request began: The ReadAsync operation was cancelled.");
(await AssertException.Throws<HalibutClientException>(async () => await doSomeActionClient.ActionAsync(new(cancellationTokenSource.Token))))
.And.Message.Should().Contain($"An error occurred when sending a request to '{clientAndService.ServiceUri}', after the request began: The Request was cancelled while Transferring.");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exception contract change - The error message changes

@@ -240,15 +240,8 @@ public async Task WhenTheListeningRequestIsCancelledWhileConnecting_AndTheConnec

var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5));

#if NETFRAMEWORK
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice that we could get rid of this 😄

Copy link
Contributor

@sburmanoctopus sburmanoctopus left a comment

Choose a reason for hiding this comment

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

LGTM. Nice to see this make its way back again 😁

@nathanwoctopusdeploy nathanwoctopusdeploy marked this pull request as ready for review December 6, 2023 02:39
@nathanwoctopusdeploy nathanwoctopusdeploy requested a review from a team as a code owner December 6, 2023 02:39
Base automatically changed from sast/test-exception-contracts to main December 6, 2023 04:23
namespace Halibut.Tests.Support.PendingRequestQueueFactories
{
/// <summary>
/// CancelWhenRequestDequeuedPendingRequestQueueFactory cancels the cancellation token source when a request is queued
Copy link
Contributor

Choose a reason for hiding this comment

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

when request is dequeued?

@nathanwoctopusdeploy nathanwoctopusdeploy force-pushed the sast/cancel-polling-requests-to-the-socket branch from 8c406ed to 0274644 Compare December 6, 2023 05:01
Combine connecting and in progress cancellation tokens
@nathanwoctopusdeploy nathanwoctopusdeploy force-pushed the sast/cancel-polling-requests-to-the-socket branch from 0274644 to 0dd6357 Compare December 7, 2023 02:44
@nathanwoctopusdeploy nathanwoctopusdeploy merged commit ac3deb7 into main Dec 11, 2023
1 check passed
@nathanwoctopusdeploy nathanwoctopusdeploy deleted the sast/cancel-polling-requests-to-the-socket branch December 11, 2023 01:06
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.

3 participants