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

Optimise the usage of CancellationTokens #481

Merged
merged 2 commits into from
Aug 30, 2023

Conversation

nathanwoctopusdeploy
Copy link
Contributor

@nathanwoctopusdeploy nathanwoctopusdeploy commented Aug 29, 2023

Background

This PR has a few changes around the way cancellation tokens are used to reduce some undesirable memory growth and object retention observed in Async Halibut when running at scale.

The combination of these fixes seems to have reduced the observed memory growth, with the GC seen to be working more efficiently and has removed some suspect retention of object around Sockets.

Please see the comments for details of the individual changes.

Related to OctopusDeploy/Issues#8266

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.


public CancellationToken LinkedCancellationToken => LazyLinkedCancellationToken.Value;

Lazy<CancellationToken> LazyLinkedCancellationToken => new (() =>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed the Lazy as the LinkedCancellationToken is always referenced so the Lazy is always created.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reason: Simplification to remove any possibility of retention due to the referenced objects of the lazy

return CancellationToken.None;
LinkedCancellationToken = CancellationToken.None;
}
else if (InProgressRequestCancellationToken == CancellationToken.None)
Copy link
Contributor Author

@nathanwoctopusdeploy nathanwoctopusdeploy Aug 29, 2023

Choose a reason for hiding this comment

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

Optimisations to not wrap a single cancelable cancellation token in a new cancellation token source

@@ -38,7 +38,8 @@ public class SecureListener : IDisposable
readonly ILogFactory logFactory;
readonly Func<string> getFriendlyHtmlPageContent;
readonly Func<Dictionary<string, string>> getFriendlyHtmlPageHeaders;
readonly CancellationTokenSource cts = new();
readonly CancellationTokenSource cts;
readonly CancellationToken cancellationToken;
Copy link
Contributor Author

@nathanwoctopusdeploy nathanwoctopusdeploy Aug 29, 2023

Choose a reason for hiding this comment

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

Usage of a single cancellation token to avoid creating new cancellation tokens each time they are needed from the source. It's a simple pass-through object but we create a lot of them

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh so the CTS creates a new token each time it is asked for a token?

Copy link
Contributor Author

@nathanwoctopusdeploy nathanwoctopusdeploy Aug 29, 2023

Choose a reason for hiding this comment

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

Yes, it's a very lightweight struct that calls into the CancellationTokenSource, but it's another object with another reference.

{
// The stream is wrapped in a NetworkTimeoutStream which handles closing the inner stream on timeout
Copy link
Contributor Author

@nathanwoctopusdeploy nathanwoctopusdeploy Aug 29, 2023

Choose a reason for hiding this comment

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

For Async Halibut we don't need this weak ref and disposal as we wrap all Async Halibut stream operations so they cancel / timeout / close or dispose

Copy link
Contributor

Choose a reason for hiding this comment

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

I had understood the cancellationToken is triggered in the dispose and this was a way to kill any streams when the secure listener is disposed.

Does this mean if the secure listener is disposed the accepted streams are not disposed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@LukeButters TcpClientManager is now Disposable and the SecureListener will Dispose of it

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice thanks! I think the result is simpler to follow code.

var actionTask = action(linkedCancellationTokenSource.Token);
var cancellationTask = linkedCancellationTokenSource.Token.AsTask<T>();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removing the usage of .AsTask which does a .Register on a cancellation token in favour of a simpler flow using Task.Delay. The main driver is to remove the .Register


namespace Halibut.Util
{
internal static class CancellationTokenExtensionMethods
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 longer needed

@nathanwoctopusdeploy nathanwoctopusdeploy enabled auto-merge (squash) August 30, 2023 03:47
@nathanwoctopusdeploy nathanwoctopusdeploy merged commit 01bb7e8 into main Aug 30, 2023
15 of 17 checks passed
@nathanwoctopusdeploy nathanwoctopusdeploy deleted the sast/cancellation-token-usage-changes branch August 30, 2023 03:49
}
}

public CancellationToken ConnectingCancellationToken { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

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

A bit late to the party, but why are these two property setters public? Why not just read only properties?

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.

None yet

3 participants