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

Update our 'delay until visible' logic to not throw cancellation exceptions #68382

Merged
merged 13 commits into from
Jun 9, 2023

Conversation

CyrusNajmabadi
Copy link
Member

This is a small change (as part of a large set of work) to address taggers causing stress on the system due to excessive exceptions and lock contention (specifically on large core count machines).

The issue being addressed here is that taggers will constantly gets a stream of events coming in saying "something changed, update tags in the near future". The tagger then says "ok, but if i'm not visible, i'm going to delay that work till much later". This delaying is good for preventing unnecessary CPU work actually computing the tags, but is bad in that it is causing excess cancellation exceptions to be thrown. Specifically, the tagger puts itself to sleep when non-visible, but then gets canceled (throwing an exception) when the next event comes in. This "event-cancel-sleep, event-cancel-sleep, ..." can happen repeatedly, across all taggers (we have many).

These exceptions are not cheap themselves. For example, they take locks here to copy exception stack information: https://referencesource.microsoft.com/#mscorlib/system/exception.cs,a445c4e8ae46b283

While there is work already happening at the compiler layer to lighten this (by not always throwing), we can make things better in the interim by having our own code no throw here, but instead gracefully check for this, and pop up the async stack simply.

@CyrusNajmabadi CyrusNajmabadi requested a review from sharwell May 30, 2023 21:48
@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner May 30, 2023 21:48
@dotnet-issue-labeler dotnet-issue-labeler bot added Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead labels May 30, 2023
@@ -51,6 +55,9 @@ internal static class ITextBufferVisibilityTrackerExtensions
if (service is null)
return;

if (cancellationToken.IsCancellationRequested)
return;

await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
Copy link
Member

Choose a reason for hiding this comment

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

❗ Cancellation here (during the switch) will result in an exception

Copy link
Member Author

Choose a reason for hiding this comment

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

good call.

Copy link
Member Author

Choose a reason for hiding this comment

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

unfortunately, no NoThrowAwaitable for MainThreadAwaitable. Hopefully the chance of getting the CT just in the switch is low. (seems like the majority case would be where we're actually delay'ing and an event comes in in that time).

Copy link
Member

Choose a reason for hiding this comment

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

I've been discussing with Andrew a new GetResult(out Exception) method that would support this case and also support the feature from #67487.

@CyrusNajmabadi CyrusNajmabadi requested a review from sharwell May 30, 2023 22:07
@CyrusNajmabadi CyrusNajmabadi requested a review from sharwell May 31, 2023 16:51
@CyrusNajmabadi
Copy link
Member Author

@sharwell ptal.

var callback = void () => visibilityChangedTaskSource.TrySetResult(true);
service.RegisterForVisibilityChanges(subjectBuffer, callback);

try
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume utilizing CancellationToken.Register and doing something like:

cancellationToken.Register(callback);

and then sending in CancellationToken.None to the IAsynchronousOperationListener.Delay call isn't an option to prevent the exception from being thrown when Task.Delay is inside the listener.Delay call?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a very nice idea. I wasn't able to apply it though here as we still need the CT for Expeditable.Delay. Maybe sam has thoughts on how to combine both of htese?

@CyrusNajmabadi CyrusNajmabadi requested a review from ToddGrun June 9, 2023 16:43
Copy link
Contributor

@ToddGrun ToddGrun left a comment

Choose a reason for hiding this comment

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

:shipit:

@CyrusNajmabadi CyrusNajmabadi enabled auto-merge June 9, 2023 16:45
@CyrusNajmabadi CyrusNajmabadi dismissed sharwell’s stale review June 9, 2023 18:44

Feedback accounted for :)

@CyrusNajmabadi CyrusNajmabadi merged commit 55e3953 into dotnet:main Jun 9, 2023
@CyrusNajmabadi CyrusNajmabadi deleted the noThrowDelay branch June 9, 2023 18:44
@ghost ghost added this to the Next milestone Jun 9, 2023
@RikkiGibson RikkiGibson modified the milestones: Next, 17.7 P3 Jun 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants