Skip to content
Next Next commit
Make WindowsServiceLifetime gracefully stop
WindowsServiceLifetime was not waiting for ServiceBase to stop the service.  As a result
we would sometimes end the process before notifying service control manager that the service
had stopped -- resulting in an error in the eventlog and sometimes a service restart.

We also were permitting multiple calls to Stop to occur - through SCM callbacks, and through
public API.  We must not call SetServiceStatus again once the service is marked as stopped.
  • Loading branch information
ericstj committed Mar 24, 2023
commit 98024705ad9acbd8c7f64e87ded9ff2581a4993a
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace Microsoft.Extensions.Hosting.WindowsServices
public class WindowsServiceLifetime : ServiceBase, IHostLifetime
{
private readonly TaskCompletionSource<object?> _delayStart = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly TaskCompletionSource<object?> _serviceStopped = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly ManualResetEventSlim _delayStop = new ManualResetEventSlim();
private readonly HostOptions _hostOptions;

Expand Down Expand Up @@ -92,14 +93,14 @@ private void Run()
{
_delayStart.TrySetException(ex);
}
_serviceStopped.TrySetResult(null);
}

public Task StopAsync(CancellationToken cancellationToken)
{
// Avoid deadlock where host waits for StopAsync before firing ApplicationStopped,
// and Stop waits for ApplicationStopped.
// Stop will cause the ServiceBase.Run method to complete and return, which completes _serviceStopped.
Task.Run(Stop, CancellationToken.None);
return Task.CompletedTask;
return _serviceStopped.Task;
}

// Called by base.Run when the service is ready to start.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,12 @@ public static void Run(ServiceBase service)

public void Stop()
{
if (_status.currentState == ServiceControlStatus.STATE_STOPPED || _status.currentState == default)
{
// nothing to do if the service is already stopped or never started
return;
}

DeferredStop();
}

Expand Down