Skip to content

Fix incorrect task chaining in InlineRenameSession#34254

Merged
sharwell merged 9 commits intodotnet:masterfrom
sharwell:task-chaining
Jan 10, 2020
Merged

Fix incorrect task chaining in InlineRenameSession#34254
sharwell merged 9 commits intodotnet:masterfrom
sharwell:task-chaining

Conversation

@sharwell
Copy link
Copy Markdown
Contributor

@sharwell sharwell commented Mar 19, 2019

Fixes #29483

@sharwell sharwell requested a review from a team as a code owner March 19, 2019 17:56
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

📝 QueueApplyReplacements is chained on _allRenameLocationsTask, but depends on data produced by RaiseSessionSpansUpdated. If the tasks are reordered (e.g. if a Task.Delay is inserted above before RaiseSessionSpansUpdated), the data will be processed incorrectly and lead to a failure.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i'm not understanding. if QueueApplyReplacements is chained on _allRenameLocationsTask, then shouldn't it only run when _allRenameLocationsTask completes? And wouldn't htat only complete once that lambda completes? Which only finishes after RaiseSessionSpansUpdated runs and finishes? Even if something caused a delay before/after RaiseSessionSpansUpdated what would change?

Can you clarify your explanation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

And wouldn't htat only complete once that lambda completes?

You are confusing _allRenameLocationsTask (the field) with allRenameLocationsTask (the local variable). 😉

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

oh boy. i'll reread, but that certainly is a confusing way to name things.

Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

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

There is a deadlock introduced by this approach.

Comment thread src/EditorFeatures/Core.Wpf/InlineRename/InlineRenameSession.cs Outdated
@jasonmalinowski jasonmalinowski dismissed their stale review March 19, 2019 19:22

Pull request changed since original problem.

sharwell added 2 commits May 15, 2019 16:44
QueueApplyReplacements is chained on _allRenameLocationsTask, but depends
on data produced by RaiseSessionSpansUpdated. If the tasks are reordered
(e.g. if a Task.Delay is inserted above before RaiseSessionSpansUpdated),
the data will be processed incorrectly and lead to a failure.

Fixes dotnet#34221
@RikkiGibson
Copy link
Copy Markdown
Member

@dotnet/roslyn-ide this could help with test reliability. Could we get this reviewed?

Copy link
Copy Markdown
Member

@JoeRobich JoeRobich left a comment

Choose a reason for hiding this comment

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

Looks good to me, but I can't speak for the JTF usage.

Comment thread src/EditorFeatures/Core.Wpf/InlineRename/InlineRenameSession.cs Outdated
Comment thread src/EditorFeatures/Core.Wpf/InlineRename/InlineRenameSession.cs Outdated
Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

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

So I agree this does fix the task chaining problem, but I regret it doesn't actually simplify all the crazy state in the first place. If nothing else, can we add a comment or two at the points where we're updating the chain to describe why it's important to have the ordering? I'm happy to sign off if this is done.

I'm also worried about the perf impact of this: previously, it was OK for you to start an inline rename, type something, and hit enter really fast, the task chain is directly computing the original locations, then the conflict resolution, and then will go and apply that. This is now sticking in the middle of that the UI update for something the user won't ever actually see. I don't know how often that happens of course.

Comment thread src/EditorFeatures/Core.Wpf/InlineRename/InlineRenameSession.cs
Comment thread src/EditorFeatures/Core.Wpf/InlineRename/InlineRenameSession.cs Outdated
@sharwell

This comment has been minimized.

@sharwell
Copy link
Copy Markdown
Contributor Author

If nothing else, can we add a comment or two at the points where we're updating the chain to describe why it's important to have the ordering?

@jasonmalinowski This is now done. I also corrected an incorrect cancellation token use, with documentation to clarify why CancellationToken.None is correct even though another cancellation is available in the current scope.

@sharwell sharwell removed the Blocked label Sep 25, 2019
@RikkiGibson
Copy link
Copy Markdown
Member

@dotnet/roslyn-ide for review.

Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

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

Not understanding why we're trying to maintain the LazyCancellation behavior -- it seems like it's making the conversion more complicated than necessary...

