[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 a9b7e24791
This commit is contained in:
parent
a2081d584f
commit
bcd542a1bb
|
|
@ -83,22 +83,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
var timeoutTask = Task.Delay(timeout.Value).ContinueWith<ProcessResult>((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<ProcessResult> 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<Process> CaptureDump(Process process)
|
||||
{
|
||||
var metadataAttributes = Assembly.GetExecutingAssembly()
|
||||
.GetCustomAttributes<AssemblyMetadataAttribute>();
|
||||
|
||||
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<object>();
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -43,6 +43,16 @@
|
|||
<_Parameter2>$(MSBuildThisFileDirectory)..\testapps\TestPackageRestoreSource</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>ArtifactsLogDir</_Parameter1>
|
||||
<_Parameter2>$([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'log', '$(_BuildConfiguration)'))</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>ProcDumpToolPath</_Parameter1>
|
||||
<_Parameter2>$(ProcDumpPath)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<!-- The test projects rely on these binaries being available -->
|
||||
|
|
@ -112,4 +122,8 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
<MSBuild Projects="..\testapps\RestoreTestProjects\RestoreTestProjects.csproj" Targets="Restore" Properties="MicrosoftNetCompilersToolsetPackageVersion=$(MicrosoftNetCompilersToolsetPackageVersion)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="EnsureLogFolder" AfterTargets="Build">
|
||||
<MakeDir Directories="$([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'log', '$(_BuildConfiguration)'))" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
Loading…
Reference in New Issue