-
Notifications
You must be signed in to change notification settings - Fork 790
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
Conversation
Not quite ready, I just tested some script files and they seem to have some trouble. |
There was a problem hiding this 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
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
src/fsharp/service/service.fs
Outdated
@@ -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") |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or defaultArg
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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 ;
@@ -1760,6 +1760,7 @@ type UnresolvedReferencesSet = UnresolvedReferencesSet of UnresolvedAssemblyRefe | |||
type FSharpProjectOptions = | |||
{ | |||
ProjectFileName: string | |||
ProjectId: string option |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/fsharp/service/service.fs
Outdated
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) |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
I understand the change, it looks good. Basically
That's fine, good fix |
…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
Here are the things that this PR does:
FSharpProjectOptions
,ProjectId
. It is an optional string. If set to None, the cache will key off theProjectFileName
. When it is set, it will key off theProjectId
. This extends FCS to be able to handle multi-targeted projects since each target is a different project with the sameProjectFileName
.ProjectId
when a workspaceProjectRemoved
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.