/// <summary> /// Cleanly terminates the current process (for internal use). /// </summary> /// <param name="exitCode">Optional process exit code (defaults to <b>0</b>).</param> /// <param name="explicitTermination">Optionally indicates that termination is not due to receiving an external signal.</param> private void ExitInternal(int exitCode = 0, bool explicitTermination = false) { if (readyToExit) { // Application has already indicated that it has terminated. return; } var isTerminating = terminating; terminating = true; if (isTerminating) { return; // Already terminating. } if (explicitTermination) { log?.LogInfo(() => $"INTERNAL stop request: [timeout={Timeout}]"); } else { log?.LogInfo(() => $"SIGTERM received: Stopping process [timeout={Timeout}]"); } cts.Cancel(); lock (handlers) { foreach (var handler in handlers) { new Thread(new ThreadStart(handler)).Start(); } } try { NeonHelper.WaitFor(() => readyToExit, Timeout); log?.LogInfo(() => "Process stopped gracefully."); } catch (TimeoutException) { log?.LogWarn(() => $"Process did not stop within [{Timeout}]."); } Environment.Exit(exitCode); }
/// <summary> /// Starts a process to run an executable file and then waits for the process to terminate. /// </summary> /// <param name="path">Path to the executable file.</param> /// <param name="args">Command line arguments (or <c>null</c>).</param> /// <param name="timeout"> /// Optional maximum time to wait for the process to complete or <c>null</c> to wait /// indefinitely. /// </param> /// <param name="process"> /// The optional <see cref="Process"/> instance to use to launch the process. /// </param> /// <returns>The process exit code.</returns> /// <exception cref="TimeoutException">Thrown if the process did not exit within the <paramref name="timeout"/> limit.</exception> /// <remarks> /// <note> /// If <paramref name="timeout"/> is exceeded and execution has not commpleted in time /// then a <see cref="TimeoutException"/> will be thrown and the process will be killed /// if it was created by this method. Process instances passed via the <paramref name="process"/> /// parameter will not be killed in this case. /// </note> /// </remarks> public static int Execute(string path, string args, TimeSpan?timeout = null, Process process = null) { var processInfo = new ProcessStartInfo(GetProgramPath(path), args ?? string.Empty); var killOnTimeout = process == null; if (process == null) { process = new Process(); } try { processInfo.UseShellExecute = false; processInfo.CreateNoWindow = true; processInfo.RedirectStandardError = true; processInfo.RedirectStandardOutput = true; processInfo.WorkingDirectory = Environment.CurrentDirectory; process.StartInfo = processInfo; process.EnableRaisingEvents = true; // Configure the sub-process STDOUT and STDERR streams to use // code page 1252 which simply passes byte values through. processInfo.StandardErrorEncoding = processInfo.StandardOutputEncoding = ByteEncoding.Instance; // Relay STDOUT and STDERR output from the child process // to this process's STDOUT and STDERR streams. // $todo(jefflill): // // This won't work properly for binary data streaming // back from the process because we're not going to be // sure whether the "line" was terminated by a CRLF or // just a CR. I'm not sure if there's a clean way to // address this in .NET code. // // https://github.com/nforgeio/neonKUBE/issues/461 var stdErrClosed = false; var stdOutClosed = false; process.ErrorDataReceived += (s, a) => { if (a.Data == null) { stdErrClosed = true; } else { Console.Error.WriteLine(a.Data); } }; process.OutputDataReceived += (s, a) => { if (a.Data == null) { stdOutClosed = true; } else { Console.Out.WriteLine(a.Data); } }; process.Start(); process.BeginErrorReadLine(); process.BeginOutputReadLine(); if (!timeout.HasValue || timeout.Value >= TimeSpan.FromDays(1)) { NeonHelper.WaitFor(() => stdErrClosed && stdOutClosed, timeout: timeout ?? TimeSpan.FromDays(365), pollTime: TimeSpan.FromMilliseconds(250)); process.WaitForExit(); } else { NeonHelper.WaitFor(() => stdErrClosed && stdOutClosed, timeout: timeout.Value, pollTime: TimeSpan.FromMilliseconds(250)); process.WaitForExit((int)timeout.Value.TotalMilliseconds); if (!process.HasExited) { if (killOnTimeout) { process.Kill(); } throw new TimeoutException(string.Format("Process [{0}] execute has timed out.", path)); } } return(process.ExitCode); } finally { process.Dispose(); } }