UpdateReferenceLocationsTask(ThreadingContext.JoinableTaskFactory.RunAsync(async () =>
{
// Join prior work before proceeding, since it performs a required state update.
// https://github.com/dotnet/roslyn/pull/34254#discussion_r267024593
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not sure if it's worth inlining the explanation rather than relying on GitHub not breaking a URL like that....

//
// The cancellation token is passed to the prior work when it starts, not when it's joined. This is
// the equivalent of TaskContinuationOptions.LazyCancellation.
await _allRenameLocationsTask.JoinAsync(CancellationToken.None).ConfigureAwait(false);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why wouldn't this just pass the cancellationToken? I think you're trying to preserve behavior, but do we need to?

(I never liked that LazyCancellation was forced on everybody using SafeContinueWith who didn't perhaps need it because it means we would be potentially cancelling things a bit later.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

A behavior change here requires proof that the newly-allowed concurrency would not result in state corruption.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

FindRenameLocationsAsync already can be running multiple times in parallel if for example options were changed in rapid succession multiple times. Either it's already safe for concurrency (and it's not a concern) or it's not, and then this is still bugged.

Copy link
Copy Markdown
Contributor Author

@sharwell sharwell Dec 6, 2019

Choose a reason for hiding this comment

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

I was not able to prove that this one would behave correctly with lazy cancellation. With that said, this cancellation token only gets cancelled when the rename session is cancelled or committed, so it won't be impacting typing performance. If/when it is cancelled, the remaining tasks will quickly cancel themselves after the currently executing task finishes (by cancelling).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With that said, this cancellation token only gets cancelled when the rename session is cancelled or committed, so it won't be impacting typing performance.

Ah, maybe this is why I was confused earlier: this code has more problems than what you're trying to fix. 😄 If the options are changed, we should have entirely cancelled off the old work because there's no reason for that to continue running. What this should be doing (loosely) is cancelling _cancellationTokenSource and creating a new one before it calls UpdateReferenceLocationsTask. It appears this predates the open sourcing of this so I don't know why this ended up this way.

Want to file a bug? It's not that hard to fix but requires some care and I don't blame you for not wanting to fix it in this PR. (Although you're welcome to!)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Filed #40891

Comment thread src/EditorFeatures/Core.Wpf/InlineRename/InlineRenameSession.cs Outdated
@sharwell sharwell requested a review from a team December 6, 2019 00:14
@sharwell
Copy link
Copy Markdown
Contributor Author

@jasonmalinowski to re-review the final updates

Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

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

Looks good but I think I understand some of our confusion earlier: there's an (existing) perf issue where an option change should be cancelling off the old reference location search but isn't. Please file a bug for that while closing this, as that's a very not good bug.

await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, _cancellationTokenSource.Token);
_cancellationTokenSource.Token.ThrowIfCancellationRequested();

RaiseSessionSpansUpdated(inlineRenameLocations.Locations.ToImmutableArray());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we get a comment here saying it's unfortunate that _allRenameLocationsTask now requires the UI thread before it can let continuations run? Don't get me wrong: this is correctly expressing how it has to work right now, but it's still not how it was supposed to work.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a comment and linked it to #40890

//
// The cancellation token is passed to the prior work when it starts, not when it's joined. This is
// the equivalent of TaskContinuationOptions.LazyCancellation.
await _allRenameLocationsTask.JoinAsync(CancellationToken.None).ConfigureAwait(false);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With that said, this cancellation token only gets cancelled when the rename session is cancelled or committed, so it won't be impacting typing performance.

Ah, maybe this is why I was confused earlier: this code has more problems than what you're trying to fix. 😄 If the options are changed, we should have entirely cancelled off the old work because there's no reason for that to continue running. What this should be doing (loosely) is cancelling _cancellationTokenSource and creating a new one before it calls UpdateReferenceLocationsTask. It appears this predates the open sourcing of this so I don't know why this ended up this way.

Want to file a bug? It's not that hard to fix but requires some care and I don't blame you for not wanting to fix it in this PR. (Although you're welcome to!)

@sharwell sharwell merged commit 0a0baef into dotnet:master Jan 10, 2020
@sharwell sharwell deleted the task-chaining branch January 10, 2020 21:39
@sharwell sharwell added this to the 16.5.P3 milestone Jan 10, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Flaky] CSharp_FixupSpanDuringResolvableConflict_ComplexificationReordersReferenceSpans Failure

6 participants