Trying to replicate the behavior of preventing Stop command in a .NET 7 Windows Service. As I read Topshelf code, their service host is subclassing ServiceBase
and in the OnStop
override, if the _serviceHandle.Stop(this)
returns false
, it throws exception. This sets ExitCode = 1064
and rethrows.
public class WindowsServiceHost :
ServiceBase,
Host,
HostControl
{
// omitted for brevity
protected override void OnStop()
{
try
{
_log.Info("[Topshelf] Stopping");
if (!_serviceHandle.Stop(this))
throw new TopshelfException("The service did not stop successfully (return false).");
_log.Info("[Topshelf] Stopped");
}
catch (Exception ex)
{
_settings.ExceptionCallback?.Invoke(ex);
_log.Fatal("The service did not shut down gracefully", ex);
ExitCode = (int) TopshelfExitCode.ServiceControlRequestFailed;
throw;
}
}
Then, looking at the ServiceBase
code, it simply calls OnStop
and if exception is throw, just re-set the service status to ‘previous’ (running) and rethrow.
private unsafe void DeferredStop ()
{
fixed (NativeMethods.SERVICE_STATUS *pStatus = &status)
{
int previousState = status.currentState;
status.checkPoint = 0;
status.waitHint = 0;
status.currentState = NativeMethods.STATE_STOP_PENDING;
NativeMethods.SetServiceStatus (statusHandle, pStatus);
try
{
OnStop();
// omitted for brevity...
}
catch (Exception e)
{
status.currentState = previousState;
NativeMethods.SetServiceStatus (statusHandle, pStatus);
WriteEventLogEntry (Res.GetString (Res.StopFailed, e.ToString ()), EventLogEntryType.Error);
throw;
}
} // fixed
} // DeferredStop
I’ve created my own class for .NET 7 deriving from WindowsServiceLifetime
(which itself derives from ServiceBase
) with the following ‘same’ code, but after I throw exception in OnStop
the service does stop.
[System.Runtime.Versioning.SupportedOSPlatform( "windows" )]
public class CancelableWindowsServiceLifetime : Microsoft.Extensions.Hosting.WindowsServices.WindowsServiceLifetime
{
private readonly ICancelableBackgroundService cancelableBackgroundService;
public CancelableWindowsServiceLifetime( IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ICancelableBackgroundService cancelableBackgroundService, ILoggerFactory loggerFactory, IOptions<HostOptions> optionsAccessor )
: this( environment, applicationLifetime, cancelableBackgroundService, loggerFactory, optionsAccessor, Options.Create( new WindowsServiceLifetimeOptions() ) )
{
}
public CancelableWindowsServiceLifetime( IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ICancelableBackgroundService cancelableBackgroundService, ILoggerFactory loggerFactory, IOptions<HostOptions> optionsAccessor, IOptions<WindowsServiceLifetimeOptions> windowsServiceOptionsAccessor )
: base( environment, applicationLifetime, loggerFactory, optionsAccessor, windowsServiceOptionsAccessor )
{
this.cancelableBackgroundService = cancelableBackgroundService;
}
protected override void OnStop()
{
if ( !cancelableBackgroundService.CanStop() )
{
ExitCode = 1064; // ERROR_EXCEPTION_IN_SERVICE
throw new ApplicationException( $"Unable to stop service at this time." );
}
base.OnStop();
}
}
I looked at the .NET 7 version of ServiceBase
and don’t see any real differences in the implementation. Any ideas why my version is not working?