Enable ProcessInfo2 in DiagnosticsClient. (#2564)
authorJustin Anderson <jander-msft@users.noreply.github.com>
Fri, 10 Sep 2021 20:40:20 +0000 (13:40 -0700)
committerGitHub <noreply@github.com>
Fri, 10 Sep 2021 20:40:20 +0000 (13:40 -0700)
Update tests to reflect when entrypoint is available.

src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs
src/tests/Microsoft.Diagnostics.NETCore.Client/DiagnosticPortsHelper.cs [new file with mode: 0644]
src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs
src/tests/Microsoft.Diagnostics.NETCore.Client/RemoteTestExecution.cs
src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerHelper.cs [deleted file]
src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerTests.cs

index 3a420f9df1c150cd99f140f293f1044ee688625d..c054e02a3cd9fdd921445768dc24d00b880f0ecb 100644 (file)
@@ -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<ProcessInfo> 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 (file)
index 0000000..c5b3122
--- /dev/null
@@ -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";
+
+        /// <summary>
+        /// Creates a unique server name to avoid collisions from simultaneous running tests
+        /// or potentially abandoned socket files.
+        /// </summary>
+        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");
+        }
+    }
+}
index 172d271657e61645eb13dab4e202c955e8d3b53f..868890489c8cf37fd76a4b07b6f82a3404704535 100644 (file)
@@ -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
             }
         }
 
+        /// <summary>
+        /// Get process information with entrypoint information with exponential backoff on retries.
+        /// </summary>
+        private async Task<ProcessInfo> 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);
index 22e74e9673efc27b05d67d7fbcea9fa9e55e1d18..a3b592339db1f462ccd3ae543c7e15a07a9a2ed7 100644 (file)
@@ -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 (file)
index d6c0955..0000000
+++ /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
-    {
-        /// <summary>
-        /// Creates a unique server name to avoid collisions from simultaneous running tests
-        /// or potentially abandoned socket files.
-        /// </summary>
-        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);
-            }
-        }
-
-        /// <summary>
-        /// Starts the Tracee executable while enabling connection to reverse diagnostics server.
-        /// </summary>
-        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;");
-        }
-    }
-}
index de76509a7a6e3838b32ac0d544bf3ad6002a8e4c..07f6a171707a97943ad06f97ebecc7aec7f92b40 100644 (file)
@@ -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)