From 615519f6a327fea3c13490db022ff463c4025cef Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 31 Mar 2017 12:06:36 +0200 Subject: [PATCH] [JENKINS-42744] - Decouple the process start logic to a separate method in the helper class --- src/Core/ServiceWrapper/Main.cs | 74 ++++++------------------ src/Core/WinSWCore/Util/ProcessHelper.cs | 73 ++++++++++++++++++++++- 2 files changed, 91 insertions(+), 56 deletions(-) diff --git a/src/Core/ServiceWrapper/Main.cs b/src/Core/ServiceWrapper/Main.cs index be83d5e..b9e6a3e 100644 --- a/src/Core/ServiceWrapper/Main.cs +++ b/src/Core/ServiceWrapper/Main.cs @@ -133,25 +133,6 @@ namespace winsw } } - /// - /// Starts a thread that protects the execution with a try/catch block. - /// It appears that in .NET, unhandled exception in any thread causes the app to terminate - /// http://msdn.microsoft.com/en-us/library/ms228965.aspx - /// - private void StartThread(ThreadStart main) - { - new Thread(delegate() { - try - { - main(); - } - catch (Exception e) - { - Log.Error("Thread failed unexpectedly",e); - } - }).Start(); - } - /// /// Handle the creation of the logfiles based on the optional logmode setting. /// @@ -419,53 +400,26 @@ namespace winsw private void StartProcess(Process processToStart, string arguments, String executable) { - var ps = processToStart.StartInfo; - ps.FileName = executable; - ps.Arguments = arguments; - ps.WorkingDirectory = _descriptor.WorkingDirectory; - ps.CreateNoWindow = false; - ps.UseShellExecute = false; - ps.RedirectStandardInput = true; // this creates a pipe for stdin to the new process, instead of having it inherit our stdin. - ps.RedirectStandardOutput = true; - ps.RedirectStandardError = true; + string msg = processToStart.Id + " - " + processToStart.StartInfo.FileName + " " + processToStart.StartInfo.Arguments; - foreach (string key in _envs.Keys) + // Define handler of the completed process + ProcessCompletionCallback processCompletionCallback = delegate(Process proc) { - Environment.SetEnvironmentVariable(key, _envs[key]); - // ps.EnvironmentVariables[key] = envs[key]; // bugged (lower cases all variable names due to StringDictionary being used, see http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=326163) - } - - // TODO: Make it generic via extension points. The issue mentioned above should be ideally worked around somehow - ps.EnvironmentVariables[WinSWSystem.ENVVAR_NAME_SERVICE_ID.ToLower()] = _descriptor.Id; - - processToStart.Start(); - Log.Info("Started " + processToStart.Id); - - var priority = _descriptor.Priority; - if (priority != ProcessPriorityClass.Normal) - processToStart.PriorityClass = priority; - - // monitor the completion of the process - StartThread(delegate - { - string msg = processToStart.Id + " - " + processToStart.StartInfo.FileName + " " + processToStart.StartInfo.Arguments; - processToStart.WaitForExit(); - try { if (_orderlyShutdown) { - LogEvent("Child process [" + msg + "] terminated with " + processToStart.ExitCode, EventLogEntryType.Information); + LogEvent("Child process [" + msg + "] terminated with " + proc.ExitCode, EventLogEntryType.Information); } else { - LogEvent("Child process [" + msg + "] finished with " + processToStart.ExitCode, EventLogEntryType.Warning); + LogEvent("Child process [" + msg + "] finished with " + proc.ExitCode, EventLogEntryType.Warning); // if we finished orderly, report that to SCM. // by not reporting unclean shutdown, we let Windows SCM to decide if it wants to // restart the service automatically - if (processToStart.ExitCode == 0) + if (proc.ExitCode == 0) SignalShutdownComplete(); - Environment.Exit(processToStart.ExitCode); + Environment.Exit(proc.ExitCode); } } catch (InvalidOperationException ioe) @@ -475,13 +429,23 @@ namespace winsw try { - processToStart.Dispose(); + proc.Dispose(); } catch (InvalidOperationException ioe) { LogEvent("Dispose " + ioe.Message); } - }); + }; + + // Invoke process and exit + ProcessHelper.StartProcessAndCallbackForExit( + processToStart: processToStart, + executable: executable, + arguments: arguments, + envVars: _envs, + workingDirectory: _descriptor.WorkingDirectory, + priority: _descriptor.Priority, + callback: processCompletionCallback); } public static int Main(string[] args) diff --git a/src/Core/WinSWCore/Util/ProcessHelper.cs b/src/Core/WinSWCore/Util/ProcessHelper.cs index e26cf55..fd4fdc1 100644 --- a/src/Core/WinSWCore/Util/ProcessHelper.cs +++ b/src/Core/WinSWCore/Util/ProcessHelper.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Management; using System.Text; +using System.Threading; namespace winsw.Util { @@ -117,6 +118,76 @@ namespace winsw.Util } } - // TODO: Also move StartProcess methods once LogEvent()/WriteEvent() mess gets solved + //TODO: generalize API + /// + /// Starts a process and asynchronosly waits for its termination. + /// Once the process exits, the callback will be invoked. + /// + /// Process object to be used + /// Arguments to be passed + /// Executable, which should be invoked + /// Additional environment variables + /// Working directory + /// Priority + /// Completion callback + public static void StartProcessAndCallbackForExit(Process processToStart, String executable, string arguments, Dictionary envVars, + string workingDirectory, ProcessPriorityClass priority, ProcessCompletionCallback callback) + { + var ps = processToStart.StartInfo; + ps.FileName = executable; + ps.Arguments = arguments; + ps.WorkingDirectory = workingDirectory; + ps.CreateNoWindow = false; + ps.UseShellExecute = false; + ps.RedirectStandardInput = true; // this creates a pipe for stdin to the new process, instead of having it inherit our stdin. + ps.RedirectStandardOutput = true; + ps.RedirectStandardError = true; + + foreach (string key in envVars.Keys) + { + Environment.SetEnvironmentVariable(key, envVars[key]); + // ps.EnvironmentVariables[key] = envs[key]; // bugged (lower cases all variable names due to StringDictionary being used, see http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=326163) + } + + //TODO: move outside, stubbed to reproduce the issue + // TODO: Make it generic via extension points. The issue mentioned above should be ideally worked around somehow + ps.EnvironmentVariables[WinSWSystem.ENVVAR_NAME_SERVICE_ID.ToLower()] = "myapp";// _descriptor.Id; + // Environment.SetEnvironmentVariable(WinSWSystem.ENVVAR_NAME_SERVICE_ID.ToLower(), _descriptor.Id); + + processToStart.Start(); + Logger.Info("Started process " + processToStart.Id); + + if (priority != ProcessPriorityClass.Normal) + processToStart.PriorityClass = priority; + + // monitor the completion of the process + StartThread(delegate + { + processToStart.WaitForExit(); + callback(processToStart); + }); + } + + /// + /// Starts a thread that protects the execution with a try/catch block. + /// It appears that in .NET, unhandled exception in any thread causes the app to terminate + /// http://msdn.microsoft.com/en-us/library/ms228965.aspx + /// + public static void StartThread(ThreadStart main) + { + new Thread(delegate() + { + try + { + main(); + } + catch (Exception e) + { + Logger.Error("Thread failed unexpectedly", e); + } + }).Start(); + } } + + public delegate void ProcessCompletionCallback(Process process); }