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

Fix some underlying causes of long UI blocks in new LS implementation #1819

Merged
merged 5 commits into from Nov 23, 2016

Conversation

dsyme
Copy link
Contributor

@dsyme dsyme commented Nov 23, 2016

Fixes underlying issues associated with #1815

After getting a 15+ second UI block in VS 2017, I've done a complete review of the way the new LanguageService implementation is using FSharp.Compiler.Service (the DLL internally called FSharp.Compiler.LanguageService in the Visual F# Tools repo, but it's basically the same component as FCS)

In short, the new LS implementation expects FCS to honour cancellation of submitted tasks in a more timely way. This PR greatly improves the cancellability of FCS requests, which was one of the underlying causes of such a long UI block.

  1. Improve FCS so that it respects cancellation in a more timely way. Cancellation will now be respected at each step of the evaluation of the incremental build graph (IncrementalBuild.fs). This represents a fundamental improvement to FCS which will be very useful to other F# editors as well, as long as they are cancelling tasks.

    In particular, some FCS requests could be very long running, e.g. CheckFileInProject may require checking all the files prior to this file (and if cross-F#-to-F#-project checking is enabled then it will require checking all the dependent projects too).

    Prior to this PR FCS was not respecting cancellation of these requests, and was simply running them through and then throwing the results away.

  2. The new LS implementation was using some unnecessary calls to Async.RunSynchronously. All of these could inject arbitrary non-responsiveness into some part of the causality chains. I've removed these in favour of fully async code.

  3. In FCS the old IsResultObsolete logic can be removed in favour of checking a cancellation token

@dsyme
Copy link
Contributor Author

dsyme commented Nov 23, 2016

@Pilchie @KevinRansom @OmarTawfik @brettfo I have a couple of questions

  • Here we have a situation where FCS is giving us back file names exactly as they were given on the command line, which might include ..\.. kind of stuff. I'm calling GetFullPath to get rid of those but I get the feeling that's not the right way to do things in Roslynized code. If there is a correct way to get a file name (and then an ID) then please let me know

  • Here and in several other places I make a comment that the tasks being executed must be cancelled by Roslyn if the results are no longer needed by the UI, for example if the editor moves away from a completion point or cancels a goto-definition. For now I will assume that this is happening correctly, and remove these REVIEW comments from the PR.

  • I still notice some very long UI blocks on Ctrl-Space completion where the UI seems to be clocking until information is available. This should be non-blocking. Here is the stack - it seems to me Roslyn is doing a full blocking wait when you press Ctrl-Space?

>	WindowsBase.dll!System.Windows.Threading.DispatcherSynchronizationContext.Wait(System.IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)	Unknown
 	mscorlib.dll!System.Threading.SynchronizationContext.InvokeWaitMethodHelper(System.Threading.SynchronizationContext syncContext, System.IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)	Unknown
 	[Native to Managed Transition]	
 	[Managed to Native Transition]	
 	mscorlib.dll!System.Threading.Monitor.Wait(object obj, int millisecondsTimeout, bool exitContext)	Unknown
 	mscorlib.dll!System.Threading.Monitor.Wait(object obj, int millisecondsTimeout)	Unknown
 	mscorlib.dll!System.Threading.ManualResetEventSlim.Wait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken)	Unknown
 	mscorlib.dll!System.Threading.Tasks.Task.SpinThenBlockingWait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken)	Unknown
 	mscorlib.dll!System.Threading.Tasks.Task.InternalWait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken)	Unknown
 	mscorlib.dll!System.Threading.Tasks.Task.Wait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken)	Unknown
 	Microsoft.CodeAnalysis.EditorFeatures.dll!Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion.Controller.Session.WaitForModel()	Unknown
 	Microsoft.CodeAnalysis.EditorFeatures.dll!Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion.Controller.Microsoft.CodeAnalysis.Editor.ICommandHandler<Microsoft.CodeAnalysis.Editor.Commands.CommitUniqueCompletionListItemCommandArgs>.ExecuteCommand(Microsoft.CodeAnalysis.Editor.Commands.CommitUniqueCompletionListItemCommandArgs args, System.Action nextHandler)	Unknown
 	Microsoft.CodeAnalysis.EditorFeatures.dll!Microsoft.CodeAnalysis.Editor.CommandHandlers.AbstractCompletionCommandHandler.ExecuteCommandWorker<Microsoft.CodeAnalysis.Editor.Commands.CommitUniqueCompletionListItemCommandArgs>(Microsoft.CodeAnalysis.Editor.Commands.CommitUniqueCompletionListItemCommandArgs args, System.Action nextHandler)	Unknown
 	Microsoft.CodeAnalysis.EditorFeatures.dll!Microsoft.CodeAnalysis.Editor.CommandHandlers.AbstractCompletionCommandHandler.Microsoft.CodeAnalysis.Editor.ICommandHandler<Microsoft.CodeAnalysis.Editor.Commands.CommitUniqueCompletionListItemCommandArgs>.ExecuteCommand(Microsoft.CodeAnalysis.Editor.Commands.CommitUniqueCompletionListItemCommandArgs args, System.Action nextHandler)	Unknown
 	Microsoft.CodeAnalysis.EditorFeatures.dll!Microsoft.CodeAnalysis.Editor.Implementation.Commands.CommandHandlerService.ExecuteHandlers<Microsoft.CodeAnalysis.Editor.Commands.CommitUniqueCompletionListItemCommandArgs>(System.Collections.Generic.IList<Microsoft.CodeAnalysis.Editor.ICommandHandler<Microsoft.CodeAnalysis.Editor.Commands.CommitUniqueCompletionListItemCommandArgs>> commandHandlers, Microsoft.CodeAnalysis.Editor.Commands.CommitUniqueCompletionListItemCommandArgs args, System.Action lastHandler)	Unknown
 	Microsoft.CodeAnalysis.EditorFeatures.dll!Microsoft.CodeAnalysis.Editor.Implementation.Commands.CommandHandlerService.Execute<Microsoft.CodeAnalysis.Editor.Commands.CommitUniqueCompletionListItemCommandArgs>(Microsoft.VisualStudio.Utilities.IContentType contentType, Microsoft.CodeAnalysis.Editor.Commands.CommitUniqueCompletionListItemCommandArgs args, System.Action lastHandler)	Unknown
 	Microsoft.VisualStudio.LanguageServices.dll!Microsoft.VisualStudio.LanguageServices.Implementation.AbstractOleCommandTarget.ExecuteCommitUniqueCompletionItem(Microsoft.VisualStudio.Text.ITextBuffer subjectBuffer, Microsoft.VisualStudio.Utilities.IContentType contentType, System.Action executeNextCommandTarget)	Unknown
 	Microsoft.VisualStudio.LanguageServices.dll!Microsoft.VisualStudio.LanguageServices.Implementation.AbstractOleCommandTarget.ExecuteVisualStudio2000(ref System.Guid pguidCmdGroup, uint commandId, uint executeInformation, System.IntPtr pvaIn, System.IntPtr pvaOut, Microsoft.VisualStudio.Text.ITextBuffer subjectBuffer, Microsoft.VisualStudio.Utilities.IContentType contentType)	Unknown
 	Microsoft.VisualStudio.LanguageServices.dll!Microsoft.VisualStudio.LanguageServices.Implementation.AbstractOleCommandTarget.Exec(ref System.Guid pguidCmdGroup, uint commandId, uint executeInformation, System.IntPtr pvaIn, System.IntPtr pvaOut)	Unknown
 	Microsoft.VisualStudio.Editor.Implementation.dll!Microsoft.VisualStudio.Editor.Implementation.CommandChainNode.InnerExec(ref System.Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, System.IntPtr pvaIn, System.IntPtr pvaOut)	Unknown
 	Microsoft.VisualStudio.Editor.Implementation.dll!Microsoft.VisualStudio.Editor.Implementation.CommandChainNode.InnerExec(ref System.Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, System.IntPtr pvaIn, System.IntPtr pvaOut)	Unknown

