Search code examples
c#azure-devopsservicecontroller

How can I detect when it is safe to upgrade my windows service?


I have a windows service that polls a database and will do things depending on the records it finds.

I want to set up Azure Devops to automatically deploy an upgrade to this service.

In order to deploy the upgrade I need to stop the service.

Is there a way that I can tell whether this would interrupt it processing?

In my release pipeline, with a command task, I use

sc stop MyService

[Update]

Here is my simplified code

static class Program
{
    static void Main()
    {
        ServiceBase.Run(new ServiceBase[] { new Hoster() });
    }
}

  public sealed class Hoster : ServiceBase
{
    private IMyEngine _engine;
    private readonly EventHandler<EngineProgressEventArgs> _progressHandler;
    public Hoster()
    {
            _progressHandler = TrapEngineProgress;
    }

    protected override void OnStart(string[] args)
    {
        try
        {
            _engine = MyFactory.Create();
            _engine.Progress += _progressHandler;
            _engine.StartupEngine();
        }
        catch (Exception ex)
        {
            _logger.Error("OnStart failed", ex);
        }
    }

    protected override void OnStop()
    {
        if (_engine == null) return;
        _engine.Dispose();
        _engine.Progress -= _progressHandler;
        _engine = null;
    }


    private void TrapEngineProgress(object sender, EngineProgressEventArgs e)
    {
        switch (e.Type)
        {
            case ProgressType.Changed:
                Trace("Changed: " + e.Filename);
                break;
            case ProgressType.Created:
                Trace("Created: " + e.Filename);
                break;
            case ProgressType.Trace:
                Trace(e.Message);
                break;
            case ProgressType.Error:
                Error(e.Error, e.Message);
                break;
        }
    }
}

Solution

  • When you issue the SC STOP command, you are merely making a request for the service to stop itself. When the service receives the request, it can stop immediately or wait until it has finished important tasks — whatever it wants to do. Your service is in control.

    To avoid inappropriate interruption, your service's "stop workflow" should look like this:

    1. A request is made to stop the service (via SC STOP, NET STOP, etc.)
    2. The service's "listener thread" receives the request to stop
    3. The thread sets a flag that a stop has been requested and sets the service's state to SERVICE_STOP_PENDING
    4. The "processing thread" periodically checks the flag (at convenient times — likely between jobs)
    5. If the flag is set, the processing loop exits when convenient
    6. And since all processing is done, the service status is set to SERVICE_STOPPED and the entire service exits.

    With that approach, your service will never be interrupted in a "bad" place.