Add debug data to help diagnose HTTP2 test failure (dotnet/corefx#38345)
authorStephen Toub <stoub@microsoft.com>
Fri, 7 Jun 2019 20:09:38 +0000 (16:09 -0400)
committerGitHub <noreply@github.com>
Fri, 7 Jun 2019 20:09:38 +0000 (16:09 -0400)
The GoAwayFrame_AbortAllPendingStreams_StreamFailWithExpectedException test has been frequently hanging on some Linux distros.  This adds some diagnostics to help track it down in CI: every minute, it'll dump out the fields of the async state machine, which if nothing else will let us see which await it's stuck on and what is the state of each of the tasks.

Commit migrated from https://github.com/dotnet/corefx/commit/410a162f68eb319e8066ac7bf2d7bcd6e2ce47d0

src/libraries/Common/tests/System/Threading/Tasks/GetStateMachineData.cs [new file with mode: 0644]
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs
src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj

diff --git a/src/libraries/Common/tests/System/Threading/Tasks/GetStateMachineData.cs b/src/libraries/Common/tests/System/Threading/Tasks/GetStateMachineData.cs
new file mode 100644 (file)
index 0000000..00ad9a8
--- /dev/null
@@ -0,0 +1,81 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+/// <summary>
+/// Fragile trick for getting a description of the current state of a .NET Core async method state machine.
+/// To use, first await FetchAsync to get back an object:
+///     object box = await GetStateMachineData.FetchAsync();
+/// and then when the description is desired, use:
+///     string description = GetStateMachineData.Describe(box);
+/// </summary>
+namespace System.Threading.Tasks
+{
+    internal sealed class GetStateMachineData : ICriticalNotifyCompletion
+    {
+        private object _box;
+
+        private GetStateMachineData() { }
+        public static GetStateMachineData FetchAsync() => new GetStateMachineData();
+
+        public GetStateMachineData GetAwaiter() => this;
+        public bool IsCompleted => false;
+        public void OnCompleted(Action continuation) => UnsafeOnCompleted(continuation);
+        public void UnsafeOnCompleted(Action continuation)
+        {
+            _box = continuation.Target;
+            Task.Run(continuation);
+        }
+        public object GetResult() => _box;
+
+        public static string Describe(object box)
+        {
+            if (box is null)
+            {
+                return "(Couldn't get state machine box.)";
+            }
+
+            FieldInfo stateMachineField = box.GetType().GetField("StateMachine", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+            if (stateMachineField is null)
+            {
+                return $"(Couldn't get state machine field from {box}.";
+            }
+
+            IAsyncStateMachine stateMachine = stateMachineField.GetValue(box) as IAsyncStateMachine;
+            if (stateMachine is null)
+            {
+                return $"(Null state machine from {box}.)";
+            }
+
+            Type stateMachineType = stateMachine.GetType();
+            FieldInfo[] fields = stateMachineType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+
+            var sb = new StringBuilder();
+            sb.AppendLine(stateMachineType.FullName);
+            foreach (FieldInfo fi in fields)
+            {
+                sb.AppendLine($"    {fi.Name}: {ToString(fi.GetValue(stateMachine))}");
+            }
+            return sb.ToString();
+        }
+
+        private static string ToString(object value)
+        {
+            if (value is null)
+            {
+                return "(null)";
+            }
+
+            if (value is Task t)
+            {
+                return $"Status: {t.Status}, Exception: {t.Exception?.InnerException}";
+            }
+
+            return value.ToString();
+        }
+    }
+}
index e951d657a538e35aa7562cc4e261b5a0097f19bc..74176f3c6fd6b5f4ba6a9601e9185965996091ca 100644 (file)
@@ -798,6 +798,7 @@ namespace System.Net.Http.Functional.Tests
         [ConditionalFact(nameof(SupportsAlpn))]
         public async Task GoAwayFrame_AbortAllPendingStreams_StreamFailWithExpectedException()
         {
+            using (new Timer(s => Console.WriteLine(GetStateMachineData.Describe(s)), await GetStateMachineData.FetchAsync(), 60_000, 60_000))
             using (Http2LoopbackServer server = Http2LoopbackServer.CreateServer())
             using (HttpClient client = CreateHttpClient())
             {
index be3b84d94bd7951722d23f114b57f25ed064c3bb..51f84f4580610679491462f399a65aff9be17cd5 100644 (file)
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <ProjectGuid>{C85CF035-7804-41FF-9557-48B7C948B58D}</ProjectGuid>
     <DefineConstants Condition="'$(TargetsWindows)'=='true'">$(DefineConstants);TargetsWindows</DefineConstants>
@@ -80,6 +80,9 @@
     <Compile Include="$(CommonTestPath)\System\Threading\TrackingSynchronizationContext.cs">
       <Link>Common\System\Threading\TrackingSynchronizationContext.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)\System\Threading\Tasks\GetStateMachineData.cs">
+      <Link>Common\System\Threading\Tasks\GetStateMachineData.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)\System\Threading\Tasks\TaskTimeoutExtensions.cs">
       <Link>Common\System\Threading\Tasks\TaskTimeoutExtensions.cs</Link>
     </Compile>