From: Justin Anderson Date: Fri, 10 Sep 2021 20:40:20 +0000 (-0700) Subject: Enable ProcessInfo2 in DiagnosticsClient. (#2564) X-Git-Tag: submit/tizen/20220302.040122~21^2^2~89 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=a1dcf218de100905d1152f5588e241d276aecc55;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Enable ProcessInfo2 in DiagnosticsClient. (#2564) Update tests to reflect when entrypoint is available. --- diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs index 3a420f9df..c054e02a3 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs @@ -274,16 +274,12 @@ namespace Microsoft.Diagnostics.NETCore.Client internal ProcessInfo GetProcessInfo() { - // RE: https://github.com/dotnet/runtime/issues/54083 - // If the GetProcessInfo2 command is sent too early, it will crash the runtime instance. - // Disable the usage of the command until that issue is fixed. - // Attempt to get ProcessInfo v2 - //ProcessInfo processInfo = TryGetProcessInfo2(); - //if (null != processInfo) - //{ - // return processInfo; - //} + ProcessInfo processInfo = TryGetProcessInfo2(); + if (null != processInfo) + { + return processInfo; + } IpcMessage request = CreateProcessInfoMessage(); using IpcResponse response = IpcClient.SendMessageGetContinuation(_endpoint, request); @@ -292,16 +288,12 @@ namespace Microsoft.Diagnostics.NETCore.Client internal async Task GetProcessInfoAsync(CancellationToken token) { - // RE: https://github.com/dotnet/runtime/issues/54083 - // If the GetProcessInfo2 command is sent too early, it will crash the runtime instance. - // Disable the usage of the command until that issue is fixed. - // Attempt to get ProcessInfo v2 - //ProcessInfo processInfo = await TryGetProcessInfo2Async(token); - //if (null != processInfo) - //{ - // return processInfo; - //} + ProcessInfo processInfo = await TryGetProcessInfo2Async(token); + if (null != processInfo) + { + return processInfo; + } IpcMessage request = CreateProcessInfoMessage(); using IpcResponse response = await IpcClient.SendMessageGetContinuationAsync(_endpoint, request, token).ConfigureAwait(false); diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/DiagnosticPortsHelper.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/DiagnosticPortsHelper.cs new file mode 100644 index 000000000..c5b312279 --- /dev/null +++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/DiagnosticPortsHelper.cs @@ -0,0 +1,44 @@ +// 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.IO; +using System.Runtime.InteropServices; +using Xunit.Abstractions; + +namespace Microsoft.Diagnostics.NETCore.Client +{ + internal static class DiagnosticPortsHelper + { + private const string DiagnosticPortsEnvName = "DOTNET_DiagnosticPorts"; + private const string DefaultDiagnosticPortSuspendEnvName = "DOTNET_DefaultDiagnosticPortSuspend"; + + /// + /// Creates a unique server name to avoid collisions from simultaneous running tests + /// or potentially abandoned socket files. + /// + public static string CreateServerTransportName() + { + string transportName = "DOTNET_DIAGSERVER_TESTS_" + Path.GetRandomFileName(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return transportName; + } + else + { + return Path.Combine(Path.GetTempPath(), transportName); + } + } + + public static void SetDiagnosticPort(this TestRunner runner, string transportName, bool suspend) + { + string suspendArgument = suspend ? "suspend" : "nosuspend"; + runner.AddEnvVar(DiagnosticPortsEnvName, $"{transportName},connect,{suspendArgument};"); + } + + public static void SuspendDefaultDiagnosticPort(this TestRunner runner) + { + runner.AddEnvVar(DefaultDiagnosticPortSuspendEnvName, "1"); + } + } +} diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs index 172d27165..868890489 100644 --- a/src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs +++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs @@ -11,44 +11,65 @@ namespace Microsoft.Diagnostics.NETCore.Client { public class GetProcessInfoTests { - private readonly ITestOutputHelper output; + private readonly ITestOutputHelper _output; public GetProcessInfoTests(ITestOutputHelper outputHelper) { - output = outputHelper; + _output = outputHelper; } [Fact] - public Task BasicProcessInfoTest() + public Task BasicProcessInfoNoSuspendTest() { - return BasicProcessInfoTestCore(useAsync: false); + return BasicProcessInfoTestCore(useAsync: false, suspend: false); } [Fact] - public Task BasicProcessInfoTestAsync() + public Task BasicProcessInfoNoSuspendTestAsync() { - return BasicProcessInfoTestCore(useAsync: true); + return BasicProcessInfoTestCore(useAsync: true, suspend: false); } - private async Task BasicProcessInfoTestCore(bool useAsync) + [Fact] + public Task BasicProcessInfoSuspendTest() + { + return BasicProcessInfoTestCore(useAsync: false, suspend: true); + } + + [Fact] + public Task BasicProcessInfoSuspendTestAsync() + { + return BasicProcessInfoTestCore(useAsync: true, suspend: true); + } + + private async Task BasicProcessInfoTestCore(bool useAsync, bool suspend) { - using TestRunner runner = new TestRunner(CommonHelper.GetTraceePathWithArgs(targetFramework: "net5.0"), output); + using TestRunner runner = new TestRunner(CommonHelper.GetTraceePathWithArgs(targetFramework: "net5.0"), _output); + if (suspend) + { + runner.SuspendDefaultDiagnosticPort(); + } runner.Start(); try { DiagnosticsClientApiShim clientShim = new DiagnosticsClientApiShim(new DiagnosticsClient(runner.Pid), useAsync); - ProcessInfo processInfo = await clientShim.GetProcessInfo(); + // While suspended, the runtime will not provide entrypoint information. + if (suspend) + { + ProcessInfo processInfoBeforeResume = await clientShim.GetProcessInfo(); + ValidateProcessInfo(runner.Pid, processInfoBeforeResume); + Assert.True(string.IsNullOrEmpty(processInfoBeforeResume.ManagedEntrypointAssemblyName)); - Assert.NotNull(processInfo); - Assert.Equal(runner.Pid, (int)processInfo.ProcessId); - Assert.NotNull(processInfo.CommandLine); - Assert.NotNull(processInfo.OperatingSystem); - Assert.NotNull(processInfo.ProcessArchitecture); - //Assert.Equal("Tracee", processInfo.ManagedEntrypointAssemblyName); - //Version clrVersion = ParseVersionRemoveLabel(processInfo.ClrProductVersionString); - //Assert.True(clrVersion >= new Version(6, 0, 0)); + await clientShim.ResumeRuntime(); + } + + // The entrypoint information is available some short time after the runtime + // begins to execute. Retry getting process information until entrypoint is available. + ProcessInfo processInfo = await GetProcessInfoWithEntrypointAsync(clientShim); + ValidateProcessInfo(runner.Pid, processInfo); + Assert.Equal("Tracee", processInfo.ManagedEntrypointAssemblyName); } finally { @@ -56,6 +77,55 @@ namespace Microsoft.Diagnostics.NETCore.Client } } + /// + /// Get process information with entrypoint information with exponential backoff on retries. + /// + private async Task GetProcessInfoWithEntrypointAsync(DiagnosticsClientApiShim shim) + { + int retryMilliseconds = 5; + int currentAttempt = 1; + const int maxAttempts = 10; + + _output.WriteLine("Getting process info with entrypoint:"); + while (currentAttempt <= maxAttempts) + { + _output.WriteLine("- Attempt {0} of {1}.", currentAttempt, maxAttempts); + + ProcessInfo processInfo = await shim.GetProcessInfo(); + Assert.NotNull(processInfo); + + if (!string.IsNullOrEmpty(processInfo.ManagedEntrypointAssemblyName)) + { + _output.WriteLine("Got process info with entrypoint."); + return processInfo; + } + + currentAttempt++; + + if (currentAttempt != maxAttempts) + { + _output.WriteLine(" Waiting {0} ms.", retryMilliseconds); + + await Task.Delay(retryMilliseconds); + + retryMilliseconds = Math.Min(2 * retryMilliseconds, 500); + } + } + + throw new InvalidOperationException("Unable to get process info with entrypoint."); + } + + private static void ValidateProcessInfo(int expectedProcessId, ProcessInfo processInfo) + { + Assert.NotNull(processInfo); + Assert.Equal(expectedProcessId, (int)processInfo.ProcessId); + Assert.NotNull(processInfo.CommandLine); + Assert.NotNull(processInfo.OperatingSystem); + Assert.NotNull(processInfo.ProcessArchitecture); + Version clrVersion = ParseVersionRemoveLabel(processInfo.ClrProductVersionString); + Assert.True(clrVersion >= new Version(6, 0, 0)); + } + private static Version ParseVersionRemoveLabel(string versionString) { Assert.NotNull(versionString); diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/RemoteTestExecution.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/RemoteTestExecution.cs index 22e74e967..a3b592339 100644 --- a/src/tests/Microsoft.Diagnostics.NETCore.Client/RemoteTestExecution.cs +++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/RemoteTestExecution.cs @@ -53,7 +53,7 @@ namespace Microsoft.Diagnostics.NETCore.Client.UnitTests TestRunner runner = new TestRunner(commandLine, outputHelper, redirectError: true, redirectInput: true); if (!string.IsNullOrEmpty(reversedServerTransportName)) { - runner.AddReversedServer(reversedServerTransportName); + runner.SetDiagnosticPort(reversedServerTransportName, suspend: false); } runner.Start(testProcessTimeout: 60_000); diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerHelper.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerHelper.cs deleted file mode 100644 index d6c0955bd..000000000 --- a/src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerHelper.cs +++ /dev/null @@ -1,47 +0,0 @@ -// 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.IO; -using System.Runtime.InteropServices; -using Xunit.Abstractions; - -namespace Microsoft.Diagnostics.NETCore.Client -{ - internal static class ReversedServerHelper - { - /// - /// Creates a unique server name to avoid collisions from simultaneous running tests - /// or potentially abandoned socket files. - /// - public static string CreateServerTransportName() - { - string transportName = "DOTNET_DIAGSERVER_TESTS_" + Path.GetRandomFileName(); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return transportName; - } - else - { - return Path.Combine(Path.GetTempPath(), transportName); - } - } - - /// - /// Starts the Tracee executable while enabling connection to reverse diagnostics server. - /// - public static TestRunner StartTracee(ITestOutputHelper _outputHelper, string transportName) - { - var runner = new TestRunner(CommonHelper.GetTraceePathWithArgs(targetFramework: "net5.0"), _outputHelper); - runner.AddReversedServer(transportName); - runner.Start(); - return runner; - } - - public static void AddReversedServer(this TestRunner runner, string transportName) - { - runner.AddEnvVar("DOTNET_DiagnosticsMonitorAddress", transportName); - runner.AddEnvVar("DOTNET_DiagnosticPorts", $"{transportName},nosuspend;"); - } - } -} diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerTests.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerTests.cs index de76509a7..07f6a1717 100644 --- a/src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerTests.cs +++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerTests.cs @@ -316,7 +316,7 @@ namespace Microsoft.Diagnostics.NETCore.Client private ReversedDiagnosticsServer CreateReversedServer(out string transportName) { - transportName = ReversedServerHelper.CreateServerTransportName(); + transportName = DiagnosticPortsHelper.CreateServerTransportName(); _outputHelper.WriteLine("Starting reversed server at '" + transportName + "'."); return new ReversedDiagnosticsServer(transportName); } @@ -331,7 +331,10 @@ namespace Microsoft.Diagnostics.NETCore.Client private TestRunner StartTracee(string transportName) { _outputHelper.WriteLine("Starting tracee."); - return ReversedServerHelper.StartTracee(_outputHelper, transportName); + var runner = new TestRunner(CommonHelper.GetTraceePathWithArgs(targetFramework: "net5.0"), _outputHelper); + runner.SetDiagnosticPort(transportName, suspend: true); + runner.Start(); + return runner; } private async Task VerifyWaitForConnection(IpcEndpointInfo info, bool useAsync, bool expectTimeout = false)