diff --git a/Topshelf.Linux/LinuxConfigurationExtensions.cs b/Topshelf.Linux/LinuxConfigurationExtensions.cs index 2c30276..80a17a0 100644 --- a/Topshelf.Linux/LinuxConfigurationExtensions.cs +++ b/Topshelf.Linux/LinuxConfigurationExtensions.cs @@ -15,6 +15,7 @@ using System; using Topshelf.HostConfigurators; +using Topshelf.Linux; using Topshelf.Runtime.Linux; namespace Topshelf @@ -28,6 +29,7 @@ public static void UseLinuxIfAvailable(this HostConfigurator configurator) // Needed to overcome mono-service style arguments. configurator.ApplyCommandLine(MonoHelper.GetUnparsedCommandLine()); configurator.UseEnvironmentBuilder((cfg) => new LinuxHostEnvironmentBuilder(cfg)); + configurator.UseHostBuilder((environment, settings) => new LinuxRunBuilder(environment, settings)); } } } diff --git a/Topshelf.Linux/LinuxConsoleRunHost.cs b/Topshelf.Linux/LinuxConsoleRunHost.cs new file mode 100644 index 0000000..ceea693 --- /dev/null +++ b/Topshelf.Linux/LinuxConsoleRunHost.cs @@ -0,0 +1,240 @@ + +using Mono.Unix; +using Mono.Unix.Native; + +namespace Topshelf.Linux +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Threading; +#if !NET35 + using System.Threading.Tasks; +#endif + using Logging; + using Microsoft.Win32; + using Runtime; + + public class LinuxConsoleRunHost : + Host, + HostControl + { + readonly LogWriter _log = HostLogger.Get(); + readonly HostEnvironment _environment; + readonly ServiceHandle _serviceHandle; + readonly HostSettings _settings; + int _deadThread; + + TopshelfExitCode _exitCode; + volatile bool _hasCancelled; + + public LinuxConsoleRunHost(HostSettings settings, HostEnvironment environment, ServiceHandle serviceHandle) + { + if (settings == null) + throw new ArgumentNullException("settings"); + if (environment == null) + throw new ArgumentNullException("environment"); + + _settings = settings; + _environment = environment; + _serviceHandle = serviceHandle; + + if (settings.CanSessionChanged) + { + SystemEvents.SessionSwitch += OnSessionChanged; + } + } + + void OnSessionChanged(object sender, SessionSwitchEventArgs e) + { + var arguments = new ConsoleSessionChangedArguments(e.Reason); + + _serviceHandle.SessionChanged(this, arguments); + } + + + public TopshelfExitCode Run() + { + Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); + + AppDomain.CurrentDomain.UnhandledException += CatchUnhandledException; + + if (_environment.IsServiceInstalled(_settings.ServiceName)) + { + if (!_environment.IsServiceStopped(_settings.ServiceName)) + { + _log.ErrorFormat("The {0} service is running and must be stopped before running via the console", + _settings.ServiceName); + + return TopshelfExitCode.ServiceAlreadyRunning; + } + } + + bool started = false; + try + { + _log.Debug("Starting up as a console application"); + _exitCode = TopshelfExitCode.Ok; + + Console.Title = _settings.DisplayName; + Console.CancelKeyPress += HandleCancelKeyPress; + + if (!_serviceHandle.Start(this)) + throw new TopshelfException("The service failed to start (return false)."); + + started = true; + + _log.InfoFormat("The {0} service is now running, press Control+C to exit.", _settings.ServiceName); + + var terminationSignals = GetUnixTerminationSignals(); + UnixSignal.WaitAny(terminationSignals); + } + catch (Exception ex) + { + _log.Error("An exception occurred", ex); + + return TopshelfExitCode.AbnormalExit; + } + finally + { + if (started) + StopService(); + HostLogger.Shutdown(); + } + return _exitCode; + } + + + void HostControl.RequestAdditionalTime(TimeSpan timeRemaining) + { + + } + + + void HostControl.Stop() + { + _log.Info("Service Stop requested, exiting."); + } + + + void HostControl.Restart() + { + _log.Info("Service Restart requested, but we don't support that here, so we are exiting."); + } + + + void CatchUnhandledException(object sender, UnhandledExceptionEventArgs e) + { + _log.Fatal("The service threw an unhandled exception", (Exception)e.ExceptionObject); + + HostLogger.Shutdown(); + + if (e.IsTerminating) + { + _exitCode = TopshelfExitCode.UnhandledServiceException; + +#if !NET35 + // it isn't likely that a TPL thread should land here, but if it does let's no block it + if (Task.CurrentId.HasValue) + { + return; + } +#endif + + // this is evil, but perhaps a good thing to let us clean up properly. + int deadThreadId = Interlocked.Increment(ref _deadThread); + Thread.CurrentThread.IsBackground = true; + Thread.CurrentThread.Name = "Unhandled Exception " + deadThreadId.ToString(); + while (true) + Thread.Sleep(TimeSpan.FromHours(1)); + } + } + + + void StopService() + { + try + { + if (_hasCancelled == false) + { + _log.InfoFormat("Stopping the {0} service", _settings.ServiceName); + + if (!_serviceHandle.Stop(this)) + throw new TopshelfException("The service failed to stop (returned false)."); + } + } + catch (Exception ex) + { + _log.Error("The service did not shut down gracefully", ex); + } + finally + { + _serviceHandle.Dispose(); + + _log.InfoFormat("The {0} service has stopped.", _settings.ServiceName); + } + } + + + void HandleCancelKeyPress(object sender, ConsoleCancelEventArgs consoleCancelEventArgs) + { + if (consoleCancelEventArgs.SpecialKey == ConsoleSpecialKey.ControlBreak) + { + _log.Error("Control+Break detected, terminating service (not cleanly, use Control+C to exit cleanly)"); + return; + } + + consoleCancelEventArgs.Cancel = true; + + if (_hasCancelled) + return; + + _log.Info("Control+C detected, attempting to stop service."); + if (_serviceHandle.Stop(this)) + { + _hasCancelled = true; + } + else + { + _hasCancelled = false; + _log.Error("The service is not in a state where it can be stopped."); + } + } + + private static UnixSignal[] GetUnixTerminationSignals() + { + return new[] + { + new UnixSignal(Signum.SIGINT), + new UnixSignal(Signum.SIGTERM), + new UnixSignal(Signum.SIGQUIT), + new UnixSignal(Signum.SIGHUP) + }; + } + + + + class ConsoleSessionChangedArguments : + SessionChangedArguments + { + readonly SessionChangeReasonCode _reasonCode; + readonly int _sessionId; + + public ConsoleSessionChangedArguments(SessionSwitchReason reason) + { + _reasonCode = (SessionChangeReasonCode)Enum.ToObject(typeof(SessionChangeReasonCode), (int)reason); + _sessionId = Process.GetCurrentProcess().SessionId; + } + + public SessionChangeReasonCode ReasonCode + { + get { return _reasonCode; } + } + + public int SessionId + { + get { return _sessionId; } + } + } + } +} diff --git a/Topshelf.Linux/LinuxRunBuilder.cs b/Topshelf.Linux/LinuxRunBuilder.cs new file mode 100644 index 0000000..1fdd0e7 --- /dev/null +++ b/Topshelf.Linux/LinuxRunBuilder.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Topshelf.Builders; + +namespace Topshelf.Linux +{ + using System; + using Hosts; + using Logging; + using Runtime; + + + public class LinuxRunBuilder : RunBuilder + { + static readonly LogWriter _log = HostLogger.Get(); + + public LinuxRunBuilder(HostEnvironment environment, HostSettings settings) : base(environment, settings) + { + } + + public override Host Build(ServiceBuilder serviceBuilder) + { + ServiceHandle serviceHandle = serviceBuilder.Build(Settings); + return CreateHost(serviceHandle); + } + + Host CreateHost(ServiceHandle serviceHandle) + { + if (Environment.IsRunningAsAService) + { + + _log.Debug("Running as a service, creating service host."); + return Environment.CreateServiceHost(Settings, serviceHandle); + } + + _log.Debug("Running as a console application, creating the console host."); + return new LinuxConsoleRunHost(Settings, Environment, serviceHandle); + } + } +} diff --git a/Topshelf.Linux/Topshelf.Linux.csproj b/Topshelf.Linux/Topshelf.Linux.csproj index af3375d..8f05385 100644 --- a/Topshelf.Linux/Topshelf.Linux.csproj +++ b/Topshelf.Linux/Topshelf.Linux.csproj @@ -33,6 +33,10 @@ 4 + + ..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll + True + @@ -47,6 +51,8 @@ + + diff --git a/Topshelf.Linux/packages.config b/Topshelf.Linux/packages.config index a64ec1b..de06f27 100644 --- a/Topshelf.Linux/packages.config +++ b/Topshelf.Linux/packages.config @@ -1,5 +1,4 @@  - - + + \ No newline at end of file