@@ -14,7 +14,8 @@ internal sealed class ExceptionPipeline :
14
14
15
15
private long _disposedState ;
16
16
17
- private int _processingState ;
17
+ private long _processingState ;
18
+ private const long WriteFlag = unchecked ( ( long ) 0x8000000000000000 ) ;
18
19
19
20
public ExceptionPipeline ( ExceptionSourceBase exceptionSource , Action < ExceptionPipelineBuilder > configure )
20
21
{
@@ -31,15 +32,19 @@ public ExceptionPipeline(ExceptionSourceBase exceptionSource, Action<ExceptionPi
31
32
public void Start ( )
32
33
{
33
34
DisposableHelper . ThrowIfDisposed < ExceptionPipeline > ( ref _disposedState ) ;
35
+ _processingState = 0 ;
34
36
35
37
_exceptionSource . ExceptionAvailable += ExceptionSource_ExceptionAvailable ;
36
38
}
37
39
38
40
public void Stop ( )
39
41
{
42
+ // We must do this first to prevent any new exception callbacks.
40
43
_exceptionSource . ExceptionAvailable -= ExceptionSource_ExceptionAvailable ;
41
44
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 ) )
43
48
{
44
49
// Wait for all exceptions to be processed
45
50
Thread . Sleep ( 100 ) ;
@@ -55,9 +60,32 @@ private void ExceptionSource_ExceptionAvailable(object? sender, ExceptionAvailab
55
60
// Synchronous execution is required for scenarios where the exception needs to be held
56
61
// at the site of where it is thrown before allowing it to unwind (e.g. capturing a dump of the exception).
57
62
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
+
58
87
try
59
88
{
60
- Interlocked . Increment ( ref _processingState ) ;
61
89
_exceptionHandler . Invoke (
62
90
args . Exception ,
63
91
new ExceptionPipelineExceptionContext (
0 commit comments