Skip to content

Commit 487baf0

Browse files
authored
Fix concurrent extractionCache read/write (#4562)
1 parent d35cc5f commit 487baf0

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

internal/ls/autoimport/registry.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -963,7 +963,10 @@ func (b *registryBuilder) updateIndexes(ctx context.Context, change RegistryChan
963963

964964
// For packages whose main extraction yielded nothing, fall back to @types.
965965
for _, pkg := range typesFallbackCandidates {
966-
if extractionCache[pkg.realpath] != nil || seen[pkg.typesRealpath] {
966+
extractionMu.Lock()
967+
mainExtracted := extractionCache[pkg.realpath] != nil
968+
extractionMu.Unlock()
969+
if mainExtracted || seen[pkg.typesRealpath] {
967970
continue
968971
}
969972
seen[pkg.typesRealpath] = true

internal/ls/autoimport/registry_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,41 @@ const (
11291129
monorepoProjectRoot = "/home/src/autoimport-monorepo"
11301130
)
11311131

1132+
func TestUpdateIndexesConcurrentMapSafety(t *testing.T) {
1133+
t.Parallel()
1134+
const projectRoot = "/home/src/autoimport-fallback-race"
1135+
const packageCount = 40
1136+
1137+
files := map[string]any{
1138+
projectRoot + "/tsconfig.json": `{
1139+
"compilerOptions": { "module": "esnext", "target": "esnext", "strict": true }
1140+
}`,
1141+
projectRoot + "/index.ts": "export {};\n",
1142+
}
1143+
for i := range packageCount {
1144+
pkgDir := fmt.Sprintf("%s/node_modules/pkg%d", projectRoot, i)
1145+
files[pkgDir+"/package.json"] = fmt.Sprintf(`{"name":"pkg%d","version":"1.0.0","main":"index.js"}`, i)
1146+
files[pkgDir+"/index.js"] = "module.exports = {};\n"
1147+
typesDir := fmt.Sprintf("%s/node_modules/@types/pkg%d", projectRoot, i)
1148+
files[typesDir+"/package.json"] = fmt.Sprintf(`{"name":"@types/pkg%d","version":"1.0.0","types":"index.d.ts"}`, i)
1149+
files[typesDir+"/index.d.ts"] = fmt.Sprintf("export declare const foo%d: number;\n", i)
1150+
}
1151+
1152+
session, _ := projecttestutil.Setup(files)
1153+
t.Cleanup(session.Close)
1154+
1155+
ctx := context.Background()
1156+
indexURI := lsproto.DocumentUri("file://" + projectRoot + "/index.ts")
1157+
session.DidOpenFile(ctx, indexURI, 1, "export {};\n", lsproto.LanguageKindTypeScript)
1158+
1159+
_, err := session.GetCurrentLanguageServiceWithAutoImports(ctx, indexURI)
1160+
assert.NilError(t, err)
1161+
1162+
stats := autoImportStats(t, session)
1163+
nodeModulesBucket := singleBucket(t, stats.NodeModulesBuckets)
1164+
assert.Equal(t, nodeModulesBucket.ExportCount, packageCount)
1165+
}
1166+
11321167
func autoImportStats(t *testing.T, session *project.Session) *autoimport.CacheStats {
11331168
t.Helper()
11341169
snapshot := session.Snapshot()

0 commit comments

Comments
 (0)