@Pilchie
Copy link
Member

Pilchie commented Nov 23, 2016

  1. Generally in Roslyn when we get results from the compiler they are for a SyntaxTree, so we don't use filenames as often, but what you've done there seems fine to me. Maybe @jasonmalinowski has another idea though?

  2. Roslyn does generally try to cancel things, but that comment is still concerning due to the implication that things Roslyn tends to think of as cheap, syntax only operations (like breakpoint span resolution) might depend on parsing all the files in the solution. I guess this goes with the idea of supporting more parallelism in the compiler, but it would be really nice if we could just get syntax for a single file any time :(.

  3. Yes, that will block. Ctrl+Space is the "Edit.CompleteWord" command which inserts the completion match if it is unambiguous or displays the list if it is ambiguous. In order to determine the match, we have to finish processing completion. Edit.ListMembers (Ctrl+J) should work to just trigger completion asynchronously. If this isn't F#'s expected behavior (I know C++ imparts different semantics on these commands), we can probably tweak the command handler.

@dsyme
Copy link
Contributor Author

dsyme commented Nov 23, 2016

The Ubuntu failure is some transient thing with restoring package FsLexYacc 7.0.1, it's unrelated to the PR.

So I think this is ready to go in given the comments above

@dsyme
Copy link
Contributor Author

dsyme commented Nov 23, 2016

... that comment is still concerning due to the implication that things Roslyn tends to think of as cheap..

Yes, the general rule for the F# compiler service is that things are more expensive. The cause of this are

  • we do work in larger chunks (e.g. whole files)
  • we don't cancel operations (blocking up the single-threaded queue - this PR)
  • we don't do operations in parallel (just one compiler service thread, processing a request queue)
  • the operations are intrinsically more expensive (e.g. F# type inference is more expensive than C#)
  • the implementation of the operations is less refined/optmiized
  • the data structures underlying the operation are less refined/optimized

We can make incremental improvements to all of the above.

syntax only operations (like breakpoint span resolution) might depend on parsing all the files in the solution.
I guess this goes with the idea of supporting more parallelism in the compiler, but it would be really nice if we could just get syntax for a single file any time :(.

I was wrong - ParseFileInProject creates the builder for the project and then parses the file and caches that result. That's sufficient. I've not experience validating breakpoints as slow - the problem is more if the operation gets requested and then dropped on the floor (without cancellation).

Yes, that will block. Ctrl+Space is the "Edit.CompleteWord" command which inserts the completion match if it is unambiguous or displays the list if it is ambiguous. In order to determine the match, we have to finish processing completion. Edit.ListMembers (Ctrl+J) should work to just trigger completion asynchronously. If this isn't F#'s expected behavior (I know C++ imparts different semantics on these commands), we can probably tweak the command handler.

In the old language service it didn't block. Also, for F# type providers it's crucial that it doesn't. We should adjust the handler so it doesn't block.

@Pilchie
Copy link
Member

Pilchie commented Nov 23, 2016

Filed #1825 to track the blocking completion.

@dsyme
Copy link
Contributor Author

dsyme commented Nov 23, 2016

This also fixes #1756, see this change here

We have a separate issue that too may errors/warnings are shown for out-of-project files - the old LS implementation showed the first 3 errors - a good choice which we decided on long ago. I'll add an issue for that.

@dsyme
Copy link
Contributor Author

dsyme commented Nov 23, 2016

I'll merge this since all the key CI runs have passed (Ubuntu failure is transient, AppVeyor is just advisory and often times out)

@dsyme dsyme merged commit 820af62 into dotnet:master Nov 23, 2016
@dsyme
Copy link
Contributor Author

dsyme commented Nov 23, 2016

@Pilchie thanks for that

@dsyme
Copy link
Contributor Author

dsyme commented Nov 23, 2016

Yes, the general rule for the F# compiler service is that things are more expensive....

@KevinRansom @Pilchie @OmarTawfik @brettfo @cartermp BTW one very good thing that's coming out of the Roslynization is that it becomes vastly more clear when a problem is caused by a fundamental problem in FCS, rather than in the layers of horrible C# code in the old FSHarp.LanguageServicee.Base. This makes it much simper and clearer to go fix the problem in FCS, and put it under unit test too.

@cartermp
Copy link
Contributor

@dsyme That's great to hear!

nosami pushed a commit to xamarin/visualfsharp that referenced this pull request Jan 26, 2022
…dotnet#1819)

Fixes underlying issues associated with dotnet#1815

After getting a 15+ second UI block in VS 2017, I've done a complete review of the way the new LanguageService implementation is using FSharp.Compiler.Service (the DLL internally called FSharp.Compiler.LanguageService in the Visual F# Tools repo, but it's basically the same component as FCS)

In short, the new LS implementation expects FCS to honour cancellation of submitted tasks in a more timely way.  This PR greatly improves the cancellability of FCS requests, which was one of the underlying causes of such a long UI block.

1. Improve FCS so that it respects cancellation in a more timely way.  Cancellation will now be respected at each step of the evaluation of the incremental build graph (IncrementalBuild.fs). This represents a fundamental improvement to FCS which will be very useful to other F# editors as well, as long as they are cancelling tasks.

   In particular, some FCS requests could be _very_ long running, e.g. CheckFileInProject may require checking **all** the files prior to this file (and if cross-F#-to-F#-project checking is enabled then it will  require checking all the dependent projects too).  

   Prior to this PR FCS was not respecting cancellation of these requests, and was simply running them through and then throwing the results away.

2. The new LS implementation was using some unnecessary calls to Async.RunSynchronously.  All of these could inject arbitrary non-responsiveness into some part of the causality chains.  I've removed these in favour of fully async code.

3. In FCS the old IsResultObsolete logic can be removed in favour of checking a cancellation token

This also fixes dotnet#1756, see [this change here](https://github.com/Microsoft/visualfsharp/pull/1819/files#diff-69fa840cd1c6d144d0cd489cad82e901R88)

We have a separate issue that too may errors/warnings are shown for out-of-project files - the old LS implementation showed the first 3 errors - a good choice which we decided on long ago.  I'll add an issue for that.
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

4 participants