Skip to content

Commit e994266

Browse files
committed
Use an interlocked many reader single writer to synchronize exceptions threads and pipeline stop calls
1 parent 627b9cb commit e994266

File tree

1 file changed

+31
-3
lines changed

1 file changed

+31
-3
lines changed

src/Microsoft.Diagnostics.Monitoring.StartupHook/Exceptions/Pipeline/ExceptionPipeline.cs

+31-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ internal sealed class ExceptionPipeline :
1414

1515
private long _disposedState;
1616

17-
private int _processingState;
17+
private long _processingState;
18+
private const long WriteFlag = unchecked((long)0x8000000000000000);
1819

1920
public ExceptionPipeline(ExceptionSourceBase exceptionSource, Action<ExceptionPipelineBuilder> configure)
2021
{
@@ -31,15 +32,19 @@ public ExceptionPipeline(ExceptionSourceBase exceptionSource, Action<ExceptionPi
3132
public void Start()
3233
{
3334
DisposableHelper.ThrowIfDisposed<ExceptionPipeline>(ref _disposedState);
35+
_processingState = 0;
3436

3537
_exceptionSource.ExceptionAvailable += ExceptionSource_ExceptionAvailable;
3638
}
3739

3840
public void Stop()
3941
{
42+
// We must do this first to prevent any new exception callbacks.
4043
_exceptionSource.ExceptionAvailable -= ExceptionSource_ExceptionAvailable;
4144

42-
while (0 != Interlocked.CompareExchange(ref _processingState, 0, 0) && !DisposableHelper.IsDisposed(ref _disposedState))
45+
// Wait until all outstanding exception callbacks are finished. Once _processingState reaches 0, we will set the WriteFlag.
46+
// This will prevent any further exception handlers from processing.
47+
while (0 != Interlocked.CompareExchange(ref _processingState, WriteFlag, 0) && !DisposableHelper.IsDisposed(ref _disposedState))
4348
{
4449
// Wait for all exceptions to be processed
4550
Thread.Sleep(100);
@@ -55,9 +60,32 @@ private void ExceptionSource_ExceptionAvailable(object? sender, ExceptionAvailab
5560
// Synchronous execution is required for scenarios where the exception needs to be held
5661
// at the site of where it is thrown before allowing it to unwind (e.g. capturing a dump of the exception).
5762

63+
SpinWait spinWait = new();
64+
65+
while (true)
66+
{
67+
long state = Interlocked.Read(ref _processingState);
68+
if ((state & WriteFlag) == WriteFlag)
69+
{
70+
// Stop has been requested but the event handler has already occurred. Return early.
71+
return;
72+
}
73+
if (DisposableHelper.IsDisposed(ref _disposedState))
74+
{
75+
return;
76+
}
77+
// Increment the value by 1 to indicate that we are doing work.
78+
// We cannot do a simple increment since it's possible for a Stop call to attempt to set the write bit
79+
// at this stage.
80+
if (Interlocked.CompareExchange(ref _processingState, state + 1, state) == state)
81+
{
82+
break;
83+
}
84+
spinWait.SpinOnce();
85+
}
86+
5887
try
5988
{
60-
Interlocked.Increment(ref _processingState);
6189
_exceptionHandler.Invoke(
6290
args.Exception,
6391
new ExceptionPipelineExceptionContext(

0 commit comments

Comments
 (0)