2020-03-03 21:59:44 +03:00
|
|
|
using System;
|
2016-06-17 18:21:18 +03:00
|
|
|
using System.Collections.Generic;
|
2016-06-06 13:48:53 +03:00
|
|
|
using System.Diagnostics;
|
|
|
|
using System.IO;
|
2016-12-16 14:24:08 +03:00
|
|
|
using System.Linq;
|
2016-06-17 18:21:18 +03:00
|
|
|
using System.Runtime.InteropServices;
|
2016-12-16 14:24:08 +03:00
|
|
|
using System.Text;
|
2016-06-06 13:48:53 +03:00
|
|
|
using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
2020-03-27 19:41:31 +03:00
|
|
|
using System.Xml;
|
2020-04-01 20:32:21 +03:00
|
|
|
using Microsoft.DotNet.XHarness.iOS.Shared.Logging;
|
|
|
|
using Microsoft.DotNet.XHarness.iOS.Shared.Utilities;
|
|
|
|
using Microsoft.DotNet.XHarness.iOS.Shared.Execution.Mlaunch;
|
2016-06-06 13:48:53 +03:00
|
|
|
|
2020-04-01 20:32:21 +03:00
|
|
|
namespace Microsoft.DotNet.XHarness.iOS.Shared.Execution {
|
2020-03-06 17:11:33 +03:00
|
|
|
public class ProcessManager : IProcessManager {
|
2020-04-03 18:00:45 +03:00
|
|
|
static readonly Lazy<string> autoDetectedXcodeRoot = new Lazy<string>(DetectXcodePath, LazyThreadSafetyMode.PublicationOnly);
|
|
|
|
|
|
|
|
readonly string xcodeRoot;
|
|
|
|
public string XcodeRoot => xcodeRoot ?? autoDetectedXcodeRoot.Value;
|
2020-03-27 19:41:31 +03:00
|
|
|
public string MlaunchPath { get; }
|
2020-06-19 16:22:36 +03:00
|
|
|
public GetDotNetExecutableDelegate GetDotNetExecutable { get; private set; }
|
|
|
|
public string MSBuildPath { get; private set; }
|
2020-03-27 19:41:31 +03:00
|
|
|
|
|
|
|
Version xcode_version;
|
|
|
|
public Version XcodeVersion {
|
|
|
|
get {
|
|
|
|
if (xcode_version == null) {
|
|
|
|
var doc = new XmlDocument ();
|
|
|
|
doc.Load (Path.Combine (XcodeRoot, "Contents", "version.plist"));
|
|
|
|
xcode_version = Version.Parse (doc.SelectSingleNode ("//key[text() = 'CFBundleShortVersionString']/following-sibling::string").InnerText);
|
|
|
|
}
|
|
|
|
return xcode_version;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-19 16:22:36 +03:00
|
|
|
public ProcessManager (string xcodeRoot = null, string mlaunchPath = "/Library/Frameworks/Xamarin.iOS.framework/Versions/Current/bin/mlaunch", GetDotNetExecutableDelegate dotNetPath = null, string msBuildPath = null)
|
2020-03-06 17:11:33 +03:00
|
|
|
{
|
2020-04-03 18:00:45 +03:00
|
|
|
this.xcodeRoot = xcodeRoot;
|
|
|
|
MlaunchPath = mlaunchPath;
|
2020-06-19 16:22:36 +03:00
|
|
|
GetDotNetExecutable = dotNetPath;
|
|
|
|
MSBuildPath = msBuildPath;
|
2020-03-06 17:11:33 +03:00
|
|
|
}
|
2016-06-17 18:21:18 +03:00
|
|
|
|
2020-03-20 13:42:43 +03:00
|
|
|
public async Task<ProcessExecutionResult> ExecuteCommandAsync (string filename,
|
|
|
|
IList<string> args,
|
|
|
|
ILog log,
|
|
|
|
TimeSpan timeout,
|
2020-03-27 19:41:31 +03:00
|
|
|
Dictionary<string, string> environmentVariables = null,
|
|
|
|
CancellationToken? cancellationToken = null)
|
2016-06-17 18:21:18 +03:00
|
|
|
{
|
2020-03-27 19:41:31 +03:00
|
|
|
using var p = new Process ();
|
|
|
|
p.StartInfo.FileName = filename ?? throw new ArgumentNullException (nameof (filename));
|
|
|
|
p.StartInfo.Arguments = StringUtils.FormatArguments (args);
|
|
|
|
return await RunAsync (p, log, timeout, environmentVariables, cancellationToken);
|
2016-06-17 18:21:18 +03:00
|
|
|
}
|
|
|
|
|
2020-03-27 19:41:31 +03:00
|
|
|
public async Task<ProcessExecutionResult> ExecuteCommandAsync (MlaunchArguments args,
|
2020-03-20 13:42:43 +03:00
|
|
|
ILog log,
|
|
|
|
TimeSpan timeout,
|
2020-03-27 19:41:31 +03:00
|
|
|
Dictionary<string, string> environmentVariables = null,
|
|
|
|
CancellationToken? cancellationToken = null)
|
2020-03-20 13:42:43 +03:00
|
|
|
{
|
2020-03-27 19:41:31 +03:00
|
|
|
using var p = new Process ();
|
|
|
|
return await RunAsync (p, args, log, timeout, environmentVariables, cancellationToken);
|
2020-03-20 13:42:43 +03:00
|
|
|
}
|
|
|
|
|
2020-06-29 11:10:20 +03:00
|
|
|
public async Task<ProcessExecutionResult> ExecuteXcodeCommandAsync (string executable, IList<string> args, ILog log, TimeSpan timeout)
|
2016-06-17 18:21:18 +03:00
|
|
|
{
|
2020-06-29 11:10:20 +03:00
|
|
|
using var p = new Process ();
|
|
|
|
p.StartInfo.FileName = Path.Combine (XcodeRoot, "Contents", "Developer", "usr", "bin", executable);
|
|
|
|
p.StartInfo.Arguments = StringUtils.FormatArguments (args);
|
|
|
|
return await RunAsync (p, log, timeout, diagnostics: false);
|
2016-06-17 18:21:18 +03:00
|
|
|
}
|
|
|
|
|
2020-03-27 19:41:31 +03:00
|
|
|
[DllImport ("/usr/lib/libc.dylib")]
|
|
|
|
internal static extern int kill (int pid, int sig);
|
2016-06-06 13:48:53 +03:00
|
|
|
|
2020-03-27 19:41:31 +03:00
|
|
|
public Task<ProcessExecutionResult> RunAsync (Process process,
|
|
|
|
ILog log,
|
|
|
|
TimeSpan? timeout = null,
|
|
|
|
Dictionary<string, string> environment_variables = null,
|
|
|
|
CancellationToken? cancellationToken = null,
|
|
|
|
bool? diagnostics = null)
|
2016-06-17 18:21:18 +03:00
|
|
|
{
|
2020-03-27 19:41:31 +03:00
|
|
|
return RunAsync (process, log, log, log, timeout, environment_variables, cancellationToken, diagnostics);
|
2016-06-17 18:21:18 +03:00
|
|
|
}
|
|
|
|
|
2020-03-27 19:41:31 +03:00
|
|
|
public Task<ProcessExecutionResult> RunAsync (Process process,
|
|
|
|
MlaunchArguments args,
|
|
|
|
ILog log,
|
|
|
|
TimeSpan? timeout = null,
|
|
|
|
Dictionary<string, string> environmentVariables = null,
|
|
|
|
CancellationToken? cancellationToken = null,
|
|
|
|
bool? diagnostics = null)
|
2020-03-12 00:06:40 +03:00
|
|
|
{
|
2020-03-27 19:41:31 +03:00
|
|
|
if (!args.Any (a => a is SdkRootArgument))
|
|
|
|
args.Prepend (new SdkRootArgument (XcodeRoot));
|
2020-04-01 20:32:21 +03:00
|
|
|
|
2020-03-27 19:41:31 +03:00
|
|
|
process.StartInfo.FileName = MlaunchPath;
|
2020-03-12 00:06:40 +03:00
|
|
|
process.StartInfo.Arguments = args.AsCommandLine ();
|
2020-03-27 19:41:31 +03:00
|
|
|
|
|
|
|
return RunAsync (process, log, timeout, environmentVariables, cancellationToken, diagnostics);
|
2020-03-12 00:06:40 +03:00
|
|
|
}
|
|
|
|
|
2020-04-03 18:00:45 +03:00
|
|
|
public Task<ProcessExecutionResult> RunAsync (Process process,
|
2020-03-27 19:41:31 +03:00
|
|
|
ILog log,
|
|
|
|
ILog stdout,
|
|
|
|
ILog stderr,
|
|
|
|
TimeSpan? timeout = null,
|
2020-04-03 18:00:45 +03:00
|
|
|
Dictionary<string, string> environmentVariables = null,
|
|
|
|
CancellationToken? cancellationToken = null,
|
|
|
|
bool? diagnostics = null)
|
|
|
|
{
|
|
|
|
return RunAsyncInternal (process, log, stdout, stderr, timeout, environmentVariables, cancellationToken, diagnostics);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Task KillTreeAsync (Process process, ILog log, bool? diagnostics = true)
|
|
|
|
{
|
|
|
|
return KillTreeAsyncInternal (process.Id, log, diagnostics);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Task KillTreeAsync (int pid, ILog log, bool? diagnostics = true)
|
|
|
|
{
|
|
|
|
return KillTreeAsyncInternal (pid, log, diagnostics);
|
|
|
|
}
|
|
|
|
|
|
|
|
static async Task<ProcessExecutionResult> RunAsyncInternal (Process process,
|
|
|
|
ILog log,
|
|
|
|
ILog stdout,
|
|
|
|
ILog stderr,
|
|
|
|
TimeSpan? timeout = null,
|
|
|
|
Dictionary<string, string> environmentVariables = null,
|
2020-03-27 19:41:31 +03:00
|
|
|
CancellationToken? cancellationToken = null,
|
|
|
|
bool? diagnostics = null)
|
2016-06-06 13:48:53 +03:00
|
|
|
{
|
|
|
|
var stdout_completion = new TaskCompletionSource<bool> ();
|
|
|
|
var stderr_completion = new TaskCompletionSource<bool> ();
|
2016-06-17 18:21:18 +03:00
|
|
|
var rv = new ProcessExecutionResult ();
|
2016-06-06 13:48:53 +03:00
|
|
|
|
|
|
|
process.StartInfo.RedirectStandardError = true;
|
|
|
|
process.StartInfo.RedirectStandardOutput = true;
|
2020-03-09 20:40:46 +03:00
|
|
|
// Make cute emojiis show up as cute emojiis in the output instead of ugly text symbols!
|
2020-04-01 20:32:21 +03:00
|
|
|
process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
|
2020-03-09 20:40:46 +03:00
|
|
|
process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
|
2016-06-06 13:48:53 +03:00
|
|
|
process.StartInfo.UseShellExecute = false;
|
|
|
|
|
2020-04-03 18:00:45 +03:00
|
|
|
if (environmentVariables != null) {
|
2020-04-08 17:19:08 +03:00
|
|
|
foreach (var kvp in environmentVariables) {
|
|
|
|
if (kvp.Value == null) {
|
|
|
|
process.StartInfo.EnvironmentVariables.Remove (kvp.Key);
|
|
|
|
} else {
|
|
|
|
process.StartInfo.EnvironmentVariables [kvp.Key] = kvp.Value;
|
|
|
|
}
|
|
|
|
}
|
2016-06-17 18:21:18 +03:00
|
|
|
}
|
|
|
|
|
2020-04-01 20:32:21 +03:00
|
|
|
process.OutputDataReceived += (sender, e) => {
|
2016-06-06 13:48:53 +03:00
|
|
|
if (e.Data != null) {
|
2020-03-27 19:41:31 +03:00
|
|
|
lock (stdout) {
|
|
|
|
stdout.WriteLine (e.Data);
|
|
|
|
stdout.Flush ();
|
2016-06-10 21:56:48 +03:00
|
|
|
}
|
2016-06-06 13:48:53 +03:00
|
|
|
} else {
|
2017-05-24 22:04:18 +03:00
|
|
|
stdout_completion.TrySetResult (true);
|
2016-06-06 13:48:53 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-01 20:32:21 +03:00
|
|
|
process.ErrorDataReceived += (sender, e) => {
|
2016-06-06 13:48:53 +03:00
|
|
|
if (e.Data != null) {
|
2020-03-27 19:41:31 +03:00
|
|
|
lock (stderr) {
|
|
|
|
stderr.WriteLine (e.Data);
|
|
|
|
stderr.Flush ();
|
2016-06-10 21:56:48 +03:00
|
|
|
}
|
2016-06-06 13:48:53 +03:00
|
|
|
} else {
|
2017-05-24 22:04:18 +03:00
|
|
|
stderr_completion.TrySetResult (true);
|
2016-06-06 13:48:53 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-05-10 08:43:44 +03:00
|
|
|
var sb = new StringBuilder ();
|
|
|
|
if (process.StartInfo.EnvironmentVariables != null) {
|
|
|
|
var currentEnvironment = Environment.GetEnvironmentVariables ().Cast<System.Collections.DictionaryEntry> ().ToDictionary ((v) => (string) v.Key, (v) => (string) v.Value, StringComparer.Ordinal);
|
|
|
|
var processEnvironment = process.StartInfo.EnvironmentVariables.Cast<System.Collections.DictionaryEntry> ().ToDictionary ((v) => (string) v.Key, (v) => (string) v.Value, StringComparer.Ordinal);
|
|
|
|
var allKeys = currentEnvironment.Keys.Union (processEnvironment.Keys).Distinct ();
|
|
|
|
foreach (var key in allKeys) {
|
|
|
|
string a = null, b = null;
|
|
|
|
currentEnvironment.TryGetValue (key, out a);
|
|
|
|
processEnvironment.TryGetValue (key, out b);
|
|
|
|
if (a != b)
|
|
|
|
sb.Append ($"{key}={StringUtils.Quote (b)} ");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sb.Append ($"{StringUtils.Quote (process.StartInfo.FileName)} {process.StartInfo.Arguments}");
|
|
|
|
log.WriteLine (sb);
|
2016-06-17 18:21:18 +03:00
|
|
|
|
2016-06-06 13:48:53 +03:00
|
|
|
process.Start ();
|
2018-11-30 18:49:03 +03:00
|
|
|
var pid = process.Id;
|
2016-06-06 13:48:53 +03:00
|
|
|
|
|
|
|
process.BeginErrorReadLine ();
|
|
|
|
process.BeginOutputReadLine ();
|
|
|
|
|
2020-03-27 19:41:31 +03:00
|
|
|
cancellationToken?.Register (() => {
|
[xharness] Handle Process.HasExited throwing exceptions. (#5239)
Apparently Process.HasExited may throw exceptions, so handle those gracefully
(by ignoring them completely):
Unhandled Exception:
System.AggregateException: One or more errors occurred. (No process is associated with this object.) ---> System.InvalidOperationException: No process is associated with this object.
at System.Diagnostics.Process.EnsureState (System.Diagnostics.Process+State state) [0x00018] in <d012d9eca6d14975a47488863de2f4c6>:0
at System.Diagnostics.Process.get_HasExited () [0x0000b] in <d012d9eca6d14975a47488863de2f4c6>:0
at (wrapper remoting-invoke-with-check) System.Diagnostics.Process.get_HasExited()
at xharness.Process_Extensions+<>c__DisplayClass2_0.<RunAsync>b__2 () [0x00001] in <285dbdcf9e034cd496a3fef953fac640>:0
at System.Threading.CancellationToken.ActionToActionObjShunt (System.Object obj) [0x00000] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.CancellationCallbackInfo.ExecutionContextCallback (System.Object obj) [0x00007] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00071] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state) [0x0002b] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.CancellationCallbackInfo.ExecuteCallback () [0x00024] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork (System.Threading.CancellationCallbackCoreWorkArguments args) [0x00042] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers (System.Boolean throwOnFirstException) [0x000c0] in <96207d0baa204f48a53ad6be05f5ecba>:0
--- End of inner exception stack trace ---
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers (System.Boolean throwOnFirstException) [0x00132] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.CancellationTokenSource.NotifyCancellation (System.Boolean throwOnFirstException) [0x0005f] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.CancellationTokenSource.Cancel (System.Boolean throwOnFirstException) [0x00006] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.CancellationTokenSource.Cancel () [0x00000] in <96207d0baa204f48a53ad6be05f5ecba>:0
at xharness.AppRunner+<>c__DisplayClass73_0.<RunAsync>b__0 (System.Object v) [0x00029] in <285dbdcf9e034cd496a3fef953fac640>:0
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context (System.Object state) [0x00007] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00071] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem () [0x00021] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.ThreadPoolWorkQueue.Dispatch () [0x00074] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback () [0x00000] in <96207d0baa204f48a53ad6be05f5ecba>:0
---> (Inner Exception #0) System.InvalidOperationException: No process is associated with this object.
at System.Diagnostics.Process.EnsureState (System.Diagnostics.Process+State state) [0x00018] in <d012d9eca6d14975a47488863de2f4c6>:0
at System.Diagnostics.Process.get_HasExited () [0x0000b] in <d012d9eca6d14975a47488863de2f4c6>:0
at (wrapper remoting-invoke-with-check) System.Diagnostics.Process.get_HasExited()
at xharness.Process_Extensions+<>c__DisplayClass2_0.<RunAsync>b__2 () [0x00001] in <285dbdcf9e034cd496a3fef953fac640>:0
at System.Threading.CancellationToken.ActionToActionObjShunt (System.Object obj) [0x00000] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.CancellationCallbackInfo.ExecutionContextCallback (System.Object obj) [0x00007] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00071] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state) [0x0002b] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.CancellationCallbackInfo.ExecuteCallback () [0x00024] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork (System.Threading.CancellationCallbackCoreWorkArguments args) [0x00042] in <96207d0baa204f48a53ad6be05f5ecba>:0
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers (System.Boolean throwOnFirstException) [0x000c0] in <96207d0baa204f48a53ad6be05f5ecba>:0 <---
2018-12-07 17:07:21 +03:00
|
|
|
var hasExited = false;
|
|
|
|
try {
|
|
|
|
hasExited = process.HasExited;
|
|
|
|
} catch {
|
|
|
|
// Process.HasExited can sometimes throw exceptions, so
|
|
|
|
// just ignore those and to be safe treat it as the
|
|
|
|
// process didn't exit (the safe option being to not leave
|
|
|
|
// processes behind).
|
|
|
|
}
|
|
|
|
if (!hasExited) {
|
2020-03-27 19:41:31 +03:00
|
|
|
stderr.WriteLine ($"Execution of {pid} was cancelled.");
|
2020-03-06 17:11:33 +03:00
|
|
|
kill (pid, 9);
|
2016-06-18 11:35:57 +03:00
|
|
|
}
|
|
|
|
});
|
2016-06-17 18:21:18 +03:00
|
|
|
|
2018-11-30 18:49:03 +03:00
|
|
|
if (timeout.HasValue) {
|
2020-03-06 17:11:33 +03:00
|
|
|
if (!await WaitForExitAsync (process, timeout.Value)) {
|
2020-04-03 18:00:45 +03:00
|
|
|
await KillTreeAsyncInternal (process.Id, log, diagnostics ?? true);
|
2018-11-30 18:49:03 +03:00
|
|
|
rv.TimedOut = true;
|
2020-03-27 19:41:31 +03:00
|
|
|
lock (stderr)
|
2018-11-30 18:49:03 +03:00
|
|
|
log.WriteLine ($"{pid} Execution timed out after {timeout.Value.TotalSeconds} seconds and the process was killed.");
|
2016-06-10 21:56:48 +03:00
|
|
|
}
|
2018-11-30 18:49:03 +03:00
|
|
|
}
|
2020-03-06 17:11:33 +03:00
|
|
|
await WaitForExitAsync (process);
|
2018-11-30 18:49:03 +03:00
|
|
|
Task.WaitAll (new Task [] { stderr_completion.Task, stdout_completion.Task }, TimeSpan.FromSeconds (1));
|
2016-06-10 21:56:48 +03:00
|
|
|
|
2017-10-02 13:06:57 +03:00
|
|
|
try {
|
|
|
|
rv.ExitCode = process.ExitCode;
|
|
|
|
} catch (Exception e) {
|
|
|
|
rv.ExitCode = 12345678;
|
|
|
|
log.WriteLine ($"Failed to get ExitCode: {e}");
|
|
|
|
}
|
2016-06-17 18:21:18 +03:00
|
|
|
return rv;
|
|
|
|
}
|
2016-12-16 14:24:08 +03:00
|
|
|
|
2020-04-03 18:00:45 +03:00
|
|
|
static async Task KillTreeAsyncInternal (int pid, ILog log, bool? diagnostics = true)
|
2016-12-16 14:24:08 +03:00
|
|
|
{
|
2020-04-01 20:32:21 +03:00
|
|
|
var pids = GetChildrenPS (log, pid);
|
2020-03-27 19:41:31 +03:00
|
|
|
|
2018-11-29 18:33:57 +03:00
|
|
|
if (diagnostics == true) {
|
2017-07-03 08:14:00 +03:00
|
|
|
log.WriteLine ($"Pids to kill: {string.Join (", ", pids.Select ((v) => v.ToString ()).ToArray ())}");
|
2016-12-16 14:24:08 +03:00
|
|
|
using (var ps = new Process ()) {
|
|
|
|
log.WriteLine ("Writing process list:");
|
|
|
|
ps.StartInfo.FileName = "ps";
|
2016-12-19 17:18:07 +03:00
|
|
|
ps.StartInfo.Arguments = "-A -o pid,ruser,ppid,pgid,%cpu=%CPU,%mem=%MEM,flags=FLAGS,lstart,rss,vsz,tty,state,time,command";
|
2020-04-03 18:00:45 +03:00
|
|
|
await RunAsyncInternal (ps, log, log, log, TimeSpan.FromSeconds (5), diagnostics: false);
|
2016-12-16 14:24:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach (var diagnose_pid in pids) {
|
|
|
|
var template = Path.GetTempFileName ();
|
2020-07-17 18:34:03 +03:00
|
|
|
var templateQuit = Path.GetTempFileName ();
|
2016-12-16 14:24:08 +03:00
|
|
|
try {
|
|
|
|
var commands = new StringBuilder ();
|
|
|
|
using (var dbg = new Process ()) {
|
|
|
|
commands.AppendLine ($"process attach --pid {diagnose_pid}");
|
|
|
|
commands.AppendLine ("thread list");
|
|
|
|
commands.AppendLine ("thread backtrace all");
|
|
|
|
commands.AppendLine ("detach");
|
|
|
|
commands.AppendLine ("quit");
|
|
|
|
dbg.StartInfo.FileName = "/usr/bin/lldb";
|
2020-07-17 18:34:03 +03:00
|
|
|
dbg.StartInfo.Arguments = StringUtils.FormatArguments ("--source", template, "--source", templateQuit);
|
2016-12-16 14:24:08 +03:00
|
|
|
File.WriteAllText (template, commands.ToString ());
|
2020-07-17 18:34:03 +03:00
|
|
|
File.WriteAllText (templateQuit, "quit\n");
|
2016-12-16 14:24:08 +03:00
|
|
|
|
|
|
|
log.WriteLine ($"Printing backtrace for pid={pid}");
|
2020-04-03 18:00:45 +03:00
|
|
|
await RunAsyncInternal (dbg, log, log, log, TimeSpan.FromSeconds (30), diagnostics: false);
|
2016-12-16 14:24:08 +03:00
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
try {
|
|
|
|
File.Delete (template);
|
2020-07-17 18:34:03 +03:00
|
|
|
File.Delete (templateQuit);
|
2016-12-16 14:24:08 +03:00
|
|
|
} catch {
|
|
|
|
// Don't care
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-29 21:36:04 +03:00
|
|
|
// Send SIGABRT since that produces a crash report
|
|
|
|
// lldb may fail to attach to system processes, but crash reports will still be produced with potentially helpful stack traces.
|
2020-07-17 19:24:58 +03:00
|
|
|
for (int i = 0; i < pids.Count; i++) {
|
|
|
|
log.WriteLine ($"kill -6 {pids [i]}");
|
2020-03-06 17:11:33 +03:00
|
|
|
kill (pids [i], 6);
|
2020-07-17 19:24:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Wait a little bit for the OS to process the SIGABRTs
|
|
|
|
await Task.Delay (TimeSpan.FromSeconds (5));
|
2018-11-29 21:36:04 +03:00
|
|
|
|
|
|
|
// send kill -9 anyway as a last resort
|
2020-07-17 19:24:58 +03:00
|
|
|
for (int i = 0; i < pids.Count; i++) {
|
|
|
|
log.WriteLine ($"kill -9 {pids [i]}");
|
2020-03-06 17:11:33 +03:00
|
|
|
kill (pids [i], 9);
|
2020-07-17 19:24:58 +03:00
|
|
|
}
|
2016-12-16 14:24:08 +03:00
|
|
|
}
|
|
|
|
|
2020-03-27 19:41:31 +03:00
|
|
|
static async Task<bool> WaitForExitAsync (Process process, TimeSpan? timeout = null)
|
2016-12-16 14:24:08 +03:00
|
|
|
{
|
2020-03-27 19:41:31 +03:00
|
|
|
if (process.HasExited)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
var tcs = new TaskCompletionSource<bool> ();
|
|
|
|
|
|
|
|
void ProcessExited (object sender, EventArgs ea)
|
|
|
|
{
|
|
|
|
process.Exited -= ProcessExited;
|
|
|
|
tcs.TrySetResult (true);
|
|
|
|
}
|
|
|
|
|
|
|
|
process.Exited += ProcessExited;
|
|
|
|
process.EnableRaisingEvents = true;
|
|
|
|
|
|
|
|
// Check if process exited again, in case it exited after we checked
|
|
|
|
// the last time, but before we attached the event handler.
|
|
|
|
if (process.HasExited) {
|
|
|
|
process.Exited -= ProcessExited;
|
|
|
|
tcs.TrySetResult (true);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (timeout.HasValue) {
|
|
|
|
return await tcs.Task.TimeoutAfter (timeout.Value);
|
|
|
|
} else {
|
|
|
|
await tcs.Task;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static List<int> GetChildrenPS (ILog log, int pid)
|
|
|
|
{
|
|
|
|
var list = new List<int> ();
|
2016-12-16 14:24:08 +03:00
|
|
|
|
|
|
|
using (Process ps = new Process ()) {
|
|
|
|
ps.StartInfo.FileName = "ps";
|
|
|
|
ps.StartInfo.Arguments = "-eo ppid,pid";
|
|
|
|
ps.StartInfo.UseShellExecute = false;
|
|
|
|
ps.StartInfo.RedirectStandardOutput = true;
|
|
|
|
ps.Start ();
|
2020-03-27 19:41:31 +03:00
|
|
|
|
|
|
|
string stdout = ps.StandardOutput.ReadToEnd ();
|
2016-12-16 14:24:08 +03:00
|
|
|
|
|
|
|
if (!ps.WaitForExit (1000)) {
|
|
|
|
log.WriteLine ("ps didn't finish in a reasonable amount of time (1 second).");
|
2020-03-27 19:41:31 +03:00
|
|
|
return list;
|
2016-12-16 14:24:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ps.ExitCode != 0)
|
2020-03-27 19:41:31 +03:00
|
|
|
return list;
|
2016-12-16 14:24:08 +03:00
|
|
|
|
|
|
|
stdout = stdout.Trim ();
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty (stdout))
|
2020-03-27 19:41:31 +03:00
|
|
|
return list;
|
2016-12-16 14:24:08 +03:00
|
|
|
|
|
|
|
var dict = new Dictionary<int, List<int>> ();
|
|
|
|
foreach (string line in stdout.Split (new char [] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)) {
|
|
|
|
var l = line.Trim ();
|
|
|
|
var space = l.IndexOf (' ');
|
|
|
|
if (space <= 0)
|
|
|
|
continue;
|
2020-04-01 20:32:21 +03:00
|
|
|
|
2016-12-16 14:24:08 +03:00
|
|
|
var parent = l.Substring (0, space);
|
|
|
|
var process = l.Substring (space + 1);
|
|
|
|
|
2020-03-27 19:41:31 +03:00
|
|
|
if (int.TryParse (parent, out var parent_id) && int.TryParse (process, out var process_id)) {
|
|
|
|
if (!dict.TryGetValue (parent_id, out var children))
|
2016-12-16 14:24:08 +03:00
|
|
|
dict [parent_id] = children = new List<int> ();
|
2020-03-27 19:41:31 +03:00
|
|
|
|
2016-12-16 14:24:08 +03:00
|
|
|
children.Add (process_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var queue = new Queue<int> ();
|
|
|
|
queue.Enqueue (pid);
|
|
|
|
|
|
|
|
do {
|
|
|
|
var parent_id = queue.Dequeue ();
|
|
|
|
list.Add (parent_id);
|
2020-03-27 19:41:31 +03:00
|
|
|
if (dict.TryGetValue (parent_id, out var children)) {
|
2016-12-16 14:24:08 +03:00
|
|
|
foreach (var child in children)
|
|
|
|
queue.Enqueue (child);
|
|
|
|
}
|
|
|
|
} while (queue.Count > 0);
|
|
|
|
}
|
2020-03-27 19:41:31 +03:00
|
|
|
|
|
|
|
return list;
|
2016-12-16 14:24:08 +03:00
|
|
|
}
|
2020-04-03 18:00:45 +03:00
|
|
|
|
|
|
|
static string DetectXcodePath ()
|
|
|
|
{
|
|
|
|
using var process = new Process ();
|
|
|
|
process.StartInfo.FileName = "xcode-select";
|
|
|
|
process.StartInfo.Arguments = "-p";
|
|
|
|
|
|
|
|
var log = new MemoryLog ();
|
|
|
|
var stdout = new MemoryLog () { Timestamp = false };
|
|
|
|
var stderr = new ConsoleLog ();
|
|
|
|
var timeout = TimeSpan.FromSeconds (30);
|
|
|
|
|
|
|
|
var result = RunAsyncInternal (process, log, stdout, stderr, timeout).GetAwaiter ().GetResult ();
|
|
|
|
|
|
|
|
if (!result.Succeeded)
|
|
|
|
throw new Exception ("Failed to detect Xcode path from xcode-select!");
|
|
|
|
|
|
|
|
// Something like /Applications/Xcode114.app/Contents/Developers
|
|
|
|
var xcodeRoot = stdout.ToString ().Trim ();
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty (xcodeRoot))
|
|
|
|
throw new Exception ("Failed to detect Xcode path from xcode-select!");
|
|
|
|
|
|
|
|
// We need /Applications/Xcode114.app only
|
|
|
|
return Path.GetDirectoryName(Path.GetDirectoryName(xcodeRoot));
|
|
|
|
}
|
2016-06-10 21:56:48 +03:00
|
|
|
}
|
2016-06-06 13:48:53 +03:00
|
|
|
}
|