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

AnalysisService improvments #1179

Merged
merged 33 commits into from
Feb 14, 2020
Merged

Conversation

rjmholt
Copy link
Contributor

@rjmholt rjmholt commented Feb 7, 2020

This PR was originally supposed to stop crashes when we can't find PSScriptAnalyzer on the module path (when the Create() method returns null), but instead does a few things:

  • Allows on-demand disabling of the AnalysisService when the configuration changes
  • Adds a ConfigurationChanged event so that things to can subscribe to it early on
  • Breaks the thing running the analysis out from the service delivering it to other parts of the codebase, meaning it will be easier to substitute in a hosted PSScriptAnalyzer engine later
  • Improves cancellation of script analysis, both in terms of the delay but also in terms of being able to cancel when the actual analysis request is cancelled now
  • Moves a some cohesion/coupling issues that were clearly in the domain of the AnalysisService behind its API:
    • The configuration change thing
    • Comment help generation details
  • Fixes a log call where we (1) claim not to handle diagnostics with long IDs (even though we can in the function that takes diagnostic IDs) and (2) process them anyway (no continue)...
  • Improves how we publish CodeActions:
    • Formerly we would:
      • Receive a diagnostics request
      • Start processing this and eventually get to updating the dictionary
      • Take out locks for each file being updated in the dictionary
      • Update the dictionary for each file
      • And then the caller would have to acquire a lock per file
      • Then copy a whole dictionary of things into their context
      • The worst part being that the CodeAction request would usually come before the diagnostic task had passed its delay
    • So now, we ditch the locks and instead:
      • Diagnostic requests set up tasks and register them to each file
      • When a code action request comes through, it waits for the associated task to finish
      • Then it just reads directly out of the concurrent dictionary
      • (This isn't guaranteed to be a clean read, but it's likely that it will be and if it's not it's because more diagnostic requests are happening because the file's being updated -- so it doesn't mean much to not be clean)
  • Oh, I also seem to have fixed a URI encoding issue on Windows, but we could probably fix it better and in a more universal way

Aids PowerShell/vscode-powershell#2127 and PowerShell/vscode-powershell#2190

// Load any of the profile paths that exist
var command = new PSCommand();
bool hasLoadablePath = false;
foreach (var profilePath in GetLoadableProfilePaths(this.profilePaths))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I forgot. I was seeing an exception here in the debugger because none of the profile files existed. And it was my change that caused it in another PR. So I fixed it.

@PowerShell PowerShell deleted a comment from rjmholt Feb 7, 2020
@PowerShell PowerShell deleted a comment from rjmholt Feb 7, 2020
using Microsoft.Extensions.Logging;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
using System.Threading;
using System.Collections.Concurrent;
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
using Microsoft.PowerShell.EditorServices.Utility;
using Microsoft.PowerShell.EditorServices.Services.Analysis;
using Microsoft.PowerShell.EditorServices.Services.Configuration;
Copy link
Member

Choose a reason for hiding this comment

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

nit: can you sort these?

Copy link
Member

Choose a reason for hiding this comment

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

don't forget this

_workplaceService = workspaceService;
_analysisDelayMillis = 750;
_mostRecentCorrectionsByFile = new ConcurrentDictionary<string, CorrectionTableEntry>();
_hasInstantiatedAnalysisEngine = false;
Copy link
Member

Choose a reason for hiding this comment

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

potential threading concern... If you wanna go down this route, maybe it's best to make the AnalysisEngine Lazy<>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I was thinking about that, but unlike Lazy<>, this will respond to a configuration update by re-instantiating the engine. It might be that the best solution is to put a lock in the getter?

Copy link
Member

Choose a reason for hiding this comment

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

maybe use an Interlocked.Exchange or something instead. I'm concerned that if there's a configuration update when this is being instantiated for the first time that that could be a race condition?

_pssaModuleInfo = pssaModuleInfo;
_mostRecentCorrectionsByFile = new ConcurrentDictionary<string, (SemaphoreSlim, Dictionary<string, MarkerCorrection>)>();
_workplaceService = workspaceService;
_analysisDelayMillis = 750;
Copy link
Member

Choose a reason for hiding this comment

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

can this be a constant instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was wondering whether to put it in as a constructor parameter. It used to be inline where the task is. As a field, we could use reflection to change the setting even.

@@ -530,7 +535,7 @@ internal string ResolveRelativeScriptPath(string baseFilePath, string relativePa
return combinedPath;
}

private static Uri UnescapeDriveColon(Uri fileUri)
internal static Uri UnescapeDriveColon(Uri fileUri)
Copy link
Member

Choose a reason for hiding this comment

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

revert this when you use the other API

Diagnostics.Clear();

PwshExe = TestsFixture.PwshExe;
Copy link
Member

Choose a reason for hiding this comment

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

what's the reason for this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I needed to edit the tests and moved this down so it wasn't with the list instantiation code

@PowerShell PowerShell deleted a comment from rjmholt Feb 11, 2020
@PowerShell PowerShell deleted a comment from rjmholt Feb 11, 2020
@PowerShell PowerShell deleted a comment from rjmholt Feb 11, 2020
@PowerShell PowerShell deleted a comment from rjmholt Feb 11, 2020
@PowerShell PowerShell deleted a comment from rjmholt Feb 11, 2020
/// <returns>The output of PowerShell execution.</returns>
private Collection<PSObject> InvokePowerShellWithModulePathPreservation(System.Management.Automation.PowerShell powershell)
{
if (_alreadyRun)
Copy link
Member

Choose a reason for hiding this comment

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

What is this check for?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since runspace creation only happens at first invocation, I put a boolean in to reduce the overhead on subsequent calls

Copy link
Member

Choose a reason for hiding this comment

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

Run a few manual tests to make sure - I know RunspacePool has a habit of creating new runspaces within the pool (as in, we don't have control over when, why and how it creates Runspaces). I've seen in Get-Runspace incremental runspace numbering which is why I have concerns here.

using System.Collections;
using System.Runtime.InteropServices;
using System.Collections.Concurrent;
using System.Collections.Generic;
Copy link
Member

Choose a reason for hiding this comment

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

nit: usually system* come first, then sorted everything else.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's how VS sorted it. I've corrected


private CancellationTokenSource _diagnosticsCancellationTokenSource;

#region Engine Initialization
#endregion
Copy link
Member

Choose a reason for hiding this comment

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

empty region?

@@ -107,6 +107,8 @@ public PssaCmdletAnalysisEngine Build()
}
}

private const int PSSA_RUNPOOL_SIZE = 10;
Copy link
Member

Choose a reason for hiding this comment

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

has this always been 10? 😮

_alreadyRun = true;
finally
{
Interlocked.Increment(ref _pssaRunCount);
Copy link
Member

Choose a reason for hiding this comment

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

is it guaranteed that a RunspacePool uses a new Runspace until it hits the max of what it can support?

Copy link
Member

Choose a reason for hiding this comment

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

Otherwise the first 10 runs might use the same runspace and then subsequent runs spin up new runspaces.

Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

Choose a reason for hiding this comment

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

LGTM

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.

2 participants