-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix ArgumentNullException under a race condition in analyzer driver #65083
Conversation
Fixes dotnet#59988 Fixes [AB#1654606](https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1654606) Do not free state if it is still being processed for analysis, i.e. StateKind is 'InProcess'. This can happen in rare cases when multiple threads are trying to analyze the same symbol/syntax/operation, such that one thread has finished all the analyzer callbacks, but before this thread marks the state as complete and invokes this FreeState call, second thread starts processing the same symbol/syntax/operation and sets the StateKind to 'InProcess' for the same state object. If we free the state here in the first thread, then this can lead to data corruption/exceptions in the second thread. For example, see dotnet#59988. Note that our current approach of not freeing the state for StateKind.InProcess leads to a leak, as we will never return this state object to the pool. However, this should happen in extremely rare cases as described above, so it should be fine. In future, if we do see negative performance impact from this leak, we can do a more complex state tracking and ensure that we free this state object and return it to the pool once the StateKind is reset to StateKind.ReadyToProcess state.
src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs
Outdated
Show resolved
Hide resolved
src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs
Outdated
Show resolved
Hide resolved
@dotnet/roslyn-compiler for reviews |
src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.AnalyzerStateData.cs
Outdated
Show resolved
Hide resolved
Done with review pass (commit 2) |
This reverts commit 280eb3a.
@@ -375,9 +397,9 @@ public bool TryStartProcessingEvent(CompilationEvent compilationEvent, [NotNullW | |||
return TryStartProcessingEntity(compilationEvent, _pendingEvents, _analyzerStateDataPool, out state); | |||
} | |||
|
|||
public void MarkEventComplete(CompilationEvent compilationEvent) | |||
public bool TryMarkEventComplete(CompilationEvent compilationEvent) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I converted all the methods that mark completion of events/symbols/declaration/syntax from void MarkXXXComplete(...)
to bool TryMarkXXXComplete(...)
…is - InProcess to ReadyToProcess to FullyProcessed
AnalyzerStateData? analyzerState = null; | ||
|
||
try | ||
if (TryStartProcessingEvent(compilationEvent, analyzer, analysisScope, analysisState, out var analyzerState)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AlekseyTs State machine transitions should now be very clear:
- We attempt to
TryStartProcessingEvent
to get hold of analyzer state, withStateKind.InProcess
. - If we succeed, then we attempt to execute relevant analyzer callbacks for the compilation event
- After executing callbacks, we transition the state to
StateKind.ReadyToProcess
state in the finally block surrounding execute actions method - Finally, we attempt to mark completion of the event for the analyzer and try to transition to
StateKind.FullyProcessed
and return true only if we succeed. - If we fail at Step 1, which happens when either another thread is executing steps 2-4 OR analyzer state has already completed and transitioned to
FullyProcessed
, we don't attempt to execute analysis but instead returnIsEventComplete
to handle the latter case.
src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.AnalyzerStateData.cs
Outdated
Show resolved
Hide resolved
src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs
Show resolved
Hide resolved
src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs
Show resolved
Hide resolved
Should we be taking this code path if we "failed" in the loop above? In reply to: 1309059500 Refers to: src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs:2665 in 0f841ca. [](commit_id = 0f841ca, deletion_comment = False) |
Done with review pass (commit 7). |
@@ -56,7 +57,7 @@ public void ResetToReadyState() | |||
|
|||
public virtual void Free() | |||
{ | |||
this.StateKind = StateKind.ReadyToProcess; | |||
Debug.Assert(StateKind == StateKind.ReadyToProcess); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this will change to below after we implement #65330:
Debug.Assert(StateKind == StateKind.AttemptingToComplete);
this.StateKind = StateKind.ReadyToProcess;
Thanks, I missed this comment. Updated to also check |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM (commit 9)
Thanks @AlekseyTs @dotnet/roslyn-compiler for second review. |
@dotnet/roslyn-compiler Can I please get a second review? |
src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs
Show resolved
Hide resolved
Should that be "Do not mark ... "? |
Yes, updated the PR description. Thanks. |
Port #65083 to release/dev17.4 branch
Fixes #59988
Fixes AB#1654606
Do not mark declaration/symbol/event as fully processed when the state is still being processed by another thread. This can happen in rare cases when multiple threads are trying to analyze the same symbol/syntax/operation, such that one thread has finished all the analyzer callbacks, but before this thread attempts to mark the state as complete, second thread starts processing the same symbol/syntax/operation and sets the StateKind to 'InProcess' for the same state object. With this PR, we do not mark the state as complete for such cases and instead let subsequent thread attempt to mark completion.