Make CapturingDiagnosticsLogger safe for concurrent writers - #20553
Draft
xperiandri wants to merge 2 commits into
Draft
xperiandri wants to merge 2 commits into
xperiandri wants to merge 2 commits into
Conversation
Graph type-checking in the transparent compiler starts each node with
Async.Start, so every node inherits the parent AsyncMemoize job's
"cache" logger and commits its own delayed diagnostics into it from its
own thread. Racing ResizeArray.Add calls can leave Count above the
backing array's length, and every later ToArray() in
CommitDelayedDiagnostics then throws ArgumentException ("Source array
was not long enough"), which poisons the cached job.
Add under a lock, and snapshot under the same lock for Diagnostics and
CommitDelayedDiagnostics; the replay stays outside the lock.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
❗ Release notes requiredYou can open this PR in browser to add release notes: open in github.dev
|
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
CapturingDiagnosticsLoggerkeeps its diagnostics in an unsynchronizedResizeArray, but the transparent compiler writes into one instance from several threads at once. Two racingAddcalls can leave the list with aCountlarger than its backing array. From then on everyCommitDelayedDiagnosticsthrows fromToArray():The broken list belongs to a completed
AsyncMemoizejob, so the job stays cached and every later request for that result rethrows. In Visual Studio this shows up as a continuous stream of first-chance exceptions from the diagnostic analyzers and a sluggish UI while debugging. In the debugger, the logger of the failingParseAndCheckFileInProjectjob had_size = 18against_items.Length = 16.Here is one path that writes into a single logger from several threads at once:
AsyncMemoize.Getinstalls a freshCapturingDiagnosticsLogger "cache"as the ambient logger for the job's computation.ComputeTcLastFilerunsprocessTypeCheckingGraphinside that computation.processGraphAsyncstarts every graph node withAsync.Start, so each node inherits that"cache"logger through theAsyncLocal.ComputeTcIntermediate, itself anAsyncMemoize.Get. When that call finishes, it runsCommitDelayedDiagnostics DiagnosticsThreadStatics.DiagnosticsLoggeron the node's thread and so writes into the shared parent logger at the same time as its sibling nodes.MultipleDiagnosticsLoggers.Parallelavoids this by giving each computation its own logger, but graph processing does not use it. Every such path ends up in the logger, so the fix makes the logger itself safe for concurrent use:DiagnosticSinkadds the diagnostic and updates the error count under a lock.DiagnosticsandCommitDelayedDiagnosticscopy the list under the same lock.CommitDelayedDiagnosticsthen replays the copy outside the lock, so a sink that reports back into this logger still works and no other logger's lock is taken while this one is held.Order across concurrent writers is still whatever order they arrive in, as before. The change stops diagnostics from being lost and the list from being corrupted.
Covered cases
errorRcalls fromParallel.Forinto oneCapturingDiagnosticsLoggerproduce 100,000 entries inErrorCount,Diagnosticsand the committed target. Without the lock the test fails.Checklist
🤖 Generated with Claude Code