-
Notifications
You must be signed in to change notification settings - Fork 650
Introduce fast path for program updates in language service #879
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -100,6 +100,7 @@ type Project struct { | |
hasAddedOrRemovedSymlinks bool | ||
deferredClose bool | ||
pendingReload PendingReload | ||
dirtyFilePath tspath.Path | ||
|
||
comparePathsOptions tspath.ComparePathsOptions | ||
currentDirectory string | ||
|
@@ -372,14 +373,23 @@ func (p *Project) getScriptKind(fileName string) core.ScriptKind { | |
} | ||
|
||
func (p *Project) markFileAsDirty(path tspath.Path) { | ||
p.markAsDirty() | ||
p.dirtyStateMu.Lock() | ||
defer p.dirtyStateMu.Unlock() | ||
if !p.dirty { | ||
p.dirty = true | ||
p.dirtyFilePath = path | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this may need to be a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, but the minute anything else changes, we clear out There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see what you mean in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this temporary? because we have this https://github.com/microsoft/TypeScript/blob/main/src/compiler/program.ts#L2458 in strada thats much more resilient to multiple file changes (can happen as part of open and close when navigating source code) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But as a trade-off, typing performance in Strada is extremely poor compared to what it could be. We definitely need new optimizations for the 99 percentile case of typing in a single file. Program construction shouldn’t have to loop over every source file to determine what’s changed when the project infrastructure already knows what changed. |
||
p.version++ | ||
} else if path != p.dirtyFilePath { | ||
p.dirtyFilePath = "" | ||
} | ||
} | ||
|
||
func (p *Project) markAsDirty() { | ||
p.dirtyStateMu.Lock() | ||
defer p.dirtyStateMu.Unlock() | ||
if !p.dirty { | ||
p.dirty = true | ||
p.dirtyFilePath = "" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we need to reset There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! |
||
p.version++ | ||
} | ||
} | ||
|
@@ -430,47 +440,54 @@ func (p *Project) updateGraph() bool { | |
|
||
p.hasAddedOrRemovedFiles = false | ||
p.hasAddedOrRemovedSymlinks = false | ||
p.updateProgram() | ||
oldProgramReused := p.updateProgram() | ||
p.dirty = false | ||
p.dirtyFilePath = "" | ||
jakebailey marked this conversation as resolved.
Show resolved
Hide resolved
|
||
p.log(fmt.Sprintf("Finishing updateGraph: Project: %s version: %d", p.name, p.version)) | ||
if hasAddedOrRemovedFiles { | ||
p.log(p.print(true /*writeFileNames*/, true /*writeFileExplanation*/, false /*writeFileVersionAndText*/)) | ||
} else if p.program != oldProgram { | ||
p.log("Different program with same set of files") | ||
} | ||
|
||
if p.program != oldProgram && oldProgram != nil { | ||
for _, oldSourceFile := range oldProgram.GetSourceFiles() { | ||
if p.program.GetSourceFileByPath(oldSourceFile.Path()) == nil { | ||
p.host.DocumentRegistry().ReleaseDocument(oldSourceFile, oldProgram.GetCompilerOptions()) | ||
if !oldProgramReused { | ||
if oldProgram != nil { | ||
for _, oldSourceFile := range oldProgram.GetSourceFiles() { | ||
if p.program.GetSourceFileByPath(oldSourceFile.Path()) == nil { | ||
p.host.DocumentRegistry().ReleaseDocument(oldSourceFile, oldProgram.GetCompilerOptions()) | ||
} | ||
} | ||
} | ||
// TODO: this is currently always synchronously called by some kind of updating request, | ||
// but in Strada we throttle, so at least sometimes this should be considered top-level? | ||
p.updateWatchers(context.TODO()) | ||
} | ||
|
||
// TODO: this is currently always synchronously called by some kind of updating request, | ||
// but in Strada we throttle, so at least sometimes this should be considered top-level? | ||
p.updateWatchers(context.TODO()) | ||
return true | ||
} | ||
|
||
func (p *Project) updateProgram() { | ||
rootFileNames := p.GetRootFileNames() | ||
compilerOptions := p.compilerOptions | ||
|
||
func (p *Project) updateProgram() bool { | ||
if p.checkerPool != nil { | ||
p.logf("Program %d used %d checker(s)", p.version, p.checkerPool.size()) | ||
} | ||
p.program = compiler.NewProgram(compiler.ProgramOptions{ | ||
RootFiles: rootFileNames, | ||
Host: p, | ||
Options: compilerOptions, | ||
CreateCheckerPool: func(program *compiler.Program) compiler.CheckerPool { | ||
p.checkerPool = newCheckerPool(4, program, p.log) | ||
return p.checkerPool | ||
}, | ||
}) | ||
|
||
var oldProgramReused bool | ||
if p.program == nil || p.dirtyFilePath == "" { | ||
rootFileNames := p.GetRootFileNames() | ||
compilerOptions := p.compilerOptions | ||
p.program = compiler.NewProgram(compiler.ProgramOptions{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we somehow propagate out whether the old program was cloned or not, we can use that to skip reevaluating the file watchers at the end of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now in my latest commit. |
||
RootFiles: rootFileNames, | ||
Host: p, | ||
Options: compilerOptions, | ||
CreateCheckerPool: func(program *compiler.Program) compiler.CheckerPool { | ||
p.checkerPool = newCheckerPool(4, program, p.log) | ||
return p.checkerPool | ||
}, | ||
}) | ||
} else { | ||
// The only change in the current program is the contents of the file named by p.dirtyFilePath. | ||
// If possible, use data from the old program to create the new program. | ||
p.program, oldProgramReused = p.program.UpdateProgram(p.dirtyFilePath) | ||
} | ||
p.program.BindSourceFiles() | ||
return oldProgramReused | ||
} | ||
|
||
func (p *Project) isOrphan() bool { | ||
|
Uh oh!
There was an error while loading. Please reload this page.