diff --git a/src/WinSW.Core/LogAppenders.cs b/src/WinSW.Core/LogAppenders.cs index a0ff3ae..213f0ed 100644 --- a/src/WinSW.Core/LogAppenders.cs +++ b/src/WinSW.Core/LogAppenders.cs @@ -9,9 +9,9 @@ namespace WinSW { public interface IEventLogger { - void LogEvent(string message); + void WriteEntry(string message); - void LogEvent(string message, EventLogEntryType type); + void WriteEntry(string message, EventLogEntryType type); } /// @@ -54,7 +54,7 @@ namespace WinSW } catch (IOException e) { - this.EventLogger.LogEvent("Failed to move :" + sourceFileName + " to " + destFileName + " because " + e.Message); + this.EventLogger.WriteEntry("Failed to move :" + sourceFileName + " to " + destFileName + " because " + e.Message); } } } @@ -118,7 +118,7 @@ namespace WinSW } catch (Exception e) { - this.EventLogger.LogEvent("Unhandled exception in task. " + e, EventLogEntryType.Error); + this.EventLogger.WriteEntry("Unhandled exception in task. " + e, EventLogEntryType.Error); } } @@ -130,7 +130,7 @@ namespace WinSW } catch (Exception e) { - this.EventLogger.LogEvent("Unhandled exception in task. " + e, EventLogEntryType.Error); + this.EventLogger.WriteEntry("Unhandled exception in task. " + e, EventLogEntryType.Error); } } } @@ -309,7 +309,7 @@ namespace WinSW } catch (IOException e) { - this.EventLogger.LogEvent("Failed to roll log: " + e.Message); + this.EventLogger.WriteEntry("Failed to roll log: " + e.Message); } // even if the log rotation fails, create a new one, or else @@ -439,7 +439,7 @@ namespace WinSW } catch (Exception ex) { - this.EventLogger.LogEvent($"Failed to to trigger auto roll at time event due to: {ex.Message}"); + this.EventLogger.WriteEntry($"Failed to to trigger auto roll at time event due to: {ex.Message}"); } finally { @@ -476,7 +476,7 @@ namespace WinSW } catch (Exception e) { - this.EventLogger.LogEvent($"Failed to roll size time log: {e.Message}"); + this.EventLogger.WriteEntry($"Failed to roll size time log: {e.Message}"); } } @@ -516,7 +516,7 @@ namespace WinSW } catch (Exception e) { - this.EventLogger.LogEvent($"Failed to Zip files. Error {e.Message}"); + this.EventLogger.WriteEntry($"Failed to Zip files. Error {e.Message}"); } } @@ -534,7 +534,7 @@ namespace WinSW } catch (Exception e) { - this.EventLogger.LogEvent($"Failed to Zip the File {sourceFilePath}. Error {e.Message}"); + this.EventLogger.WriteEntry($"Failed to Zip the File {sourceFilePath}. Error {e.Message}"); } finally { diff --git a/src/WinSW.Core/Util/ProcessExtensions.cs b/src/WinSW.Core/Util/ProcessExtensions.cs index f89bdc8..07fb212 100644 --- a/src/WinSW.Core/Util/ProcessExtensions.cs +++ b/src/WinSW.Core/Util/ProcessExtensions.cs @@ -13,7 +13,7 @@ namespace WinSW.Util public static void StopTree(this Process process, TimeSpan stopTimeout) { - Stop(process, stopTimeout); + StopPrivate(process, stopTimeout); foreach (Process child in GetChildren(process)) { @@ -81,7 +81,37 @@ namespace WinSW.Util return children; } - private static void Stop(Process process, TimeSpan stopTimeout) + // true => canceled + // false => terminated + // null => finished + internal static bool? Stop(this Process process, TimeSpan stopTimeout) + { + if (process.HasExited) + { + return null; + } + + // (bool sent, bool exited) + KeyValuePair result = SignalHelper.SendCtrlCToProcess(process, stopTimeout); + bool exited = result.Value; + if (exited) + { + bool sent = result.Key; + return sent ? true : (bool?)null; + } + + try + { + process.Kill(); + } + catch when (process.HasExited) + { + } + + return false; + } + + private static void StopPrivate(Process process, TimeSpan stopTimeout) { Logger.Info("Stopping process " + process.Id); diff --git a/src/WinSW.Core/Util/SignalHelper.cs b/src/WinSW.Core/Util/SignalHelper.cs index 7be1bfb..35d46e2 100644 --- a/src/WinSW.Core/Util/SignalHelper.cs +++ b/src/WinSW.Core/Util/SignalHelper.cs @@ -18,7 +18,7 @@ namespace WinSW.Util if (!ConsoleApis.AttachConsole(process.Id)) { int error = Marshal.GetLastWin32Error(); - Logger.Warn("Failed to attach to console. " + error switch + Logger.Info("Failed to attach to console. " + error switch { Errors.ERROR_ACCESS_DENIED => "WinSW is already attached to a console.", // TODO: test mode Errors.ERROR_INVALID_HANDLE => "The process does not have a console.", diff --git a/src/WinSW/WrapperService.cs b/src/WinSW/WrapperService.cs index 27b453a..0984a78 100644 --- a/src/WinSW/WrapperService.cs +++ b/src/WinSW/WrapperService.cs @@ -34,11 +34,6 @@ namespace WinSW internal WinSWExtensionManager ExtensionManager { get; } - /// - /// Indicates to the watch dog thread that we are going to terminate the process, - /// so don't try to kill us when the child exits. - /// - private volatile bool orderlyShutdown; private bool shuttingdown; /// @@ -51,14 +46,12 @@ namespace WinSW public WrapperService(XmlServiceConfig config) { - this.config = config; this.ServiceName = config.Id; - this.ExtensionManager = new WinSWExtensionManager(config); - this.CanShutdown = true; this.CanStop = true; - this.CanPauseAndContinue = false; - this.AutoLog = true; - this.shuttingdown = false; + this.AutoLog = false; + + this.config = config; + this.ExtensionManager = new WinSWExtensionManager(config); // Register the event log provider eventLogProvider.Service = this; @@ -89,7 +82,7 @@ namespace WinSW string? line; while ((line = tr.ReadLine()) != null) { - this.LogInfo("Handling copy: " + line); + Log.Info("Handling copy: " + line); string[] tokens = line.Split('>'); if (tokens.Length > 2) { @@ -139,7 +132,7 @@ namespace WinSW return logAppender; } - public void LogEvent(string message) + public void WriteEntry(string message) { if (this.shuttingdown) { @@ -157,7 +150,7 @@ namespace WinSW } } - public void LogEvent(string message, EventLogEntryType type) + public void WriteEntry(string message, EventLogEntryType type) { if (this.shuttingdown) { @@ -186,9 +179,21 @@ namespace WinSW this.EventLog.WriteEntry(message, type); } - private void LogInfo(string message) + private void LogExited(string message, int exitCode) { - this.LogEvent(message); + if (exitCode == 0) + { + Log.Info(message); + } + else + { + Log.Warn(message); + } + } + + private void LogMinimal(string message) + { + this.WriteEntry(message); Log.Info(message); } @@ -201,6 +206,7 @@ namespace WinSW try { this.DoStart(); + this.LogMinimal("Service started successfully."); } catch (Exception e) { @@ -214,6 +220,7 @@ namespace WinSW try { this.DoStop(); + this.LogMinimal("Service stopped successfully."); } catch (Exception e) { @@ -228,6 +235,7 @@ namespace WinSW { this.shuttingdown = true; this.DoStop(); + this.LogMinimal("Service was shut down successfully."); } catch (Exception e) { @@ -256,7 +264,7 @@ namespace WinSW { Download download = downloads[i]; string downloadMessage = $"Downloading: {download.From} to {download.To}. failOnError={download.FailOnError.ToString()}"; - this.LogInfo(downloadMessage); + Log.Info(downloadMessage); tasks[i] = download.PerformAsync(); } @@ -294,7 +302,7 @@ namespace WinSW { using Process process = this.StartProcess(prestartExecutable, this.config.PrestartArguments); this.WaitForProcessToExit(process); - this.LogInfo($"Pre-start process '{process.Format()}' exited with code {process.ExitCode}."); + this.LogExited($"Pre-start process '{process.Format()}' exited with code {process.ExitCode}.", process.ExitCode); process.StopDescendants(additionalStopTimeout); } catch (Exception e) @@ -305,7 +313,7 @@ namespace WinSW string startArguments = this.config.StartArguments ?? this.config.Arguments; - this.LogInfo("Starting " + this.config.Executable); + Log.Info("Starting " + this.config.Executable); // Load and start extensions this.ExtensionManager.LoadExtensions(); @@ -322,7 +330,7 @@ namespace WinSW { using Process process = StartProcessLocked(); this.WaitForProcessToExit(process); - this.LogInfo($"Post-start process '{process.Format()}' exited with code {process.ExitCode}."); + this.LogExited($"Post-start process '{process.Format()}' exited with code {process.ExitCode}.", process.ExitCode); process.StopDescendants(additionalStopTimeout); this.startingProcess = null; @@ -353,7 +361,7 @@ namespace WinSW { using Process process = StartProcessLocked(prestopExecutable, this.config.PrestopArguments); this.WaitForProcessToExit(process); - this.LogInfo($"Pre-stop process '{process.Format()}' exited with code {process.ExitCode}."); + this.LogExited($"Pre-stop process '{process.Format()}' exited with code {process.ExitCode}.", process.ExitCode); process.StopDescendants(additionalStopTimeout); this.stoppingProcess = null; } @@ -363,16 +371,24 @@ namespace WinSW } } - this.LogInfo("Stopping " + this.config.Id); - this.orderlyShutdown = true; + Log.Info("Stopping " + this.config.Id); + this.process.EnableRaisingEvents = false; string? stopExecutable = this.config.StopExecutable; string? stopArguments = this.config.StopArguments; if (stopExecutable is null && stopArguments is null) { - Log.Debug("ProcessKill " + this.process.Id); - this.process.StopTree(this.config.StopTimeout); - this.ExtensionManager.FireOnProcessTerminated(this.process); + Process process = this.process; + Log.Debug("ProcessKill " + process.Id); + bool? result = process.Stop(this.config.StopTimeout); + this.LogMinimal($"Child process '{process.Format()}' " + result switch + { + true => $"canceled with code {process.ExitCode}.", + false => "terminated.", + null => $"finished with code '{process.ExitCode}'." + }); + this.process.StopDescendants(this.config.StopTimeout); + this.ExtensionManager.FireOnProcessTerminated(process); } else { @@ -407,7 +423,7 @@ namespace WinSW { using Process process = StartProcessLocked(poststopExecutable, this.config.PoststopArguments); this.WaitForProcessToExit(process); - this.LogInfo($"Post-stop process '{process.Format()}' exited with code {process.ExitCode}."); + this.LogExited($"Post-Stop process '{process.Format()}' exited with code {process.ExitCode}.", process.ExitCode); process.StopDescendants(additionalStopTimeout); this.stoppingProcess = null; } @@ -483,40 +499,29 @@ namespace WinSW private void OnMainProcessExited(Process process) { - string display = process.Format(); - - if (this.orderlyShutdown) + lock (this) { - this.LogInfo($"Child process '{display}' terminated."); - } - else - { - Log.Warn($"Child process '{display}' finished with code {process.ExitCode}."); - - process.StopDescendants(this.config.StopTimeout); - - lock (this) + try { + Log.Warn($"Child process '{process.Format()}' finished with code {process.ExitCode}."); + + process.StopDescendants(this.config.StopTimeout); + this.startingProcess?.StopTree(additionalStopTimeout); this.stoppingProcess?.StopTree(additionalStopTimeout); - } - // 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 (process.ExitCode == 0) - { - try + // 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 (process.ExitCode == 0) { this.SignalStopped(); } - catch (Exception e) - { - Log.Error(e); - } } - - Environment.Exit(process.ExitCode); + finally + { + Environment.Exit(process.ExitCode); + } } }