From 16bf50606b6e7e8d9185ab092a51e7d1ef50e269 Mon Sep 17 00:00:00 2001 From: Sam Storie Date: Mon, 25 Jul 2016 14:42:21 -0500 Subject: [PATCH] Exposed the ability to attach a custom exception handler This provides a way for clients using Topshelf to act upon any exceptions that are thrown within the service (including unhandled exceptions). --- src/SampleTopshelfService/Program.cs | 5 +++ .../HostConfigurators/HostConfigurator.cs | 7 ++++ .../HostConfigurators/HostConfiguratorImpl.cs | 5 +++ src/Topshelf/Hosts/ConsoleRunHost.cs | 12 +++++++ src/Topshelf/Hosts/InstallHost.cs | 5 +++ src/Topshelf/Runtime/HostSettings.cs | 5 +++ .../Runtime/Windows/WindowsHostSettings.cs | 2 ++ .../Runtime/Windows/WindowsServiceHost.cs | 32 +++++++++++++++++++ 8 files changed, 73 insertions(+) diff --git a/src/SampleTopshelfService/Program.cs b/src/SampleTopshelfService/Program.cs index d45cf047..160b6e6e 100644 --- a/src/SampleTopshelfService/Program.cs +++ b/src/SampleTopshelfService/Program.cs @@ -51,6 +51,11 @@ static int Main() x.AddCommandLineSwitch("throwonstart", v => throwOnStart = v); x.AddCommandLineSwitch("throwonstop", v => throwOnStop = v); x.AddCommandLineSwitch("throwunhandled", v => throwUnhandled = v); + + x.AddExceptionHandler(ex => + { + Console.WriteLine("Exception thrown - " + ex.Message); + }); }); } diff --git a/src/Topshelf/Configuration/HostConfigurators/HostConfigurator.cs b/src/Topshelf/Configuration/HostConfigurators/HostConfigurator.cs index 4d549a3e..bf5ce57e 100644 --- a/src/Topshelf/Configuration/HostConfigurators/HostConfigurator.cs +++ b/src/Topshelf/Configuration/HostConfigurators/HostConfigurator.cs @@ -116,5 +116,12 @@ public interface HostConfigurator /// /// void AddCommandLineDefinition(string name, Action callback); + + /// + /// Adds custom exception handler that will be called for any exception caught + /// by Topshelf while a service is starting, running or stopping. + /// + /// + void AddExceptionHandler(Action callback); } } \ No newline at end of file diff --git a/src/Topshelf/Configuration/HostConfigurators/HostConfiguratorImpl.cs b/src/Topshelf/Configuration/HostConfigurators/HostConfiguratorImpl.cs index 75e76bf8..350e07ec 100644 --- a/src/Topshelf/Configuration/HostConfigurators/HostConfiguratorImpl.cs +++ b/src/Topshelf/Configuration/HostConfigurators/HostConfiguratorImpl.cs @@ -182,6 +182,11 @@ public void AddCommandLineDefinition(string name, Action callback) _commandLineOptionConfigurators.Add(configurator); } + public void AddExceptionHandler(Action callback) + { + _settings.ExceptionHandler = callback; + } + public Host CreateHost() { Type type = typeof(HostFactory); diff --git a/src/Topshelf/Hosts/ConsoleRunHost.cs b/src/Topshelf/Hosts/ConsoleRunHost.cs index a73e3be4..0c06b8e1 100644 --- a/src/Topshelf/Hosts/ConsoleRunHost.cs +++ b/src/Topshelf/Hosts/ConsoleRunHost.cs @@ -102,6 +102,10 @@ public TopshelfExitCode Run() } catch (Exception ex) { + // Call the custom exception handler if it is not null + // + _settings.ExceptionHandler?.Invoke(ex); + _log.Error("An exception occurred", ex); return TopshelfExitCode.AbnormalExit; @@ -144,6 +148,10 @@ void HostControl.Restart() void CatchUnhandledException(object sender, UnhandledExceptionEventArgs e) { + // Call the custom exception handler if it is not null + // + _settings.ExceptionHandler?.Invoke((Exception)e.ExceptionObject); + _log.Fatal("The service threw an unhandled exception", (Exception)e.ExceptionObject); HostLogger.Shutdown(); @@ -185,6 +193,10 @@ void StopService() } catch (Exception ex) { + // Call the custom exception handler if it is not null + // + _settings.ExceptionHandler?.Invoke(ex); + _log.Error("The service did not shut down gracefully", ex); } finally diff --git a/src/Topshelf/Hosts/InstallHost.cs b/src/Topshelf/Hosts/InstallHost.cs index d3585591..ef8e322e 100644 --- a/src/Topshelf/Hosts/InstallHost.cs +++ b/src/Topshelf/Hosts/InstallHost.cs @@ -204,6 +204,11 @@ public TimeSpan StopTimeOut { get { return _settings.StopTimeOut; } } + + public Action ExceptionHandler + { + get { return _settings.ExceptionHandler; } + } } } } \ No newline at end of file diff --git a/src/Topshelf/Runtime/HostSettings.cs b/src/Topshelf/Runtime/HostSettings.cs index 6cb9bc10..798c0628 100644 --- a/src/Topshelf/Runtime/HostSettings.cs +++ b/src/Topshelf/Runtime/HostSettings.cs @@ -69,5 +69,10 @@ public interface HostSettings /// The amount of time to wait for the service to stop before timing out. Default is 10 seconds. /// TimeSpan StopTimeOut { get; } + + /// + /// A callback to provide additional exception handling beyond what is already provided + /// + Action ExceptionHandler { get; } } } \ No newline at end of file diff --git a/src/Topshelf/Runtime/Windows/WindowsHostSettings.cs b/src/Topshelf/Runtime/Windows/WindowsHostSettings.cs index d42b2cf3..1a92d122 100644 --- a/src/Topshelf/Runtime/Windows/WindowsHostSettings.cs +++ b/src/Topshelf/Runtime/Windows/WindowsHostSettings.cs @@ -105,5 +105,7 @@ public string ServiceName public TimeSpan StartTimeOut { get; set; } public TimeSpan StopTimeOut { get; set; } + + public Action ExceptionHandler { get; set; } } } \ No newline at end of file diff --git a/src/Topshelf/Runtime/Windows/WindowsServiceHost.cs b/src/Topshelf/Runtime/Windows/WindowsServiceHost.cs index 23311602..850e7966 100644 --- a/src/Topshelf/Runtime/Windows/WindowsServiceHost.cs +++ b/src/Topshelf/Runtime/Windows/WindowsServiceHost.cs @@ -130,6 +130,10 @@ protected override void OnStart(string[] args) } catch (Exception ex) { + // Call the custom exception handler if it is not null + // + _settings.ExceptionHandler?.Invoke(ex); + _log.Fatal("The service did not start successfully", ex); ExitCode = (int)TopshelfExitCode.StartServiceFailed; @@ -150,6 +154,10 @@ protected override void OnStop() } catch (Exception ex) { + // Call the custom exception handler if it is not null + // + _settings.ExceptionHandler?.Invoke(ex); + _log.Fatal("The service did not shut down gracefully", ex); ExitCode = (int)TopshelfExitCode.StopServiceFailed; throw; @@ -176,6 +184,10 @@ protected override void OnPause() } catch (Exception ex) { + // Call the custom exception handler if it is not null + // + _settings.ExceptionHandler?.Invoke(ex); + _log.Fatal("The service did not pause gracefully", ex); throw; } @@ -194,6 +206,10 @@ protected override void OnContinue() } catch (Exception ex) { + // Call the custom exception handler if it is not null + // + _settings.ExceptionHandler?.Invoke(ex); + _log.Fatal("The service did not resume successfully", ex); throw; } @@ -211,6 +227,10 @@ protected override void OnShutdown() } catch (Exception ex) { + // Call the custom exception handler if it is not null + // + _settings.ExceptionHandler?.Invoke(ex); + _log.Fatal("The service did not shut down gracefully", ex); ExitCode = (int)TopshelfExitCode.StopServiceFailed; throw; @@ -231,6 +251,10 @@ protected override void OnSessionChange(SessionChangeDescription changeDescripti } catch (Exception ex) { + // Call the custom exception handler if it is not null + // + _settings.ExceptionHandler?.Invoke(ex); + _log.Fatal("The service did not shut down gracefully", ex); ExitCode = (int)TopshelfExitCode.StopServiceFailed; throw; @@ -249,6 +273,10 @@ protected override void OnCustomCommand(int command) } catch (Exception ex) { + // Call the custom exception handler if it is not null + // + _settings.ExceptionHandler?.Invoke(ex); + _log.Error("Unhandled exception during custom command processing detected", ex); } } @@ -268,6 +296,10 @@ protected override void Dispose(bool disposing) void CatchUnhandledException(object sender, UnhandledExceptionEventArgs e) { + // Call the custom exception handler if it is not null + // + _settings.ExceptionHandler?.Invoke((Exception)e.ExceptionObject); + _log.Fatal("The service threw an unhandled exception", (Exception)e.ExceptionObject); HostLogger.Shutdown();