11// Copyright (c) Microsoft Corporation. All rights reserved.
22// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33
4- using System ;
5- using System . Collections ;
64using System . Collections . Generic ;
7- using System . Globalization ;
8- using System . Linq ;
9- using System . Reflection ;
10- using System . Runtime . CompilerServices ;
11- using System . Threading . Tasks ;
125
136namespace Microsoft . VisualStudio . Threading ;
147
@@ -17,15 +10,6 @@ namespace Microsoft.VisualStudio.Threading;
1710/// </summary>
1811internal static class InternalUtilities
1912{
20- /// <summary>
21- /// The substring that should be inserted before each async return stack frame.
22- /// </summary>
23- /// <remarks>
24- /// When printing synchronous callstacks, .NET begins each frame with " at ".
25- /// When printing async return stack, we use this to indicate continuations.
26- /// </remarks>
27- private const string AsyncReturnStackPrefix = " -> " ;
28-
2913 /// <summary>
3014 /// Removes an element from the middle of a queue without disrupting the other elements.
3115 /// </summary>
@@ -60,229 +44,4 @@ internal static bool RemoveMidQueue<T>(this Queue<T> queue, T valueToRemove)
6044
6145 return found ;
6246 }
63-
64- /// <summary>
65- /// Walk the continuation objects inside "async state machines" to generate the return call stack.
66- /// FOR DIAGNOSTIC PURPOSES ONLY.
67- /// </summary>
68- /// <param name="continuationDelegate">The delegate that represents the head of an async continuation chain.</param>
69- internal static IEnumerable < string > GetAsyncReturnStackFrames ( this Delegate continuationDelegate )
70- {
71- IAsyncStateMachine ? stateMachine = FindAsyncStateMachine ( continuationDelegate ) ;
72- if ( stateMachine is null )
73- {
74- // Did not find the async state machine, so returns the method name as top frame and stop walking.
75- yield return GetDelegateLabel ( continuationDelegate ) ;
76- yield break ;
77- }
78-
79- do
80- {
81- object ? state = GetStateMachineFieldValueOnSuffix ( stateMachine , "__state" ) ;
82- yield return string . Format (
83- CultureInfo . CurrentCulture ,
84- "{2}{0} (state: {1}, address: 0x{3:X8})" ,
85- stateMachine . GetType ( ) . FullName ,
86- state ,
87- AsyncReturnStackPrefix ,
88- ( long ) GetAddress ( stateMachine ) ) ; // the long cast allows hex formatting
89-
90- Delegate [ ] ? continuationDelegates = FindContinuationDelegates ( stateMachine ) . ToArray ( ) ;
91- if ( continuationDelegates . Length == 0 )
92- {
93- break ;
94- }
95-
96- // Consider: It's possible but uncommon scenario to have multiple "async methods" being awaiting for one "async method".
97- // Here we just choose the first awaiting "async method" as that should be good enough for postmortem.
98- // In future we might want to revisit this to cover the other awaiting "async methods".
99- stateMachine = continuationDelegates . Select ( ( d ) => FindAsyncStateMachine ( d ) )
100- . FirstOrDefault ( ( s ) => s is object ) ;
101- if ( stateMachine is null )
102- {
103- yield return GetDelegateLabel ( continuationDelegates . First ( ) ) ;
104- }
105- }
106- while ( stateMachine is object ) ;
107- }
108-
109- /// <summary>
110- /// A helper method to get the label of the given delegate.
111- /// </summary>
112- private static string GetDelegateLabel ( Delegate invokeDelegate )
113- {
114- Requires . NotNull ( invokeDelegate , nameof ( invokeDelegate ) ) ;
115-
116- MethodInfo ? method = invokeDelegate . GetMethodInfo ( ) ;
117- if ( invokeDelegate . Target is object )
118- {
119- string instanceType = string . Empty ;
120- if ( ! ( method ? . DeclaringType ? . Equals ( invokeDelegate . Target . GetType ( ) ) ?? false ) )
121- {
122- instanceType = " (" + invokeDelegate . Target . GetType ( ) . FullName + ")" ;
123- }
124-
125- return string . Format (
126- CultureInfo . CurrentCulture ,
127- "{3}{0}.{1}{2} (target address: 0x{4:X" + ( IntPtr . Size * 2 ) + "})" ,
128- method ? . DeclaringType ? . FullName ,
129- method ? . Name ,
130- instanceType ,
131- AsyncReturnStackPrefix ,
132- GetAddress ( invokeDelegate . Target ) . ToInt64 ( ) ) ; // the cast allows hex formatting
133- }
134-
135- return string . Format (
136- CultureInfo . CurrentCulture ,
137- "{2}{0}.{1}" ,
138- method ? . DeclaringType ? . FullName ,
139- method ? . Name ,
140- AsyncReturnStackPrefix ) ;
141- }
142-
143- /// <summary>
144- /// Gets the memory address of a given object.
145- /// </summary>
146- /// <param name="value">The object to get the address for.</param>
147- /// <returns>The memory address.</returns>
148- /// <remarks>
149- /// This method works when GCHandle will refuse because the type of object is a non-blittable type.
150- /// However, this method provides no guarantees that the address will remain valid for the caller,
151- /// so it is only useful for diagnostics and when we don't expect addresses to be changing much any more.
152- /// </remarks>
153- private static unsafe IntPtr GetAddress ( object value ) => new IntPtr ( Unsafe . AsPointer ( ref value ) ) ;
154-
155- /// <summary>
156- /// A helper method to find the async state machine from the given delegate.
157- /// </summary>
158- private static IAsyncStateMachine ? FindAsyncStateMachine ( Delegate invokeDelegate )
159- {
160- Requires . NotNull ( invokeDelegate , nameof ( invokeDelegate ) ) ;
161-
162- if ( invokeDelegate . Target is object )
163- {
164- // Some delegates are wrapped with a ContinuationWrapper object. We have to unwrap that in those cases.
165- // In testing, this m_continuation field jump is only required when the debugger is attached -- weird.
166- // I suspect however that it's a natural behavior of the async state machine (when there are >1 continuations perhaps).
167- // So we check for the case in all cases.
168- if ( GetFieldValue ( invokeDelegate . Target , "m_continuation" ) is Action continuation )
169- {
170- invokeDelegate = continuation ;
171- if ( invokeDelegate . Target is null )
172- {
173- return null ;
174- }
175- }
176-
177- var stateMachine = GetFieldValue ( invokeDelegate . Target , "m_stateMachine" ) as IAsyncStateMachine ;
178- return stateMachine ;
179- }
180-
181- return null ;
182- }
183-
184- /// <summary>
185- /// This is the core to find the continuation delegate(s) inside the given async state machine.
186- /// The chain of objects is like this: async state machine -> async method builder -> task -> continuation object -> action.
187- /// </summary>
188- /// <remarks>
189- /// There are 3 types of "async method builder": AsyncVoidMethodBuilder, AsyncTaskMethodBuilder, AsyncTaskMethodBuilder<T>.
190- /// We don't cover AsyncVoidMethodBuilder as it is used rarely and it can't be awaited either;
191- /// AsyncTaskMethodBuilder is a wrapper on top of AsyncTaskMethodBuilder<VoidTaskResult>.
192- /// </remarks>
193- private static IEnumerable < Delegate > FindContinuationDelegates ( IAsyncStateMachine stateMachine )
194- {
195- Requires . NotNull ( stateMachine , nameof ( stateMachine ) ) ;
196-
197- object ? builder = GetStateMachineFieldValueOnSuffix ( stateMachine , "__builder" ) ;
198- if ( builder is null )
199- {
200- yield break ;
201- }
202-
203- object ? task = GetFieldValue ( builder , "m_task" ) ;
204- if ( task is null )
205- {
206- // Probably this builder is an instance of "AsyncTaskMethodBuilder", so we need to get its inner "AsyncTaskMethodBuilder<VoidTaskResult>"
207- builder = GetFieldValue ( builder , "m_builder" ) ;
208- if ( builder is object )
209- {
210- task = GetFieldValue ( builder , "m_task" ) ;
211- }
212- }
213-
214- if ( task is null )
215- {
216- yield break ;
217- }
218-
219- // "task" might be an instance of the type deriving from "Task", but "m_continuationObject" is a private field in "Task",
220- // so we need to use "typeof(Task)" to access "m_continuationObject".
221- FieldInfo ? continuationField = typeof ( Task ) . GetTypeInfo ( ) . GetDeclaredField ( "m_continuationObject" ) ;
222- if ( continuationField is null )
223- {
224- yield break ;
225- }
226-
227- object ? continuationObject = continuationField . GetValue ( task ) ;
228- if ( continuationObject is null )
229- {
230- yield break ;
231- }
232-
233- if ( continuationObject is IEnumerable items )
234- {
235- foreach ( object ? item in items )
236- {
237- Delegate ? action = item as Delegate ?? GetFieldValue ( item ! , "m_action" ) as Delegate ;
238- if ( action is object )
239- {
240- yield return action ;
241- }
242- }
243- }
244- else
245- {
246- Delegate ? action = continuationObject as Delegate ?? GetFieldValue ( continuationObject , "m_action" ) as Delegate ;
247- if ( action is object )
248- {
249- yield return action ;
250- }
251- }
252- }
253-
254- /// <summary>
255- /// A helper method to get field's value given the object and the field name.
256- /// </summary>
257- private static object ? GetFieldValue ( object obj , string fieldName )
258- {
259- Requires . NotNull ( obj , nameof ( obj ) ) ;
260- Requires . NotNullOrEmpty ( fieldName , nameof ( fieldName ) ) ;
261-
262- FieldInfo ? field = obj . GetType ( ) . GetTypeInfo ( ) . GetDeclaredField ( fieldName ) ;
263- if ( field is object )
264- {
265- return field . GetValue ( obj ) ;
266- }
267-
268- return null ;
269- }
270-
271- /// <summary>
272- /// The field names of "async state machine" are not fixed; the workaround is to find the field based on the suffix.
273- /// </summary>
274- private static object ? GetStateMachineFieldValueOnSuffix ( IAsyncStateMachine stateMachine , string suffix )
275- {
276- Requires . NotNull ( stateMachine , nameof ( stateMachine ) ) ;
277- Requires . NotNullOrEmpty ( suffix , nameof ( suffix ) ) ;
278-
279- IEnumerable < FieldInfo > ? fields = stateMachine . GetType ( ) . GetTypeInfo ( ) . DeclaredFields ;
280- FieldInfo ? field = fields . FirstOrDefault ( ( f ) => f . Name . EndsWith ( suffix , StringComparison . Ordinal ) ) ;
281- if ( field is object )
282- {
283- return field . GetValue ( stateMachine ) ;
284- }
285-
286- return null ;
287- }
28847}
0 commit comments