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

Fixed multi-targeted projects from invalidating the MruCache on document open/changed #4918

Merged
merged 6 commits into from
May 16, 2018

Conversation

TIHan
Copy link
Contributor

@TIHan TIHan commented May 15, 2018

Here are the things that this PR does:

  • Added a new field to FSharpProjectOptions, ProjectId. It is an optional string. If set to None, the cache will key off the ProjectFileName. When it is set, it will key off the ProjectId. This extends FCS to be able to handle multi-targeted projects since each target is a different project with the same ProjectFileName.
  • We clear the options table based on the ProjectId when a workspace ProjectRemoved event happens; this handles removing multi-targeted projects.

All of this should fix more perf issues related to multi-targeted projects, specifically when invalidating the cache. Type checking will still happen for each target, but that is what Roslyn does today; we could add option to only type check on the document in current context if people really want that.

@TIHan TIHan changed the title Fixed multi-targeted projects from invalidating the MruCache on document open/changed [WIP] Fixed multi-targeted projects from invalidating the MruCache on document open/changed May 15, 2018
@TIHan
Copy link
Contributor Author

TIHan commented May 15, 2018

Not quite ready, I just tested some script files and they seem to have some trouble.

Copy link
Member

@Pilchie Pilchie left a comment

Choose a reason for hiding this comment

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

I'd like to understand the differences between those seemingly similar variables.

type Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.VisualStudioWorkspaceImpl with

member this.GetCurrentProjectReferenceIds(projectId: ProjectId) =
match this.ProjectTracker.GetProject(projectId) with
Copy link
Member

Choose a reason for hiding this comment

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

Do you need to use the method that will cause the project's deferred state to be created here if it isn't already?

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 don't think so if I'm understanding you correctly.

/// Gets a project file path. Throws if there is no project file path.
member this.GetProjectFilePath(projectId) =
match this.TryGetProjectFilePath(projectId) with
| None -> failwithf "Can't find project file path from %A." projectId
Copy link
Member

Choose a reason for hiding this comment

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

What happens to this error message? Should it be localized?

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 don't know if should be localized. I have a habit of doing this sort of thing when I want a method to throw. These are internal methods too.

let site = ProjectSitesAndFiles.ProjectSiteOfSingleFile(fileName)
let deps, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, serviceProvider, (tryGetOrCreateProjectId fileName), fileName, extraProjectInfo, Some projectOptionsTable)
let site = ProjectSitesAndFiles.ProjectSiteOfSingleFile(filePath)
let _deps, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, serviceProvider, Some projectId, filePath, extraProjectInfo, Some projectOptionsTable)
let parsingOptions, _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions(projectOptions)
return (deps, parsingOptions, projectOptions)
Copy link
Member

Choose a reason for hiding this comment

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

Do you want to be returning deps or _deps here? What is the difference?

Copy link
Contributor Author

@TIHan TIHan May 16, 2018

Choose a reason for hiding this comment

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

_deps means it's ignored or not used; same with _referencedProjectIds. Using _ makes the value not able to be used. At some point, GetProjectOptionsForProjectSite probably shouldn't return references to begin with.

member this.UpdateProjectInfo(projectId, site, userOpName, invalidateConfig) =
Logger.Log(LogEditorFunctionId.LanguageService_UpdateProjectInfo)

let referencedProjectIds = workspace.GetCurrentProjectReferenceIds(projectId) |> Seq.toArray
Copy link
Member

Choose a reason for hiding this comment

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

Same question on referencedProjectIds vs _ referencedProjectIds?

