Avoid Windows Service Start-up Timeouts

This is part of a series on creating and installing Windows services.

  1. How to Create a Windows Service
  2. How to Install a Windows Service
  3. How to Run a Windows Service from Visual Studio
  4. How to Avoid Windows Service Start-up Timeouts
  5. How to Make a Windows Service Install Itself
  6. How to Handle Windows Service Errors
  7. How to Build a Windows Service Framework

Last time, we created an IService interface to abstract our application logic away from its hosting mechanism (console or service). In a real-world scenario, an implementor of IService may need to do substantial work in its constructor (such as connecting to external services and loading data). Let’s simulate this by having our demo implementation sleep for a while in its constructor:

class MyService : IService
{
    private bool _stopped;

    public MyService()
    {
        Thread.Sleep(TimeSpan.FromMinutes(1));
    }
    // snip remainder of class

If we install the service and run it via services.msc, the “starting” dialog will appear:

servicestart

However, after a while, the dialog will close and the following error message will appear, informing us that the service did not respond to the start or control request in a timely fashion:

service-timeouts-error

Upon dismissing the dialog, you will notice that the service is not running. It was forcibly terminated by the service control manager. Now, let’s repeat the experiment, but this time we will move the sleep statement to the start method:

class MyService : IService
{
    private bool _stopped;

    public MyService()
    {
    }

    public void Start()
    {
        Thread.Sleep(TimeSpan.FromMinutes(1));
        // snip remainder of class

Using this approach, the “starting” dialog will linger for a minute, but then it will disappear, leaving the service in the “started” state. The lesson to learn from this experiment is that the service control manager expects you to hand over control (via ServiceBase.Run) somewhat promptly, but it has basically unlimited patience for your “on start” handler. To ensure that our services do not time out, we should do our best to ensure that any time consuming work is deferred until the “on start” handler. While we are at it, we should encapsulate the dual-mode service startup logic that we wrote in our main method last time. To begin, we will factor out our startup logic into a new static class, BasicServiceStarter:

public static class BasicServiceStarter
{
    public static void Run(IService service, string serviceName)
    {
        if (Environment.UserInteractive)
        {
            using (service)
            {
                service.Start();
                Console.WriteLine(
                    "Running {0}, press any key to stop", serviceName);
                Console.ReadKey();
            }
        }
        else
        {
            ServiceBase.Run(new BasicService(service, serviceName));
        }
    }
}

We must modify our main method accordingly, to pass in the service and the service name:

class Program
{
    public const string Name = "DemoService";

    static void Main(string[] args)
    {
        BasicServiceStarter.Run(new MyService(), Name);
    }
}

With this approach, we are still constructing MyService before the call to ServiceBase.Run. However, with minor changes and clever use of generic type constraints, we can easily have our new infrastructure defer the construction of MyService until the ServiceBase.OnStart method is called:

public static class BasicServiceStarter
{
    public static void Run<T>(string serviceName) where T : IService, new()
    {
        if (Environment.UserInteractive)
        {
            using (var service= new T())
            {
                service.Start();
                Console.WriteLine(
                    "Running {0}, press any key to stop", serviceName);
                Console.ReadKey();
            }
        }
        else
        {
            ServiceBase.Run(new BasicService<T> { ServiceName = serviceName });
        }
    }
}

public class BasicService<T> : ServiceBase where T : IService, new()
{
    private IService _service;

    protected override void OnStart(string[] args)
    {
        _service = new T();
        _service.Start();
    }

    protected override void OnStop()
    {
        _service.Dispose();
    }
}

In this approach, BasicServiceStarter.Run takes the service as a generic type argument, rather than as an instance argument. By passing the type to BasicService as generic type argument as well, we can construct the service inside the BasicService.OnStart method. This of course requires another tweak to our main method:

class Program
{
    public const string Name = "DemoService";

    static void Main(string[] args)
    {
        BasicServiceStarter.Run<MyService>(Name);
    }
}

With this approach, the constructor of MyService can take as long as it wants, and we will avoid incurring the wrath of the service control manager, because we don’t invoke the constructor until OnStart is called.

 
Comments

No comments yet.

Leave a Reply