@@ -36,6 +36,7 @@ internal static class ActivityHelper
3636 /// </summary>
3737 public const string ActivityKey = "__AspnetActivity__" ;
3838
39+ private const int MaxActivityStackSize = 128 ;
3940 private static readonly DiagnosticListener AspNetListener = new DiagnosticListener ( AspNetListenerName ) ;
4041
4142 /// <summary>
@@ -66,14 +67,52 @@ public static Activity RestoreCurrentActivity(Activity root)
6667 return childActivity ;
6768 }
6869
70+ /// <summary>
71+ /// Stops the activity and notifies listeners about it.
72+ /// </summary>
73+ /// <param name="activity">Activity to stop.</param>
74+ /// <param name="context">Current HttpContext.</param>
75+ /// <returns>True if activity was found in the stack, false otherwise.</returns>
6976 public static bool StopAspNetActivity ( Activity activity , HttpContext context )
7077 {
71- if ( activity != null && Activity . Current != null )
78+ var currentActivity = Activity . Current ;
79+ if ( activity != null && currentActivity != null )
7280 {
7381 // silently stop all child activities before activity
74- while ( Activity . Current != activity && Activity . Current != null )
82+ int iteration = 0 ;
83+ while ( currentActivity != activity )
7584 {
76- Activity . Current . Stop ( ) ;
85+ currentActivity . Stop ( ) ;
86+ var newCurrentActivity = Activity . Current ;
87+
88+ if ( newCurrentActivity == null )
89+ {
90+ break ;
91+ }
92+
93+ // there could be a case when request or any child activity is stopped
94+ // from the child execution context. In this case, Activity is present in the Current Stack,
95+ // but is finished, i.e. stopping it has no effect on the Current.
96+ if ( newCurrentActivity == currentActivity )
97+ {
98+ // We could not reach our 'activity' in the stack and have to report 'lost activity'
99+ // if child activity is broken, we can still stop the root one that we own to clean up
100+ // all resources
101+ AspNetTelemetryCorrelationEventSource . Log . FinishedActivityIsDetected ( currentActivity . Id , currentActivity . OperationName ) ;
102+ activity . Stop ( ) ;
103+ return false ;
104+ }
105+
106+ // We also protect from endless loop with the MaxActivityStackSize
107+ // in case it would ever be possible to have cycles in the Activity stack.
108+ if ( iteration ++ == MaxActivityStackSize )
109+ {
110+ AspNetTelemetryCorrelationEventSource . Log . ActivityStackIsTooDeep ( currentActivity . Id , currentActivity . OperationName ) ;
111+ activity . Stop ( ) ;
112+ return false ;
113+ }
114+
115+ currentActivity = newCurrentActivity ;
77116 }
78117
79118 // if activity is in the stack, stop it with Stop event
@@ -89,6 +128,11 @@ public static bool StopAspNetActivity(Activity activity, HttpContext context)
89128 return false ;
90129 }
91130
131+ /// <summary>
132+ /// Notifies listeners that activity was ended and lost during execution.
133+ /// </summary>
134+ /// <param name="activity">Activity to notify about.</param>
135+ /// <param name="context">Current HttpContext.</param>
92136 public static void StopLostActivity ( Activity activity , HttpContext context )
93137 {
94138 if ( activity != null )
@@ -99,6 +143,11 @@ public static void StopLostActivity(Activity activity, HttpContext context)
99143 }
100144 }
101145
146+ /// <summary>
147+ /// Creates root (first level) activity that describes incoming request.
148+ /// </summary>
149+ /// <param name="context">Current HttpContext.</param>
150+ /// <returns>New root activity.</returns>
102151 public static Activity CreateRootActivity ( HttpContext context )
103152 {
104153 if ( AspNetListener . IsEnabled ( ) && AspNetListener . IsEnabled ( AspNetActivityName ) )
@@ -117,6 +166,19 @@ public static Activity CreateRootActivity(HttpContext context)
117166 return null ;
118167 }
119168
169+ /// <summary>
170+ /// This should be called after the Activity starts and only for root activity of a request.
171+ /// </summary>
172+ /// <param name="context">Context to save context to.</param>
173+ /// <param name="activity">Activity to save.</param>
174+ internal static void SaveCurrentActivity ( HttpContext context , Activity activity )
175+ {
176+ Debug . Assert ( context != null ) ;
177+ Debug . Assert ( activity != null ) ;
178+
179+ context . Items [ ActivityKey ] = activity ;
180+ }
181+
120182 private static bool StartAspNetActivity ( Activity activity )
121183 {
122184 if ( AspNetListener . IsEnabled ( AspNetActivityName , activity , new { } ) )
@@ -136,19 +198,6 @@ private static bool StartAspNetActivity(Activity activity)
136198 return false ;
137199 }
138200
139- /// <summary>
140- /// This should be called after the Activity starts and only for root activity of a request.
141- /// </summary>
142- /// <param name="context">Context to save context to.</param>
143- /// <param name="activity">Activity to save.</param>
144- private static void SaveCurrentActivity ( HttpContext context , Activity activity )
145- {
146- Debug . Assert ( context != null ) ;
147- Debug . Assert ( activity != null ) ;
148-
149- context . Items [ ActivityKey ] = activity ;
150- }
151-
152201 private static void RemoveCurrentActivity ( HttpContext context )
153202 {
154203 Debug . Assert ( context != null ) ;
0 commit comments