#!/usr/bin/env /Library/Frameworks/Mono.framework/Commands/csharp -s // arguments are: using System.Diagnostics; using System.IO; using System.Threading; using System.Xml; var args = Environment.GetCommandLineArgs (); var initialArgumentCount = 3; if (args.Length <= initialArgumentCount + 1 /* 3 default arguments (executable + script + -s) */) { // first arg is "/Library/Frameworks/Mono.framework/Versions/4.8.0/lib/mono/4.5/csharp.exe" // second arg the script itself // third argument is -s // then comes the ones we care about Console.WriteLine ($"Need two arguments (the timeout + the command to launch), got {args.Length - initialArgumentCount} argument(s)"); Environment.Exit (1); return; } var launchTimeout = TimeSpan.FromSeconds (10); // must launch within a few seconds. var argIndex = initialArgumentCount; var executionTimeout = TimeSpan.FromSeconds (int.Parse (args [argIndex++])); var commands = args.Skip (argIndex).ToArray (); var pid = Process.GetCurrentProcess ().Id; var maxLaunchAttempts = 10; var exitCode = -1; for (var attempt = 0; attempt < maxLaunchAttempts; attempt++) { var launchTimeoutFile = Path.GetFullPath ($"launch-timeout-sentinel-{pid}-{attempt}.txt"); var launchTimedOut = new ManualResetEvent (false); var p = new Process (); var launchTimer = new Thread (() => { if (p.WaitForExit ((int) launchTimeout.TotalMilliseconds)) { Console.WriteLine ($"App finished before launch timeout triggered."); } else if (!File.Exists (launchTimeoutFile)) { Console.WriteLine ($"Launch timed out after {launchTimeout.TotalSeconds} seconds."); launchTimedOut.Set (); p.Kill (); } }) { IsBackground = true, }; try { p.StartInfo.FileName = commands [0]; p.StartInfo.Arguments = string.Join (" ", commands.Skip (1)); p.StartInfo.UseShellExecute = false; p.StartInfo.EnvironmentVariables ["LAUNCH_SENTINEL_FILE"] = launchTimeoutFile; Console.WriteLine ($"Launching (attempt #{attempt + 1}):"); Console.WriteLine ($" {p.StartInfo.FileName} {p.StartInfo.Arguments}"); p.Start (); launchTimer.Start (); if (!p.WaitForExit ((int) executionTimeout.TotalMilliseconds)) { Console.WriteLine ($"Execution timed out after {executionTimeout.TotalSeconds} seconds."); p.Kill (); p.WaitForExit (); } launchTimer.Join (); exitCode = p.ExitCode; if (launchTimedOut.WaitOne (0)) { Console.WriteLine ($"Launching again since the launch timeout triggered."); continue; } Console.WriteLine ($"Execution completed with exit code {exitCode}"); } finally { File.Delete (launchTimeoutFile); p.Dispose (); } break; } Environment.Exit (exitCode);