From b585c84595ae6d385b98194fcb5dd55b9339c12d Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 7 Jun 2019 16:09:38 -0400 Subject: [PATCH] Add debug data to help diagnose HTTP2 test failure (dotnet/corefx#38345) 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 --- .../System/Threading/Tasks/GetStateMachineData.cs | 81 ++++++++++++++++++++++ .../FunctionalTests/HttpClientHandlerTest.Http2.cs | 1 + .../System.Net.Http.Functional.Tests.csproj | 5 +- 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/libraries/Common/tests/System/Threading/Tasks/GetStateMachineData.cs 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 index 0000000..00ad9a8 --- /dev/null +++ b/src/libraries/Common/tests/System/Threading/Tasks/GetStateMachineData.cs @@ -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; + +/// +/// 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); +/// +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(); + } + } +} diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs index e951d65..74176f3 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs @@ -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()) { diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj index be3b84d..51f84f45 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj @@ -1,4 +1,4 @@ - + {C85CF035-7804-41FF-9557-48B7C948B58D} $(DefineConstants);TargetsWindows @@ -80,6 +80,9 @@ Common\System\Threading\TrackingSynchronizationContext.cs + + Common\System\Threading\Tasks\GetStateMachineData.cs + Common\System\Threading\Tasks\TaskTimeoutExtensions.cs -- 2.7.4