Skip to content

Conversation

@weswigham
Copy link
Member

This is done in the simplest way possible - the checker just makes a lock on construction that is used within the EmitResolver the checker creates (instead of the resolver making it) that also gets returned by the NewChecker constructor so it can also be used as the canonical lock for that checker by other threadsafe checker API callers.

Once again, fixes #2061 maybe - hard to know without a reproduction, but I can definitely see how parallel file emit could invoke both a pooled checker use via incremental/handleNoEmit and an EmitResolver at the same time.

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 unifies the locking mechanism between checker pool borrows and EmitResolver scopes to prevent potential race conditions. The key change is that each Checker now owns a single sync.Mutex that is shared with its EmitResolver, ensuring consistent synchronization across both exclusive pool operations and emit resolver access.

Key Changes:

  • Modified NewChecker to return both the checker and its lock, making the lock externally accessible
  • Changed EmitResolver.checkerMu from an owned mutex value to a pointer that references the checker's lock
  • Updated compiler.checkerPool to store lock pointers obtained from checkers instead of maintaining separate locks

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
internal/checker/checker.go Added lock field to Checker struct and modified NewChecker to return the lock alongside the checker instance
internal/checker/emitresolver.go Changed checkerMu from sync.Mutex to *sync.Mutex and initialized it with the checker's lock
internal/compiler/checkerpool.go Changed locks from value slice to pointer slice and populated it with locks returned from NewChecker
internal/project/checkerpool.go Updated NewChecker call to handle the new return signature (lock discarded as this pool doesn't use exclusive locking yet)
internal/transformers/tstransforms/importelision_test.go Updated test code to handle the new NewChecker return signature

}

func NewChecker(program Program) *Checker {
func NewChecker(program Program) (*Checker, *sync.Mutex) {
Copy link
Member

Choose a reason for hiding this comment

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

This feels weird to me; can we just have a method that returns &c.mu? Then callers don't need a separate list of locks either, just the checker they want to lock?

Copy link
Member

Choose a reason for hiding this comment

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

Or is this intended to force callers to reckon with the locking?

Copy link
Member Author

@weswigham weswigham Nov 13, 2025

Choose a reason for hiding this comment

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

Precisely that - it forces the callers to reckon with the lock in some way or intentionally discard it. (And basically no caller other than tests should discard it)

@weswigham weswigham added this pull request to the merge queue Nov 13, 2025
Merged via the queue into microsoft:main with commit 944083a Nov 13, 2025
22 checks passed
@weswigham weswigham deleted the unify-locks branch November 13, 2025 22:33
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.

HandleNoEmitOnError/GetDiagnosticsOfAnyProgram cause concurrent checker use

2 participants