Skip to content

Commit fe72bc2

Browse files
committed
Handle concurrency
1 parent bb45050 commit fe72bc2

File tree

4 files changed

+146
-78
lines changed

4 files changed

+146
-78
lines changed

internal/project/project.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ func (p *Project) updateGraph() bool {
509509
for _, oldSourceFile := range oldProgram.GetSourceFiles() {
510510
if p.program.GetSourceFileByPath(oldSourceFile.Path()) == nil {
511511
p.host.DocumentRegistry().ReleaseDocument(oldSourceFile, oldProgram.GetCompilerOptions())
512+
p.detachScriptInfoIfNotInferredRoot(oldSourceFile.Path())
512513
}
513514
}
514515
}
@@ -798,14 +799,9 @@ func (p *Project) isRoot(info *ScriptInfo) bool {
798799
return p.rootFileNames.Has(info.path)
799800
}
800801

801-
func (p *Project) RemoveFile(info *ScriptInfo, fileExists bool, detachFromProject bool) {
802+
func (p *Project) RemoveFile(info *ScriptInfo, fileExists bool) {
802803
p.mu.Lock()
803804
defer p.mu.Unlock()
804-
p.removeFile(info, fileExists, detachFromProject)
805-
p.markAsDirtyLocked()
806-
}
807-
808-
func (p *Project) removeFile(info *ScriptInfo, fileExists bool, detachFromProject bool) {
809805
if p.isRoot(info) && p.kind == KindInferred {
810806
p.rootFileNames.Delete(info.path)
811807
p.typeAcquisition = nil
@@ -820,9 +816,7 @@ func (p *Project) removeFile(info *ScriptInfo, fileExists bool, detachFromProjec
820816
// this.resolutionCache.invalidateResolutionOfFile(info.path);
821817
// }
822818
// this.cachedUnresolvedImportsPerFile.delete(info.path);
823-
if detachFromProject {
824-
info.detachFromProject(p)
825-
}
819+
p.markAsDirtyLocked()
826820
}
827821

828822
func (p *Project) AddInferredProjectRoot(info *ScriptInfo) {
@@ -999,19 +993,29 @@ func (p *Project) Logf(format string, args ...interface{}) {
999993
p.Log(fmt.Sprintf(format, args...))
1000994
}
1001995

996+
func (p *Project) detachScriptInfoIfNotInferredRoot(path tspath.Path) {
997+
// We might not find the script info in case its not associated with the project any more
998+
// and project graph was not updated (eg delayed update graph in case of files changed/deleted on the disk)
999+
if scriptInfo := p.host.GetScriptInfoByPath(path); scriptInfo != nil &&
1000+
(p.kind != KindInferred || !p.isRoot(scriptInfo)) {
1001+
scriptInfo.detachFromProject(p)
1002+
}
1003+
}
1004+
10021005
func (p *Project) Close() {
10031006
p.mu.Lock()
10041007
defer p.mu.Unlock()
10051008

10061009
if p.program != nil {
10071010
for _, sourceFile := range p.program.GetSourceFiles() {
10081011
p.host.DocumentRegistry().ReleaseDocument(sourceFile, p.program.GetCompilerOptions())
1009-
if scriptInfo := p.host.GetScriptInfoByPath(sourceFile.Path()); scriptInfo != nil {
1010-
scriptInfo.detachFromProject(p)
1011-
}
1012+
// Detach script info if its not root or is root of non inferred project
1013+
p.detachScriptInfoIfNotInferredRoot(sourceFile.Path())
10121014
}
10131015
p.program = nil
1014-
} else if p.kind == KindInferred {
1016+
}
1017+
1018+
if p.kind == KindInferred {
10151019
// Release root script infos for inferred projects.
10161020
for path := range p.rootFileNames.Keys() {
10171021
if info := p.host.GetScriptInfoByPath(path); info != nil {

internal/project/projectlifetime_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ func TestProjectLifetime(t *testing.T) {
162162
service.OpenFile("/home/projects/TS/p2/src/index.ts", files["/home/projects/TS/p2/src/index.ts"].(string), core.ScriptKindTS, "/home/projects/TS/p2")
163163
assert.Equal(t, len(service.Projects()), 1)
164164
assert.Assert(t, service.InferredProject(tspath.Path("")) == nil)
165-
assert.Assert(t, service.UnrootedInferredProject() == nil)
166165
assert.Assert(t, service.GetScriptInfoByPath(tspath.ToPath("/home/projects/TS/p1/src/index.ts", host.GetCurrentDirectory(), host.FS().UseCaseSensitiveFileNames())) == nil)
167166
assert.Assert(t, service.InferredProject(tspath.ToPath("/home/projects/TS/p2", host.GetCurrentDirectory(), host.FS().UseCaseSensitiveFileNames())) != nil)
168167
})

internal/project/scriptinfo.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package project
22

33
import (
44
"slices"
5+
"sync"
56

67
"github.com/microsoft/typescript-go/internal/core"
78
"github.com/microsoft/typescript-go/internal/ls"
@@ -25,7 +26,8 @@ type ScriptInfo struct {
2526
matchesDiskText bool
2627
deferredDelete bool
2728

28-
containingProjects []*Project
29+
containingProjectsMu sync.RWMutex
30+
containingProjects []*Project
2931

3032
fs vfs.FS
3133
}
@@ -68,6 +70,12 @@ func (s *ScriptInfo) Version() int {
6870
return s.version
6971
}
7072

73+
func (s *ScriptInfo) ContainingProjects() []*Project {
74+
s.containingProjectsMu.RLock()
75+
defer s.containingProjectsMu.RUnlock()
76+
return slices.Clone(s.containingProjects)
77+
}
78+
7179
func (s *ScriptInfo) reloadIfNeeded() {
7280
if s.pendingReloadFromDisk {
7381
if newText, ok := s.fs.ReadFile(s.fileName); ok {
@@ -97,6 +105,15 @@ func (s *ScriptInfo) close(fileExists bool) {
97105
s.pendingReloadFromDisk = true
98106
s.markContainingProjectsAsDirty()
99107
}
108+
109+
s.containingProjectsMu.Lock()
110+
defer s.containingProjectsMu.Unlock()
111+
for _, project := range slices.Clone(s.containingProjects) {
112+
if project.kind == KindInferred && project.isRoot(s) {
113+
project.RemoveFile(s, fileExists)
114+
s.detachFromProjectLocked(project)
115+
}
116+
}
100117
}
101118

102119
func (s *ScriptInfo) setText(newText string) {
@@ -106,6 +123,8 @@ func (s *ScriptInfo) setText(newText string) {
106123
}
107124

108125
func (s *ScriptInfo) markContainingProjectsAsDirty() {
126+
s.containingProjectsMu.RLock()
127+
defer s.containingProjectsMu.RUnlock()
109128
for _, project := range s.containingProjects {
110129
project.MarkFileAsDirty(s.path)
111130
}
@@ -115,7 +134,9 @@ func (s *ScriptInfo) markContainingProjectsAsDirty() {
115134
// and returns true if the script info was newly attached.
116135
func (s *ScriptInfo) attachToProject(project *Project) bool {
117136
if !s.isAttached(project) {
137+
s.containingProjectsMu.Lock()
118138
s.containingProjects = append(s.containingProjects, project)
139+
s.containingProjectsMu.Unlock()
119140
if project.compilerOptions.PreserveSymlinks != core.TSTrue {
120141
s.ensureRealpath(project.FS())
121142
}
@@ -126,13 +147,17 @@ func (s *ScriptInfo) attachToProject(project *Project) bool {
126147
}
127148

128149
func (s *ScriptInfo) isAttached(project *Project) bool {
150+
s.containingProjectsMu.RLock()
151+
defer s.containingProjectsMu.RUnlock()
129152
return slices.Contains(s.containingProjects, project)
130153
}
131154

132155
func (s *ScriptInfo) isOrphan() bool {
133156
if s.deferredDelete {
134157
return true
135158
}
159+
s.containingProjectsMu.RLock()
160+
defer s.containingProjectsMu.RUnlock()
136161
for _, project := range s.containingProjects {
137162
if !project.isOrphan() {
138163
return false
@@ -148,6 +173,8 @@ func (s *ScriptInfo) editContent(change ls.TextChange) {
148173

149174
func (s *ScriptInfo) ensureRealpath(fs vfs.FS) {
150175
if s.realpath == "" {
176+
s.containingProjectsMu.RLock()
177+
defer s.containingProjectsMu.RUnlock()
151178
if len(s.containingProjects) == 0 {
152179
panic("scriptInfo must be attached to a project before calling ensureRealpath")
153180
}
@@ -168,17 +195,25 @@ func (s *ScriptInfo) getRealpathIfDifferent() (tspath.Path, bool) {
168195
}
169196

170197
func (s *ScriptInfo) detachAllProjects() {
198+
s.containingProjectsMu.Lock()
199+
defer s.containingProjectsMu.Unlock()
171200
for _, project := range s.containingProjects {
172201
// !!!
173202
// if (isConfiguredProject(p)) {
174203
// p.getCachedDirectoryStructureHost().addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted);
175204
// }
176-
project.RemoveFile(s, false /*fileExists*/, false /*detachFromProject*/)
205+
project.RemoveFile(s, false /*fileExists*/)
177206
}
178207
s.containingProjects = nil
179208
}
180209

181210
func (s *ScriptInfo) detachFromProject(project *Project) {
211+
s.containingProjectsMu.Lock()
212+
defer s.containingProjectsMu.Unlock()
213+
s.detachFromProjectLocked(project)
214+
}
215+
216+
func (s *ScriptInfo) detachFromProjectLocked(project *Project) {
182217
if index := slices.Index(s.containingProjects, project); index != -1 {
183218
s.containingProjects = slices.Delete(s.containingProjects, index, index+1)
184219
}
@@ -193,6 +228,8 @@ func (s *ScriptInfo) delayReloadNonMixedContentFile() {
193228
}
194229

195230
func (s *ScriptInfo) containedByDeferredClosedProject() bool {
231+
s.containingProjectsMu.RLock()
232+
defer s.containingProjectsMu.RUnlock()
196233
return slices.IndexFunc(s.containingProjects, func(project *Project) bool {
197234
return project.deferredClose
198235
}) != -1

0 commit comments

Comments
 (0)