@@ -274,6 +274,7 @@ type internal ProjectSitesAndFiles() =
let option =
let newOption () = {
ProjectFileName = projectSite.ProjectFileName
ProjectId = projectId |> Option.map (fun x -> x.Id.ToString())
Copy link
Member

Choose a reason for hiding this comment

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

Not sure if this matters, but the ToString there is culture aware, so might return different results in different cultures (or if the thread's current culture changes between calls, or if calls happen on different threads that have different cultures).

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 a good point, I need to fix that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In a recent change, I do the comparison with StringComparison.OrdinalIgnoreCase.

Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't it follow same pattern as above let strGuid = "_ProjectId=" + (match options.ProjectId with Some(projectId) -> projectId | _ -> "null") - mabe introduce a helper?

Copy link
Member

Choose a reason for hiding this comment

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

I was actually intending to specify a specific formatting (and possibly invariant culture) to both the ToString and the comparison.

Just OrdinalIgnoreCase is not the correct fix.

I also agree with @forki about extracting a helper to generated this string.

Copy link
Member

Choose a reason for hiding this comment

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

I think you are right that it's fine according to https://msdn.microsoft.com/en-us/library/97af8hh4(v=vs.110).aspx. I just don't like unspecified formats used for this sort of thing, since they go wrong in too many instances.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In that case, it may be better to make the field a Guid instead of a string so it's clear. What do you think?

Copy link
Contributor

@smoothdeveloper smoothdeveloper May 17, 2018

Choose a reason for hiding this comment

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

I'm thinking it would force client to maintain a mapping between such guid and project name / target couples.

A simpler way for the client would be to concatenate the target name to the project name and use that as projectid, but this is only possible when ProjectId is a string.

What is wrong with leaving it case sensitive?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After some discussion, I will make the id case sensitive. Then I will be explicit with the ToString() to be the "D" format and make it lowercase.

Copy link
Member

Choose a reason for hiding this comment

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

👍

@TIHan
Copy link
Contributor Author

TIHan commented May 16, 2018

I was a bit over-zealous with some changes. I'm now going to try to do the bare minimum to get this to work correctly.

…ling multi-projects in a better way for LanguageService, making sure we clear it out of the options table
@@ -2660,7 +2664,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC
let execWithReactorAsync action = reactor.EnqueueAndAwaitOpAsync(userOpName, "ParseAndCheckFileInProject", filename, action)
async {
try
let strGuid = "_" + Guid.NewGuid().ToString()
let strGuid = "_ProjectId=" + (match options.ProjectId with Some(projectId) -> projectId | _ -> "null")
Copy link
Contributor

Choose a reason for hiding this comment

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

consider multi line pattern match

Copy link
Contributor

Choose a reason for hiding this comment

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

or defaultArg

Copy link
Contributor

Choose a reason for hiding this comment

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

or Option.defaultValue

@@ -935,6 +935,7 @@ let ``Projects creating generated types should not utilize cross-project-referen
let options =
{ProjectFileName =
__SOURCE_DIRECTORY__ + @"/data/TypeProvidersBug/TestConsole/TestConsole.fsproj";
ProjectId = None;
Copy link
Contributor

Choose a reason for hiding this comment

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

please don't introduce new ;

@TIHan TIHan changed the title [WIP] Fixed multi-targeted projects from invalidating the MruCache on document open/changed Fixed multi-targeted projects from invalidating the MruCache on document open/changed May 16, 2018
@@ -1760,6 +1760,7 @@ type UnresolvedReferencesSet = UnresolvedReferencesSet of UnresolvedAssemblyRefe
type FSharpProjectOptions =
{
ProjectFileName: string
ProjectId: string option
Copy link
Member

Choose a reason for hiding this comment

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

Why it is optional? Could it be a unique Id string so it would be the only thing to compare in UseSameProject below?

Copy link
Contributor Author

@TIHan TIHan May 16, 2018

Choose a reason for hiding this comment

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

I think it is more correct if it was that way. But existing code may not have an Id, unless they pass the file name again as the Id.

Copy link
Member

Choose a reason for hiding this comment

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

Existing client code has to be updated with some value anyway.

options1.ProjectFileName = options2.ProjectFileName
static member UseSameProject(options1,options2) =
match options1.ProjectId, options2.ProjectId with
| Some(projectId1), Some(projectId2) -> String.Equals(projectId1, projectId2, StringComparison.OrdinalIgnoreCase)
Copy link
Member

Choose a reason for hiding this comment

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

Why ignoring case? Consider using Ordinal?

Copy link
Contributor Author

@TIHan TIHan May 16, 2018

Choose a reason for hiding this comment

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

Because this Id will most likely will be a Guid and I don't like the idea of making the guid comparisons case sensitive.

@dsyme
Copy link
Contributor

dsyme commented May 16, 2018

I understand the change, it looks good. Basically

  • Before: keep at most one typecheck state for a project file name
  • After: keep at most one typecheck state for a project ID

That's fine, good fix

@TIHan TIHan merged commit a1f2f80 into dotnet:master May 16, 2018
@TIHan TIHan deleted the perf-fixes2 branch May 16, 2018 19:57
nosami pushed a commit to xamarin/visualfsharp that referenced this pull request Jan 26, 2022
…ent open/changed (dotnet#4918)

* Handling multi-targeting in FCS by adding an optional ProjectId; handling multi-projects in a better way for LanguageService, making sure we clear it out of the options table

* Fixing tests with FSharpProjectOptions type changes

* Fixed more tests

* Fixed more tests

* Fixed more tests

* Fixed build
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.

8 participants