Skip to content

Conversation

@weswigham
Copy link
Member

And, so we can test for these races more simply, enable the incremental mode program construction+emit codepath for compiler tests with @incremental: true specified.

There are other GetCheckerForFile calls in internal/execute/incremental that probably need to be swapped to GetCheckerForFileExclusive, as using basically any checker functions in a concurrent context without a lock on it could lead to a data race (thanks to lazy caching everywhere inside the checker), but the single call I changed here is definitely the root cause of the crash in the issue. I could just go swap them all, but we don't have regression tests for those sites to prove that's actually needed. The LS has request-scoped checker associations and assumes one thread per request, so is unlikely to need the per-checker lock functionality (though it is here, since I made it part of the pool interface), unless the number of in-flight requests exceeds the maximum number of available checkers (which can only happen if there's a disconnect between request workgroup max paralleism and max checkers afaik).

Fixes #1470

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a race condition in incremental compilation mode where concurrent access to the same checker for alias following operations could cause data races. The fix introduces thread-safe exclusive checker access via locks and enables incremental mode testing through compiler tests with @incremental: true.

  • Adds GetCheckerForFileExclusive methods to checker pool interfaces, which lock a mutex per checker to prevent concurrent access
  • Updates affectedfileshandler.go to use exclusive checker access for alias following operations
  • Extends the ProgramLike interface with additional methods needed for incremental program compatibility
  • Enables incremental mode in test infrastructure when @incremental: true is specified

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
testdata/tests/cases/compiler/incrementalConcurrentSafeAliasFollowing.ts New regression test with multiple files and export aliases to reproduce the race condition
testdata/baselines/reference/compiler/incrementalConcurrentSafeAliasFollowing.* Expected baseline outputs for the new test
internal/project/checkerpool.go Adds per-checker mutex locks and GetCheckerForFileExclusive implementation for the project-level checker pool
internal/compiler/checkerpool.go Adds per-checker mutex locks and GetCheckerForFileExclusive implementation for the compiler-level checker pool
internal/compiler/program.go Exposes GetCheckerForFileExclusive and extends ProgramLike interface
internal/execute/incremental/affectedfileshandler.go Uses exclusive checker access to prevent race condition during alias following
internal/execute/incremental/program.go Implements additional ProgramLike interface methods for compatibility
internal/testutil/harnessutil/harnessutil.go Enables incremental program construction in tests, changes Program field to ProgramLike
internal/testutil/tsbaseline/*.go Updates to work with ProgramLike interface instead of concrete Program type
internal/testrunner/compiler_runner.go Adds type switching logic to extract underlying Program from ProgramLike


func (p *checkerPool) GetCheckerForFileExclusive(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
c, done := p.GetCheckerForFile(ctx, file)
idx := slices.Index(p.checkers, c)
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for when slices.Index returns -1 (checker not found). If the checker is not in the slice, this will cause a panic when accessing p.locks[idx]. Although this should never happen in normal operation, defensive programming suggests adding a check or assertion.

Suggested change
idx := slices.Index(p.checkers, c)
idx := slices.Index(p.checkers, c)
if idx == -1 {
panic("checker not found in checker pool")
}

Copilot uses AI. Check for mistakes.

//// [a.tsbuildinfo]
{
"version": "TEST",
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error: Unexpected token

Copilot uses AI. Check for mistakes.
@weswigham weswigham enabled auto-merge November 11, 2025 19:30
This reverts commit b42a8b5.
@weswigham weswigham added this pull request to the merge queue Nov 11, 2025
Merged via the queue into microsoft:main with commit 3e97dac Nov 11, 2025
22 checks passed
@weswigham weswigham deleted the incremental-thread-safety branch November 11, 2025 20:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fatal error: concurrent map read and map write

4 participants