2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
4
using System ;
5
+ using System . Threading ;
5
6
6
7
namespace Microsoft . Diagnostics . Monitoring . StartupHook . Exceptions . Pipeline
7
8
{
@@ -13,6 +14,9 @@ internal sealed class ExceptionPipeline :
13
14
14
15
private long _disposedState ;
15
16
17
+ private long _processingState ;
18
+ private const long WriteFlag = unchecked ( ( long ) 0x8000000000000000 ) ;
19
+
16
20
public ExceptionPipeline ( ExceptionSourceBase exceptionSource , Action < ExceptionPipelineBuilder > configure )
17
21
{
18
22
ArgumentNullException . ThrowIfNull ( exceptionSource ) ;
@@ -28,10 +32,25 @@ public ExceptionPipeline(ExceptionSourceBase exceptionSource, Action<ExceptionPi
28
32
public void Start ( )
29
33
{
30
34
DisposableHelper . ThrowIfDisposed < ExceptionPipeline > ( ref _disposedState ) ;
35
+ _processingState = 0 ;
31
36
32
37
_exceptionSource . ExceptionAvailable += ExceptionSource_ExceptionAvailable ;
33
38
}
34
39
40
+ public void Stop ( )
41
+ {
42
+ // We must do this first to prevent any new exception callbacks.
43
+ _exceptionSource . ExceptionAvailable -= ExceptionSource_ExceptionAvailable ;
44
+
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 ) )
48
+ {
49
+ // Wait for all exceptions to be processed
50
+ Thread . Sleep ( 100 ) ;
51
+ }
52
+ }
53
+
35
54
private void ExceptionSource_ExceptionAvailable ( object ? sender , ExceptionAvailableEventArgs args )
36
55
{
37
56
// DESIGN: While async patterns are typically favored over synchronous patterns,
@@ -40,12 +59,45 @@ private void ExceptionSource_ExceptionAvailable(object? sender, ExceptionAvailab
40
59
// (e.g. EventSource provides events but diagnostic pipe events are queued and asynchronously emitted).
41
60
// Synchronous execution is required for scenarios where the exception needs to be held
42
61
// at the site of where it is thrown before allowing it to unwind (e.g. capturing a dump of the exception).
43
- _exceptionHandler . Invoke (
44
- args . Exception ,
45
- new ExceptionPipelineExceptionContext (
46
- args . Timestamp ,
47
- args . ActivityId ,
48
- args . ActivityIdFormat ) ) ;
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
+
87
+ try
88
+ {
89
+ _exceptionHandler . Invoke (
90
+ args . Exception ,
91
+ new ExceptionPipelineExceptionContext (
92
+ args . Timestamp ,
93
+ args . ActivityId ,
94
+ args . ActivityIdFormat ) ) ;
95
+
96
+ }
97
+ finally
98
+ {
99
+ Interlocked . Decrement ( ref _processingState ) ;
100
+ }
49
101
}
50
102
51
103
0 commit comments