| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Globalization;
- using System.IO;
- using System.Runtime.InteropServices;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- #nullable enable
- namespace HelixTestRunner;
- public static partial class ProcessUtil
- {
- [LibraryImport("libc", SetLastError = true, EntryPoint = "kill")]
- private static partial int sys_kill(int pid, int sig);
- public static Task CaptureDumpAsync()
- {
- var dumpDirectoryPath = Environment.GetEnvironmentVariable("HELIX_DUMP_FOLDER");
- if (dumpDirectoryPath == null)
- {
- return Task.CompletedTask;
- }
- var process = Process.GetCurrentProcess();
- var dumpFilePath = Path.Combine(dumpDirectoryPath, $"{process.ProcessName}-{process.Id}.dmp");
- return CaptureDumpAsync(process.Id, dumpFilePath);
- }
- public static Task CaptureDumpAsync(int pid)
- {
- var dumpDirectoryPath = Environment.GetEnvironmentVariable("HELIX_DUMP_FOLDER");
- if (dumpDirectoryPath == null)
- {
- return Task.CompletedTask;
- }
- var process = Process.GetProcessById(pid);
- var dumpFilePath = Path.Combine(dumpDirectoryPath, $"{process.ProcessName}.{process.Id}.dmp");
- return CaptureDumpAsync(process.Id, dumpFilePath);
- }
- public static Task CaptureDumpAsync(int pid, string dumpFilePath)
- {
- // Skip this on OSX, we know it's unsupported right now
- if (OperatingSystem.IsMacOS())
- {
- // Can we capture stacks or do a gcdump instead?
- return Task.CompletedTask;
- }
- if (!File.Exists($"{Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT")}/dotnet-dump") &&
- !File.Exists($"{Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT")}/dotnet-dump.exe"))
- {
- return Task.CompletedTask;
- }
- return RunAsync($"{Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT")}/dotnet-dump", $"collect -p {pid} -o \"{dumpFilePath}\"");
- }
- public static async Task<ProcessResult> RunAsync(
- string filename,
- string arguments,
- string? workingDirectory = null,
- string? dumpDirectoryPath = null,
- bool throwOnError = true,
- IDictionary<string, string?>? environmentVariables = null,
- Action<string>? outputDataReceived = null,
- Action<string>? errorDataReceived = null,
- Action<int>? onStart = null,
- CancellationToken cancellationToken = default)
- {
- PrintMessage($"Running '{filename} {arguments}'");
- using var process = new Process()
- {
- StartInfo =
- {
- FileName = filename,
- Arguments = arguments,
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- UseShellExecute = false,
- CreateNoWindow = true,
- },
- EnableRaisingEvents = true
- };
- if (workingDirectory != null)
- {
- process.StartInfo.WorkingDirectory = workingDirectory;
- }
- dumpDirectoryPath ??= Environment.GetEnvironmentVariable("HELIX_DUMP_FOLDER");
- if (dumpDirectoryPath != null)
- {
- process.StartInfo.EnvironmentVariables["COMPlus_DbgEnableMiniDump"] = "1";
- process.StartInfo.EnvironmentVariables["COMPlus_DbgMiniDumpName"] = Path.Combine(dumpDirectoryPath, $"{Path.GetFileName(filename)}.%d.dmp");
- }
- if (environmentVariables != null)
- {
- foreach (var kvp in environmentVariables)
- {
- process.StartInfo.Environment.Add(kvp);
- }
- }
- var outputBuilder = new StringBuilder();
- process.OutputDataReceived += (_, e) =>
- {
- if (e.Data != null)
- {
- if (outputDataReceived != null)
- {
- outputDataReceived.Invoke(e.Data);
- }
- else
- {
- outputBuilder.AppendLine(e.Data);
- }
- }
- };
- var errorBuilder = new StringBuilder();
- process.ErrorDataReceived += (_, e) =>
- {
- if (e.Data != null)
- {
- if (errorDataReceived != null)
- {
- errorDataReceived.Invoke(e.Data);
- }
- else
- {
- errorBuilder.AppendLine(e.Data);
- }
- }
- };
- var processLifetimeTask = new TaskCompletionSource<ProcessResult>();
- process.Exited += (_, e) =>
- {
- PrintMessage($"'{process.StartInfo.FileName} {process.StartInfo.Arguments}' completed with exit code '{process.ExitCode}'");
- if (throwOnError && process.ExitCode != 0)
- {
- processLifetimeTask.TrySetException(new InvalidOperationException($"Command {filename} {arguments} returned exit code {process.ExitCode} output: {outputBuilder.ToString()}"));
- }
- else
- {
- processLifetimeTask.TrySetResult(new ProcessResult(outputBuilder.ToString(), errorBuilder.ToString(), process.ExitCode));
- }
- };
- process.Start();
- onStart?.Invoke(process.Id);
- process.BeginOutputReadLine();
- process.BeginErrorReadLine();
- var canceledTcs = new TaskCompletionSource<object?>();
- await using var _ = cancellationToken.Register(() => canceledTcs.TrySetResult(null));
- var result = await Task.WhenAny(processLifetimeTask.Task, canceledTcs.Task);
- if (result == canceledTcs.Task)
- {
- if (dumpDirectoryPath != null)
- {
- var dumpFilePath = Path.Combine(dumpDirectoryPath, $"{Path.GetFileName(filename)}.{process.Id}.dmp");
- // Capture a process dump if the dumpDirectory is set
- await CaptureDumpAsync(process.Id, dumpFilePath);
- }
- if (!OperatingSystem.IsWindows())
- {
- sys_kill(process.Id, sig: 2); // SIGINT
- var cancel = new CancellationTokenSource();
- await Task.WhenAny(processLifetimeTask.Task, Task.Delay(TimeSpan.FromSeconds(5), cancel.Token));
- cancel.Cancel();
- }
- if (!process.HasExited)
- {
- process.CloseMainWindow();
- if (!process.HasExited)
- {
- process.Kill();
- }
- }
- }
- return await processLifetimeTask.Task;
- }
- public static void PrintMessage(string message) => Console.WriteLine($"{DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)} {message}");
- public static void PrintErrorMessage(string message) => Console.Error.WriteLine($"{DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)} {message}");
- }
|