Catch all

pull/601/head
NextTurn 2020-07-22 00:00:00 +08:00 committed by Next Turn
parent 048e8e57bb
commit 27bf1c00e1
5 changed files with 162 additions and 120 deletions

View File

@ -0,0 +1,20 @@
using System;
using System.Diagnostics;
namespace WinSW
{
internal static class FormatExtensions
{
internal static string Format(this Process process)
{
try
{
return $"{process.ProcessName} ({process.Id})";
}
catch (InvalidOperationException)
{
return $"({process.Id})";
}
}
}
}

View File

@ -2,6 +2,7 @@
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using WinSW.Util;
namespace WinSW
@ -30,7 +31,7 @@ namespace WinSW
/// <summary>
/// Convenience method to copy stuff from StreamReader to StreamWriter
/// </summary>
protected async void CopyStreamAsync(StreamReader reader, StreamWriter writer)
protected async Task CopyStreamAsync(StreamReader reader, StreamWriter writer)
{
string? line;
while ((line = await reader.ReadLineAsync()) != null)
@ -90,7 +91,7 @@ namespace WinSW
}
else
{
this.LogOutput(outputReader);
this.SafeLogOutput(outputReader);
}
if (this.ErrFileDisabled)
@ -99,15 +100,39 @@ namespace WinSW
}
else
{
this.LogError(errorReader);
this.SafeLogError(errorReader);
}
}
protected StreamWriter CreateWriter(FileStream stream) => new StreamWriter(stream) { AutoFlush = true };
protected abstract void LogOutput(StreamReader outputReader);
protected abstract Task LogOutput(StreamReader outputReader);
protected abstract void LogError(StreamReader errorReader);
protected abstract Task LogError(StreamReader errorReader);
private async void SafeLogOutput(StreamReader outputReader)
{
try
{
await this.LogOutput(outputReader);
}
catch (Exception e)
{
this.EventLogger.LogEvent("Unhandled exception in task. " + e, EventLogEntryType.Error);
}
}
private async void SafeLogError(StreamReader errorReader)
{
try
{
await this.LogError(errorReader);
}
catch (Exception e)
{
this.EventLogger.LogEvent("Unhandled exception in task. " + e, EventLogEntryType.Error);
}
}
}
public abstract class SimpleLogAppender : AbstractFileLogAppender
@ -126,14 +151,14 @@ namespace WinSW
this.ErrorLogFileName = this.BaseLogFileName + ".err.log";
}
protected override void LogOutput(StreamReader outputReader)
protected override Task LogOutput(StreamReader outputReader)
{
this.CopyStreamAsync(outputReader, this.CreateWriter(new FileStream(this.OutputLogFileName, this.FileMode)));
return this.CopyStreamAsync(outputReader, this.CreateWriter(new FileStream(this.OutputLogFileName, this.FileMode)));
}
protected override void LogError(StreamReader errorReader)
protected override Task LogError(StreamReader errorReader)
{
this.CopyStreamAsync(errorReader, this.CreateWriter(new FileStream(this.ErrorLogFileName, this.FileMode)));
return this.CopyStreamAsync(errorReader, this.CreateWriter(new FileStream(this.ErrorLogFileName, this.FileMode)));
}
}
@ -178,20 +203,20 @@ namespace WinSW
this.Period = period;
}
protected override void LogOutput(StreamReader outputReader)
protected override Task LogOutput(StreamReader outputReader)
{
this.CopyStreamWithDateRotationAsync(outputReader, this.OutFilePattern);
return this.CopyStreamWithDateRotationAsync(outputReader, this.OutFilePattern);
}
protected override void LogError(StreamReader errorReader)
protected override Task LogError(StreamReader errorReader)
{
this.CopyStreamWithDateRotationAsync(errorReader, this.ErrFilePattern);
return this.CopyStreamWithDateRotationAsync(errorReader, this.ErrFilePattern);
}
/// <summary>
/// Works like the CopyStream method but does a log rotation based on time.
/// </summary>
private async void CopyStreamWithDateRotationAsync(StreamReader reader, string ext)
private async Task CopyStreamWithDateRotationAsync(StreamReader reader, string ext)
{
PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(this.Pattern, this.Period);
periodicRollingCalendar.Init();
@ -237,20 +262,20 @@ namespace WinSW
{
}
protected override void LogOutput(StreamReader outputReader)
protected override Task LogOutput(StreamReader outputReader)
{
this.CopyStreamWithRotationAsync(outputReader, this.OutFilePattern);
return this.CopyStreamWithRotationAsync(outputReader, this.OutFilePattern);
}
protected override void LogError(StreamReader errorReader)
protected override Task LogError(StreamReader errorReader)
{
this.CopyStreamWithRotationAsync(errorReader, this.ErrFilePattern);
return this.CopyStreamWithRotationAsync(errorReader, this.ErrFilePattern);
}
/// <summary>
/// Works like the CopyStream method but does a log rotation.
/// </summary>
private async void CopyStreamWithRotationAsync(StreamReader reader, string ext)
private async Task CopyStreamWithRotationAsync(StreamReader reader, string ext)
{
StreamWriter writer = this.CreateWriter(new FileStream(this.BaseLogFileName + ext, FileMode.Append));
long fileLength = new FileInfo(this.BaseLogFileName + ext).Length;
@ -363,17 +388,17 @@ namespace WinSW
this.ZipDateFormat = zipdateformat;
}
protected override void LogOutput(StreamReader outputReader)
protected override Task LogOutput(StreamReader outputReader)
{
this.CopyStreamWithRotationAsync(outputReader, this.OutFilePattern);
return this.CopyStreamWithRotationAsync(outputReader, this.OutFilePattern);
}
protected override void LogError(StreamReader errorReader)
protected override Task LogError(StreamReader errorReader)
{
this.CopyStreamWithRotationAsync(errorReader, this.ErrFilePattern);
return this.CopyStreamWithRotationAsync(errorReader, this.ErrFilePattern);
}
private async void CopyStreamWithRotationAsync(StreamReader reader, string extension)
private async Task CopyStreamWithRotationAsync(StreamReader reader, string extension)
{
// lock required as the timer thread and the thread that will write to the stream could try and access the file stream at the same time
var fileLock = new object();

View File

@ -56,7 +56,7 @@ namespace WinSW.Util
if ((int)information.InheritedFromUniqueProcessId == processId)
{
Logger.Info("Found child process: " + process.Id + " Name: " + process.ProcessName);
Logger.Info($"Found child process '{process.Format()}'.");
children.Add(process);
}
else
@ -129,7 +129,7 @@ namespace WinSW.Util
/// <param name="envVars">Additional environment variables</param>
/// <param name="workingDirectory">Working directory</param>
/// <param name="priority">Priority</param>
/// <param name="callback">Completion callback. If null, the completion won't be monitored</param>
/// <param name="onExited">Completion callback. If null, the completion won't be monitored</param>
/// <param name="redirectStdin">Redirect standard input</param>
/// <param name="logHandler">Log handler. If enabled, logs will be redirected to the process and then reported</param>
public static void StartProcessAndCallbackForExit(
@ -139,7 +139,7 @@ namespace WinSW.Util
Dictionary<string, string>? envVars = null,
string? workingDirectory = null,
ProcessPriorityClass? priority = null,
Action<Process>? callback = null,
Action<Process>? onExited = null,
bool redirectStdin = true,
LogHandler? logHandler = null,
bool hideWindow = false)
@ -184,17 +184,17 @@ namespace WinSW.Util
}
// monitor the completion of the process
if (callback != null)
if (onExited != null)
{
processToStart.Exited += (sender, _) =>
{
try
{
callback((Process)sender!);
onExited((Process)sender!);
}
catch (Exception e)
{
Logger.Error("Thread failed unexpectedly", e);
Logger.Error("Unhandled exception in event handler.", e);
}
};

View File

@ -9,16 +9,6 @@ namespace WinSW.Logging
{
public WrapperService? Service { get; set; }
public EventLog? Locate()
{
WrapperService? service = this.Service;
if (service != null && !service.IsShuttingDown)
{
return service.EventLog;
}
// By default return null
return null;
}
public EventLog? Locate() => this.Service?.EventLog;
}
}

View File

@ -38,7 +38,7 @@ namespace WinSW
/// so don't try to kill us when the child exits.
/// </summary>
private bool orderlyShutdown;
private bool systemShuttingdown;
private bool shuttingdown;
/// <summary>
/// Version of Windows service wrapper
@ -48,11 +48,6 @@ namespace WinSW
/// </remarks>
public static Version Version => Assembly.GetExecutingAssembly().GetName().Version!;
/// <summary>
/// Indicates that the system is shutting down.
/// </summary>
public bool IsShuttingDown => this.systemShuttingdown;
public WrapperService(ServiceDescriptor descriptor)
{
this.descriptor = descriptor;
@ -62,7 +57,7 @@ namespace WinSW
this.CanStop = true;
this.CanPauseAndContinue = false;
this.AutoLog = true;
this.systemShuttingdown = false;
this.shuttingdown = false;
// Register the event log provider
eventLogProvider.Service = this;
@ -143,17 +138,13 @@ namespace WinSW
public void LogEvent(string message)
{
if (this.systemShuttingdown)
try
{
/* NOP - cannot call EventLog because of shutdown. */
this.EventLog.WriteEntry(message);
}
else
catch (Exception e)
{
try
{
this.EventLog.WriteEntry(message);
}
catch (Exception e)
if (!this.shuttingdown)
{
Log.Error("Failed to log event in Windows Event Log: " + message + "; Reason: ", e);
}
@ -162,17 +153,13 @@ namespace WinSW
public void LogEvent(string message, EventLogEntryType type)
{
if (this.systemShuttingdown)
try
{
/* NOP - cannot call EventLog because of shutdown. */
this.EventLog.WriteEntry(message, type);
}
else
catch (Exception e)
{
try
{
this.EventLog.WriteEntry(message, type);
}
catch (Exception e)
if (!this.shuttingdown)
{
Log.Error("Failed to log event in Windows Event Log. Reason: ", e);
}
@ -185,7 +172,51 @@ namespace WinSW
Log.Info(message);
}
internal void RaiseOnStart(string[] args) => this.OnStart(args);
internal void RaiseOnStop() => this.OnStop();
protected override void OnStart(string[] args)
{
try
{
this.DoStart();
}
catch (Exception e)
{
Log.Error("Failed to start service.", e);
throw;
}
}
protected override void OnStop()
{
try
{
this.DoStop();
}
catch (Exception e)
{
Log.Error("Failed to stop service.", e);
throw;
}
}
protected override void OnShutdown()
{
try
{
this.shuttingdown = true;
this.DoStop();
}
catch (Exception e)
{
Log.Error("Failed to shut down service.", e);
throw;
}
}
private void DoStart()
{
this.envs = this.descriptor.EnvironmentVariables;
@ -236,7 +267,7 @@ namespace WinSW
{
using Process process = this.StartProcess(prestartExecutable, this.descriptor.PrestartArguments);
this.WaitForProcessToExit(process);
this.LogInfo($"Pre-start process '{GetDisplayName(process)}' exited with code {process.ExitCode}.");
this.LogInfo($"Pre-start process '{process.Format()}' exited with code {process.ExitCode}.");
}
}
catch (Exception e)
@ -260,7 +291,7 @@ namespace WinSW
// in the xml for readability.
startArguments = Regex.Replace(startArguments, @"\s*[\n\r]+\s*", " ");
this.LogInfo("Starting " + this.descriptor.Executable + ' ' + startArguments);
this.LogInfo("Starting " + this.descriptor.Executable);
// Load and start extensions
this.ExtensionManager.LoadExtensions();
@ -277,14 +308,10 @@ namespace WinSW
string? poststartExecutable = this.descriptor.PoststartExecutable;
if (poststartExecutable != null)
{
using Process process = this.StartProcess(poststartExecutable, this.descriptor.PoststartArguments);
process.Exited += (sender, _) =>
using Process process = this.StartProcess(poststartExecutable, this.descriptor.PoststartArguments, process =>
{
Process process = (Process)sender!;
this.LogInfo($"Post-start process '{GetDisplayName(process)}' exited with code {process.ExitCode}.");
};
process.EnableRaisingEvents = true;
this.LogInfo($"Post-start process '{process.Format()}' exited with code {process.ExitCode}.");
});
}
}
catch (Exception e)
@ -293,43 +320,10 @@ namespace WinSW
}
}
protected override void OnShutdown()
{
// WriteEvent("OnShutdown");
try
{
this.systemShuttingdown = true;
this.StopIt();
}
catch (Exception ex)
{
Log.Error("Shutdown exception", ex);
}
}
protected override void OnStop()
{
// WriteEvent("OnStop");
try
{
this.StopIt();
}
catch (Exception ex)
{
Log.Error("Cannot stop exception", ex);
}
}
internal void RaiseOnStart(string[] args) => this.OnStart(args);
internal void RaiseOnStop() => this.OnStop();
/// <summary>
/// Called when we are told by Windows SCM to exit.
/// </summary>
private void StopIt()
private void DoStop()
{
try
{
@ -338,7 +332,7 @@ namespace WinSW
{
using Process process = this.StartProcess(prestopExecutable, this.descriptor.PrestopArguments);
this.WaitForProcessToExit(process);
this.LogInfo($"Pre-stop process '{GetDisplayName(process)}' exited with code {process.ExitCode}.");
this.LogInfo($"Pre-stop process '{process.Format()}' exited with code {process.ExitCode}.");
}
}
catch (Exception e)
@ -383,7 +377,7 @@ namespace WinSW
{
using Process process = this.StartProcess(poststopExecutable, this.descriptor.PoststopArguments);
this.WaitForProcessToExit(process);
this.LogInfo($"Post-stop process '{GetDisplayName(process)}' exited with code {process.ExitCode}.");
this.LogInfo($"Post-stop process '{process.Format()}' exited with code {process.ExitCode}.");
}
}
catch (Exception e)
@ -394,7 +388,7 @@ namespace WinSW
// Stop extensions
this.ExtensionManager.FireBeforeWrapperStopped();
if (this.systemShuttingdown && this.descriptor.BeepOnShutdown)
if (this.shuttingdown && this.descriptor.BeepOnShutdown)
{
Console.Beep();
}
@ -435,15 +429,15 @@ namespace WinSW
// Define handler of the completed process
void OnProcessCompleted(Process process)
{
string msg = process.Id + " - " + process.StartInfo.FileName + " " + process.StartInfo.Arguments;
string display = process.Format();
if (this.orderlyShutdown)
{
this.LogInfo("Child process [" + msg + "] terminated with " + process.ExitCode);
this.LogInfo($"Child process '{display}' terminated with code {process.ExitCode}.");
}
else
{
Log.Warn("Child process [" + msg + "] finished with " + process.ExitCode);
Log.Warn($"Child process '{display}' finished with code {process.ExitCode}.");
// if we finished orderly, report that to SCM.
// by not reporting unclean shutdown, we let Windows SCM to decide if it wants to
@ -465,13 +459,13 @@ namespace WinSW
envVars: this.envs,
workingDirectory: this.descriptor.WorkingDirectory,
priority: this.descriptor.Priority,
callback: OnProcessCompleted,
onExited: OnProcessCompleted,
logHandler: logHandler,
redirectStdin: redirectStdin,
hideWindow: this.descriptor.HideWindow);
}
private Process StartProcess(string executable, string? arguments)
private Process StartProcess(string executable, string? arguments, Action<Process>? onExited = null)
{
var info = new ProcessStartInfo(executable, arguments)
{
@ -481,13 +475,26 @@ namespace WinSW
};
Process process = Process.Start(info);
if (onExited != null)
{
process.Exited += (sender, _) =>
{
try
{
onExited((Process)sender!);
}
catch (Exception e)
{
Log.Error("Unhandled exception in event handler.", e);
}
};
process.EnableRaisingEvents = true;
}
process.StandardInput.Close();
return process;
}
private static string GetDisplayName(Process process)
{
return $"{process.ProcessName} ({process.Id})";
}
}
}