diff --git a/tsc/internal/compiler/checkerpool.go b/tsc/internal/compiler/checkerpool.go index e129951e423df..bc1c087dbec6b 100644 --- a/tsc/internal/compiler/checkerpool.go +++ b/tsc/internal/compiler/checkerpool.go @@ -367,7 +367,7 @@ func (p *checkerPool) getCheckerNonExclusive() (*checker.Checker, func()) { func (p *checkerPool) createCheckers() { p.createCheckersOnce.Do(func() { checkerCount := len(p.checkers) - wg := core.NewWorkGroup(p.program.SingleThreaded()) + wg := core.NewWorkGroup(core.WorkGroupConcurrency(p.program.SingleThreaded())) for i := range checkerCount { wg.Queue(func() { var tracer *checker.Tracer @@ -450,7 +450,7 @@ func (p *checkerPool) getImportAdjacency() [][]int { // making it safe to call `forEachCheckerParallel` from many threads simultaneously. func (p *checkerPool) forEachCheckerParallel(cb func(idx int, c *checker.Checker)) { p.createCheckers() - wg := core.NewWorkGroup(p.program.SingleThreaded()) + wg := core.NewWorkGroup(core.WorkGroupConcurrency(p.program.SingleThreaded())) for idx, checker := range p.checkers { wg.Queue(func() { p.locks[idx].Lock() @@ -477,7 +477,7 @@ func (p *checkerPool) forEachCheckerGroupDo(ctx context.Context, files []*ast.So p.createCheckers() checkerCount := len(p.checkers) - wg := core.NewWorkGroup(singleThreaded) + wg := core.NewWorkGroup(core.WorkGroupConcurrency(singleThreaded)) for checkerIdx := range checkerCount { wg.Queue(func() { p.locks[checkerIdx].Lock() diff --git a/tsc/internal/compiler/fileloader.go b/tsc/internal/compiler/fileloader.go index d4e159246c6ca..dd79d7ffe11e7 100644 --- a/tsc/internal/compiler/fileloader.go +++ b/tsc/internal/compiler/fileloader.go @@ -182,7 +182,7 @@ func processAllProgramFiles( defaultLibraryPath: opts.Host.DefaultLibraryPath(), caseSensitivity: opts.Host.FS().CaseSensitivity(), filesParser: &filesParser{ - wg: core.NewWorkGroup(singleThreaded), + wg: core.NewWorkGroup(core.WorkGroupConcurrency(singleThreaded)), maxDepth: maxNodeModuleJsDepth, }, rootTasks: make([]*parseTask, 0, len(rootFiles)+len(compilerOptions.Lib)), @@ -352,7 +352,7 @@ func (p *fileLoader) addProjectReferenceTasks(singleThreaded bool) { parser := &projectReferenceParser{ loader: p, - wg: core.NewWorkGroup(singleThreaded), + wg: core.NewWorkGroup(core.WorkGroupConcurrency(singleThreaded)), } rootTasks := createProjectReferenceParseTasks(projectReferences) parser.parse(rootTasks) diff --git a/tsc/internal/compiler/program.go b/tsc/internal/compiler/program.go index 2c7aa08030d8a..2db4ec49e269c 100644 --- a/tsc/internal/compiler/program.go +++ b/tsc/internal/compiler/program.go @@ -593,7 +593,7 @@ func (p *Program) SingleThreaded() bool { } func (p *Program) BindSourceFiles() { - wg := core.NewWorkGroup(p.SingleThreaded()) + wg := core.NewWorkGroup(core.WorkGroupConcurrency(p.SingleThreaded())) for _, file := range p.files { if !file.IsBound() { wg.Queue(func() { @@ -701,7 +701,7 @@ func (p *Program) collectDiagnostics(ctx context.Context, sourceFile *ast.Source func (p *Program) collectDiagnosticsFromFiles(ctx context.Context, sourceFiles []*ast.SourceFile, concurrent bool, collect func(context.Context, *ast.SourceFile) []*ast.Diagnostic) [][]*ast.Diagnostic { diagnostics := make([][]*ast.Diagnostic, len(sourceFiles)) - wg := core.NewWorkGroup(!concurrent || p.SingleThreaded()) + wg := core.NewWorkGroup(core.WorkGroupConcurrency(!concurrent || p.SingleThreaded())) for i, file := range sourceFiles { wg.Queue(func() { diagnostics[i] = collect(ctx, file) @@ -748,7 +748,7 @@ func (p *Program) collectCheckerDiagnosticsFromFiles(ctx context.Context, source diagnostics[fileIndex] = collect(ctx, c, file) }) } else { - wg := core.NewWorkGroup(p.SingleThreaded()) + wg := core.NewWorkGroup(core.WorkGroupConcurrency(p.SingleThreaded())) for i, file := range sourceFiles { if p.SkipTypeChecking(file, false) { continue @@ -1910,7 +1910,7 @@ func (p *Program) Emit(ctx context.Context, options EmitOptions) *EmitResult { return printer.NewTextWriter(newLine, 0) }, } - wg := core.NewWorkGroup(p.SingleThreaded()) + wg := core.NewWorkGroup(core.WorkGroupConcurrency(p.SingleThreaded())) var emitters []*emitter forceDtsEmit := options.EmitOnly == EmitOnlyBuilderSignature || options.ForceEmit && options.EmitOnly == EmitOnlyDts forceJsEmit := options.ForceEmit && options.EmitOnly == EmitOnlyJs diff --git a/tsc/internal/core/workgroup.go b/tsc/internal/core/workgroup.go index 73618a1a19fc1..dc3b27f2adf3b 100644 --- a/tsc/internal/core/workgroup.go +++ b/tsc/internal/core/workgroup.go @@ -17,16 +17,28 @@ type WorkGroup interface { RunAndWait() } -func NewWorkGroup(singleThreaded bool) WorkGroup { - if singleThreaded { +// A concurrency of 1 runs the functions on the goroutine that calls RunAndWait; 0 means no limit. +func NewWorkGroup(concurrency int) WorkGroup { + switch concurrency { + case 0: + return ¶llelWorkGroup{semaphore: UnlimitedSemaphore{}} + case 1: return &singleThreadedWorkGroup{} } - return ¶llelWorkGroup{} + return ¶llelWorkGroup{semaphore: NewLimitedSemaphore(concurrency)} +} + +func WorkGroupConcurrency(singleThreaded bool) int { + if singleThreaded { + return 1 + } + return 0 } type parallelWorkGroup struct { - done atomic.Bool - wg sync.WaitGroup + done atomic.Bool + wg sync.WaitGroup + semaphore Semaphore } var _ WorkGroup = (*parallelWorkGroup)(nil) @@ -37,6 +49,8 @@ func (w *parallelWorkGroup) Queue(fn func()) { } w.wg.Go(func() { + // Running functions call Queue, so waiting for a slot there would deadlock at the limit. + defer w.semaphore.Acquire()() fn() }) } diff --git a/tsc/internal/core/workgroup_test.go b/tsc/internal/core/workgroup_test.go new file mode 100644 index 0000000000000..f436b493c1a4a --- /dev/null +++ b/tsc/internal/core/workgroup_test.go @@ -0,0 +1,52 @@ +package core_test + +import ( + "runtime" + "sync" + "sync/atomic" + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/core" + "gotest.tools/v3/assert" +) + +func TestWorkGroupConcurrencyLimit(t *testing.T) { + t.Parallel() + const limit = 3 + const depth = 2 + const children = 4 + wg := core.NewWorkGroup(limit) + var running, maxRunning, completed atomic.Int32 + var allRootsRunning sync.WaitGroup + allRootsRunning.Add(limit) + var queue func(remaining int) + queue = func(remaining int) { + wg.Queue(func() { + now := running.Add(1) + for { + seen := maxRunning.Load() + if now <= seen || maxRunning.CompareAndSwap(seen, now) { + break + } + } + if remaining == depth { + allRootsRunning.Done() + allRootsRunning.Wait() + } + if remaining > 0 { + for range children { + queue(remaining - 1) + } + } + runtime.Gosched() + running.Add(-1) + completed.Add(1) + }) + } + for range limit { + queue(depth) + } + wg.RunAndWait() + assert.Equal(t, completed.Load(), int32(limit*(1+children+children*children))) + assert.Equal(t, maxRunning.Load(), int32(limit)) +} diff --git a/tsc/internal/execute/build/orchestrator.go b/tsc/internal/execute/build/orchestrator.go index db33676e55597..fd78c7ec7c28e 100644 --- a/tsc/internal/execute/build/orchestrator.go +++ b/tsc/internal/execute/build/orchestrator.go @@ -260,7 +260,7 @@ func (o *Orchestrator) GenerateGraphReusingOldTasks() { func (o *Orchestrator) GenerateGraph(oldTasks *collections.SyncMap[tspath.PathKey, *BuildTask]) { projects := o.opts.Command.ResolvedProjectPaths() // Parse all config files in parallel - wg := core.NewWorkGroup(o.opts.Command.CompilerOptions.SingleThreaded.IsTrue()) + wg := core.NewWorkGroup(core.WorkGroupConcurrency(o.opts.Command.CompilerOptions.SingleThreaded.IsTrue())) o.createBuildTasks(oldTasks, projects, wg) wg.RunAndWait() @@ -940,7 +940,7 @@ func (o *Orchestrator) rangeTasks(order []*BuildTask, f func(path tspath.PathKey if numRoutines == 1 { runTask() } else { - wg := core.NewWorkGroup(false) + wg := core.NewWorkGroup(0) for range numRoutines { wg.Queue(runTask) } diff --git a/tsc/internal/execute/incremental/affectedfileshandler.go b/tsc/internal/execute/incremental/affectedfileshandler.go index 5f8d918ceea9f..dab11844849ca 100644 --- a/tsc/internal/execute/incremental/affectedfileshandler.go +++ b/tsc/internal/execute/incremental/affectedfileshandler.go @@ -2,7 +2,7 @@ package incremental import ( "context" - "maps" + "runtime" "slices" "sync" "sync/atomic" @@ -88,12 +88,11 @@ func (h *affectedFilesHandler) updateShapeSignature(file *ast.SourceFile, useFil update := &updatedSignature{} update.mu.Lock() defer update.mu.Unlock() - // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate if existing, ok := h.updatedSignatures.LoadOrStore(file.PathKey(), update); ok { // Ensure calculations for existing ones are complete before using the value existing.mu.Lock() defer existing.mu.Unlock() - return false + return h.isChangedSignature(file.PathKey()) } info, _ := h.program.snapshot.fileInfos.Load(file.PathKey()) @@ -111,42 +110,45 @@ func (h *affectedFilesHandler) updateShapeSignature(file *ast.SourceFile, useFil return update.signature != prevSignature } -func (h *affectedFilesHandler) getFilesAffectedBy(path tspath.PathKey) []*ast.SourceFile { +func (h *affectedFilesHandler) collectFilesAffectedBy(path tspath.PathKey, wg core.WorkGroup, result *collections.SyncSet[*ast.SourceFile]) { file := h.program.program.GetSourceFileByPath(path) if file == nil { - return nil + return } if !h.updateShapeSignature(file, false) { - return []*ast.SourceFile{file} + result.Add(file) + return } if info, _ := h.program.snapshot.fileInfos.Load(file.PathKey()); info.affectsGlobalScope { h.hasAllFilesExcludingDefaultLibraryFile.Store(true) - return h.program.snapshot.getAllFilesExcludingDefaultLibraryFile(h.program.program, file) + for _, affectedFile := range h.program.snapshot.getAllFilesExcludingDefaultLibraryFile(h.program.program, file) { + result.Add(affectedFile) + } + return } + result.Add(file) if h.program.snapshot.options.IsolatedModules.IsTrue() { - return []*ast.SourceFile{file} + return } - // Now we need to if each file in the referencedBy list has a shape change as well. - // Because if so, its own referencedBy files need to be saved as well to make the - // emitting result consistent with files on disk. - seenFileNamesMap := h.forEachFileReferencedBy( - file, - func(currentFile *ast.SourceFile, currentPath tspath.PathKey) (queueForFile bool, fastReturn bool) { - // If the current file is not nil and has a shape change, we need to queue it for processing - if currentFile != nil && h.updateShapeSignature(currentFile, false) { - return true, false + h.collectReferencingFiles(file, wg, result) +} + +func (h *affectedFilesHandler) collectReferencingFiles(file *ast.SourceFile, wg core.WorkGroup, result *collections.SyncSet[*ast.SourceFile]) { + for path := range h.program.snapshot.referencedMap.getReferencedBy(file.PathKey()) { + currentFile := h.program.program.GetSourceFileByPath(path) + if currentFile == nil || !result.AddIfAbsent(currentFile) { + continue + } + wg.Queue(func() { + if h.updateShapeSignature(currentFile, false) { + h.collectReferencingFiles(currentFile, wg, result) } - return false, false - }, - ) - // Return array of values that needs emit - return core.Filter(slices.Collect(maps.Values(seenFileNamesMap)), func(file *ast.SourceFile) bool { - return file != nil - }) + }) + } } func (h *affectedFilesHandler) forEachFileReferencedBy(file *ast.SourceFile, fn func(currentFile *ast.SourceFile, currentPath tspath.PathKey) (queueForFile bool, fastReturn bool)) map[tspath.PathKey]*ast.SourceFile { @@ -362,13 +364,13 @@ func collectAllAffectedFiles(ctx context.Context, program *Program) { } handler := affectedFilesHandler{ctx: ctx, program: program} - wg := core.NewWorkGroup(handler.program.program.SingleThreaded()) + // Starting every signature at once raises peak memory without finishing sooner. + concurrency := core.IfElse(program.program.SingleThreaded(), 1, runtime.GOMAXPROCS(0)) + wg := core.NewWorkGroup(concurrency) var result collections.SyncSet[*ast.SourceFile] program.snapshot.changedFilesSet.Range(func(file tspath.PathKey) bool { wg.Queue(func() { - for _, affectedFile := range handler.getFilesAffectedBy(file) { - result.Add(affectedFile) - } + handler.collectFilesAffectedBy(file, wg, &result) }) return true }) @@ -380,7 +382,7 @@ func collectAllAffectedFiles(ctx context.Context, program *Program) { // For all the affected files, get all the files that would need to change their dts or js files, // update their diagnostics - wg = core.NewWorkGroup(program.program.SingleThreaded()) + wg = core.NewWorkGroup(concurrency) emitKind := GetFileEmitKind(program.snapshot.options) result.Range(func(file *ast.SourceFile) bool { // remove the cached semantic diagnostics and handle dts emit and js emit if needed diff --git a/tsc/internal/execute/incremental/affectedfileshandler_internal_test.go b/tsc/internal/execute/incremental/affectedfileshandler_internal_test.go new file mode 100644 index 0000000000000..84dc7a6b570a0 --- /dev/null +++ b/tsc/internal/execute/incremental/affectedfileshandler_internal_test.go @@ -0,0 +1,98 @@ +package incremental + +import ( + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/ast" + "github.com/microsoft/TypeScript/tsc/internal/bundled" + "github.com/microsoft/TypeScript/tsc/internal/collections" + "github.com/microsoft/TypeScript/tsc/internal/compiler" + "github.com/microsoft/TypeScript/tsc/internal/core" + "github.com/microsoft/TypeScript/tsc/internal/tsoptions" + "github.com/microsoft/TypeScript/tsc/internal/tspath" + "github.com/microsoft/TypeScript/tsc/internal/vfs" + "github.com/microsoft/TypeScript/tsc/internal/vfs/vfstest" + "gotest.tools/v3/assert" +) + +func buildTestProgram(t *testing.T, fs vfs.FS) *Program { + t.Helper() + host := compiler.NewCompilerHost(fs, bundled.LibPath(), nil, nil, nil) + config, diagnostics := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, nil, fs, nil) + assert.Equal(t, len(diagnostics), 0) + old := ReadBuildInfoProgram(config, NewBuildInfoReader(host), host) + return NewProgram(compiler.NewProgram(compiler.ProgramOptions{Config: config, Host: host}), old, CreateHost(host), nil, false) +} + +func buildAndEmitTestProgram(t *testing.T, fs vfs.FS) { + t.Helper() + program := buildTestProgram(t, fs) + assert.Equal(t, len(program.GetSemanticDiagnostics(t.Context(), nil)), 0) + assert.Equal(t, len(program.Emit(t.Context(), compiler.EmitOptions{}).Diagnostics), 0) +} + +func TestUpdateShapeSignatureCachedResult(t *testing.T) { + t.Parallel() + if !bundled.Embedded { + t.Skip("bundled files are not embedded") + } + fs := bundled.WrapFS(vfstest.FromMap(map[string]string{ + "/tsconfig.json": `{"compilerOptions":{"strict":true,"noEmit":true,"incremental":true,"skipLibCheck":true}}`, + "/a.ts": `export const a = 1;`, + "/b.ts": `import { a } from "./a"; export const b = a; declare global { interface Window { fromB: string; } }`, + "/c.ts": `export const c = window.fromB;`, + "/d.ts": `import { b } from "./b"; export const d = 2;`, + }, tspath.CaseSensitive)) + buildAndEmitTestProgram(t, fs) + // A rebuild replaces the version-based signatures of a, b and d with computed ones. + assert.NilError(t, fs.AppendFile("/a.ts", "\n// edit\n")) + buildAndEmitTestProgram(t, fs) + + assert.NilError(t, fs.WriteFile("/a.ts", `export const a = "changed";`)) + assert.NilError(t, fs.AppendFile("/b.ts", "\n// edit\n")) + program := buildTestProgram(t, fs) + h := affectedFilesHandler{ctx: t.Context(), program: program} + b := program.program.GetSourceFile("/b.ts") + c := program.program.GetSourceFile("/c.ts") + d := program.program.GetSourceFile("/d.ts") + + assert.Assert(t, h.updateShapeSignature(b, false)) + assert.Assert(t, h.updateShapeSignature(b, false), "cached result must still report the changed signature") + assert.Assert(t, !h.updateShapeSignature(d, false)) + assert.Assert(t, !h.updateShapeSignature(d, false)) + + wg := core.NewWorkGroup(1) + var result collections.SyncSet[*ast.SourceFile] + wg.Queue(func() { h.collectFilesAffectedBy(b.PathKey(), wg, &result) }) + wg.RunAndWait() + assert.Assert(t, h.hasAllFilesExcludingDefaultLibraryFile.Load(), "global-scope invalidation must not depend on which traversal computed the signature") + assert.Assert(t, result.Has(c)) +} + +func TestCollectFilesAffectedByChangedDefaultLibrary(t *testing.T) { + t.Parallel() + if !bundled.Embedded { + t.Skip("bundled files are not embedded") + } + const libPath = "/node_modules/@typescript/lib-dom/index.d.ts" + fs := bundled.WrapFS(vfstest.FromMap(map[string]string{ + "/tsconfig.json": `{"compilerOptions":{"strict":true,"noEmit":true,"incremental":true,"lib":["es5","dom"],"libReplacement":true}}`, + libPath: `interface DOMInterface { value: string; }`, + "/a.ts": `export const a = 1;`, + }, tspath.CaseSensitive)) + buildAndEmitTestProgram(t, fs) + + assert.NilError(t, fs.WriteFile(libPath, `interface DOMInterface { value: number; }`)) + program := buildTestProgram(t, fs) + h := affectedFilesHandler{ctx: t.Context(), program: program} + lib := program.program.GetSourceFile(libPath) + assert.Assert(t, program.program.IsSourceFileDefaultLibrary(lib.PathKey())) + + wg := core.NewWorkGroup(1) + var result collections.SyncSet[*ast.SourceFile] + wg.Queue(func() { h.collectFilesAffectedBy(lib.PathKey(), wg, &result) }) + wg.RunAndWait() + assert.Assert(t, h.hasAllFilesExcludingDefaultLibraryFile.Load()) + assert.Assert(t, result.Has(program.program.GetSourceFile("/a.ts"))) + assert.Assert(t, !result.Has(lib)) +} diff --git a/tsc/internal/execute/incremental/emitfileshandler.go b/tsc/internal/execute/incremental/emitfileshandler.go index 36711a652bbe3..a1a0164a587b5 100644 --- a/tsc/internal/execute/incremental/emitfileshandler.go +++ b/tsc/internal/execute/incremental/emitfileshandler.go @@ -121,7 +121,7 @@ func (h *emitFilesHandler) emitFilesIncremental(options compiler.EmitOptions) [] return nil } - wg := core.NewWorkGroup(h.program.program.SingleThreaded()) + wg := core.NewWorkGroup(core.WorkGroupConcurrency(h.program.program.SingleThreaded())) h.program.snapshot.affectedFilesPendingEmit.Range(func(path tspath.PathKey, emitKind FileEmitKind) bool { affectedFile := h.program.program.GetSourceFileByPath(path) if affectedFile == nil || !h.program.program.SourceFileMayBeEmitted(affectedFile, false) { diff --git a/tsc/internal/execute/incremental/programtosnapshot.go b/tsc/internal/execute/incremental/programtosnapshot.go index 3470c2dc41082..6773ccfa6aa51 100644 --- a/tsc/internal/execute/incremental/programtosnapshot.go +++ b/tsc/internal/execute/incremental/programtosnapshot.go @@ -88,7 +88,7 @@ func (t *toProgramSnapshot) computeProgramFileChanges() { t.snapshot.options.SkipDefaultLibCheck.IsTrue() == t.oldProgram.snapshot.options.SkipDefaultLibCheck.IsTrue() files := t.program.GetSourceFiles() - wg := core.NewWorkGroup(t.program.SingleThreaded()) + wg := core.NewWorkGroup(core.WorkGroupConcurrency(t.program.SingleThreaded())) for _, file := range files { wg.Queue(func() { versionText := file.Text() diff --git a/tsc/internal/execute/tsctests/runner.go b/tsc/internal/execute/tsctests/runner.go index 2e65925135e82..55d0de9392fb9 100644 --- a/tsc/internal/execute/tsctests/runner.go +++ b/tsc/internal/execute/tsctests/runner.go @@ -92,7 +92,7 @@ func (test *tscInput) run(t *testing.T, scenario string) { for index, do := range test.edits { sys.clearOutput() - wg := core.NewWorkGroup(false) + wg := core.NewWorkGroup(0) var nonIncrementalSys *TestSys commandLineArgs := core.IfElse(do.commandLineArgs == nil, test.commandLineArgs, do.commandLineArgs) wg.Queue(func() { diff --git a/tsc/internal/ls/crossproject.go b/tsc/internal/ls/crossproject.go index 82fb86321f1a5..1e3b79b636cc6 100644 --- a/tsc/internal/ls/crossproject.go +++ b/tsc/internal/ls/crossproject.go @@ -76,7 +76,7 @@ func (defaultLs *LanguageService) handleCrossProject[Req lsproto.HasTextDocument _, searched := results.Load(project.Id()) return !searched } - wg := core.NewWorkGroup(false) + wg := core.NewWorkGroup(0) var errMu sync.Mutex var enqueueItem func(item projectAndTextDocumentPosition) var panicsOccurred []string @@ -231,7 +231,7 @@ func (defaultLs *LanguageService) handleCrossProject[Req lsproto.HasTextDocument return resp, err } - wg = core.NewWorkGroup(false) + wg = core.NewWorkGroup(0) hasMoreWork := false if defaultDefinition != nil { var requestedProjectTrees collections.Set[tspath.PathKey] diff --git a/tsc/internal/project/projectcollectionbuilder.go b/tsc/internal/project/projectcollectionbuilder.go index 99872b4cb6eee..4f58770d9e9fe 100644 --- a/tsc/internal/project/projectcollectionbuilder.go +++ b/tsc/internal/project/projectcollectionbuilder.go @@ -745,7 +745,7 @@ func (b *ProjectCollectionBuilder) DidRequestProjectTrees(projectTreeRequest *Pr }) var seenProjects collections.SyncSet[ConfiguredProjectID] - wg := core.NewWorkGroup(false) + wg := core.NewWorkGroup(0) for _, projectId := range currentProjects { wg.Queue(func() { if entry, ok := b.configuredProjects.Load(projectId); ok { diff --git a/tsc/internal/project/snapshotfs.go b/tsc/internal/project/snapshotfs.go index 3e95fb3709231..64ef21181a061 100644 --- a/tsc/internal/project/snapshotfs.go +++ b/tsc/internal/project/snapshotfs.go @@ -514,7 +514,7 @@ func (s *snapshotFSBuilder) invalidateNodeModulesCache() { func (s *snapshotFSBuilder) markDirtyFiles(change FileChangeSummary) FileChangeSummary { if change.Changed.Len() > 0 { var filteredChanged collections.SyncSet[lsproto.DocumentUri] - wg := core.NewWorkGroup(false) + wg := core.NewWorkGroup(0) for uri := range change.Changed.Keys() { path := s.caseSensitivity.PathKey(uri.FileName().AsPath()) if file := s.fs.GetFileByPath(uri.FileName(), path); file != nil && file.IsOverlay() { diff --git a/tsc/internal/transformers/tstransforms/importelision_test.go b/tsc/internal/transformers/tstransforms/importelision_test.go index b6654955a7c5d..719a0c5e80d42 100644 --- a/tsc/internal/transformers/tstransforms/importelision_test.go +++ b/tsc/internal/transformers/tstransforms/importelision_test.go @@ -123,7 +123,7 @@ func (p *fakeProgram) SourceFiles() []*ast.SourceFile { } func (p *fakeProgram) BindSourceFiles() { - wg := core.NewWorkGroup(p.singleThreaded) + wg := core.NewWorkGroup(core.WorkGroupConcurrency(p.singleThreaded)) for _, file := range p.files { if !file.IsBound() { wg.Queue(func() {