From bcd542a1bb22e0cea7a159d293c0706d4f35aa42 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Thu, 19 Sep 2019 18:36:02 +0200 Subject: [PATCH] [Infrastructure] Install procdump and capture dumps on build hangs (dotnet/aspnetcore-tooling#1146) * Captures process dumps when the build hangs. * Captures process dumps when a child process hangs inside a test. \n\nCommit migrated from https://github.com/dotnet/aspnetcore-tooling/commit/a9b7e24791078493c4cb7676478b1b76cd0a4894 --- .../IntegrationTests/MSBuildProcessManager.cs | 98 ++++++++++++++++--- .../test/Microsoft.NET.Sdk.Razor.Test.csproj | 14 +++ 2 files changed, 96 insertions(+), 16 deletions(-) diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/test/IntegrationTests/MSBuildProcessManager.cs b/src/Razor/Microsoft.NET.Sdk.Razor/test/IntegrationTests/MSBuildProcessManager.cs index de81c1f049..50bdadf639 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/test/IntegrationTests/MSBuildProcessManager.cs +++ b/src/Razor/Microsoft.NET.Sdk.Razor/test/IntegrationTests/MSBuildProcessManager.cs @@ -83,22 +83,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests process.BeginOutputReadLine(); process.BeginErrorReadLine(); - var timeoutTask = Task.Delay(timeout.Value).ContinueWith((t) => - { - // Don't timeout during debug sessions - while (Debugger.IsAttached) - { - Thread.Sleep(TimeSpan.FromSeconds(1)); - } - - if (!process.HasExited) - { - // This is a timeout. - process.Kill(); - } - - throw new TimeoutException($"command '${process.StartInfo.FileName} {process.StartInfo.Arguments}' timed out after {timeout}. Output: {output.ToString()}"); - }); + var timeoutTask = GetTimeoutForProcess(process, timeout); var waitTask = Task.Run(() => { @@ -142,6 +127,87 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests output.AppendLine(e.Data); } } + + async Task GetTimeoutForProcess(Process process, TimeSpan? timeout) + { + await Task.Delay(timeout.Value); + + // Don't timeout during debug sessions + while (Debugger.IsAttached) + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + } + if (!process.HasExited) + { + var procDumpProcess = await CaptureDump(process); + if (procDumpProcess != null && procDumpProcess.HasExited) + { + Console.WriteLine("ProcDump failed to run."); + procDumpProcess.Kill(); + } + + // This is a timeout. + process.Kill(); + } + + throw new TimeoutException($"command '${process.StartInfo.FileName} {process.StartInfo.Arguments}' timed out after {timeout}. Output: {output.ToString()}"); + } + + async Task CaptureDump(Process process) + { + var metadataAttributes = Assembly.GetExecutingAssembly() + .GetCustomAttributes(); + + var procDumpPath = metadataAttributes + .SingleOrDefault(ama => ama.Key == "ProcDumpPath")?.Value; + + if (string.IsNullOrEmpty(procDumpPath)) + { + Console.WriteLine("ProcDumpPath not defined."); + return null; + } + var procDumpExePath = Path.Combine(procDumpPath, "procdump.exe"); + if (!File.Exists(procDumpExePath)) + { + Console.WriteLine($"Can't find procdump.exe in '{procDumpPath}'."); + return null; + } + + var dumpDirectory = metadataAttributes + .SingleOrDefault(ama => ama.Key == "ArtifactsLogDir")?.Value; + + if (string.IsNullOrEmpty(dumpDirectory)) + { + Console.WriteLine("ArtifactsLogDir not defined."); + return null; + } + + if (!Directory.Exists(dumpDirectory)) + { + Console.WriteLine($"'{dumpDirectory}' does not exist."); + return null; + } + + var procDumpPattern = Path.Combine(dumpDirectory, "HangingProcess_PROCESSNAME_PID_YYMMDD_HHMMSS.dmp"); + var procDumpStartInfo = new ProcessStartInfo( + procDumpExePath, + $"-accepteula -ma {process.Id} {procDumpPattern}") + { + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + var procDumpProcess = Process.Start(procDumpStartInfo); + var tcs = new TaskCompletionSource(); + + procDumpProcess.Exited += (s, a) => tcs.TrySetResult(null); + procDumpProcess.EnableRaisingEvents = true; + + await Task.WhenAny(tcs.Task, Task.Delay(timeout ?? TimeSpan.FromSeconds(30))); + return procDumpProcess; + } } internal class ProcessResult diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/test/Microsoft.NET.Sdk.Razor.Test.csproj b/src/Razor/Microsoft.NET.Sdk.Razor/test/Microsoft.NET.Sdk.Razor.Test.csproj index 0ae4b051c3..e99c60292c 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/test/Microsoft.NET.Sdk.Razor.Test.csproj +++ b/src/Razor/Microsoft.NET.Sdk.Razor/test/Microsoft.NET.Sdk.Razor.Test.csproj @@ -43,6 +43,16 @@ <_Parameter2>$(MSBuildThisFileDirectory)..\testapps\TestPackageRestoreSource + + <_Parameter1>ArtifactsLogDir + <_Parameter2>$([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'log', '$(_BuildConfiguration)')) + + + + <_Parameter1>ProcDumpToolPath + <_Parameter2>$(ProcDumpPath) + + @@ -112,4 +122,8 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests + + + +