TopShelf service stuck in 'Stopping' state on exception
Asked Answered
S

2

7

I have a TopShelf (3.1.3) service that hangs in a 'Stopping' state when an exception is raised. As a result none of the service recovery steps get invoked, and uninstalling the service only succeeds if the service is manually killed via 'taskkill'.

What is the recommended approach for handling exceptions in TopShelf? I do not wish to simply swallow/log the exception and continue. Ideally the call to hostControl.Stop would indeed put the service in a 'stopped' state, however this is not the case.

This thread asks a similar question, however it does not provide an answer: How to catch exception and stop Topshelf service?

Thoughts?

HostFactory.Run(o =>
{
    o.UseNLog();
    o.Service<TaskRunner>();
    o.RunAsLocalSystem();
    o.SetServiceName("MyService");
    o.SetDisplayName("MyService");
    o.SetDescription("MyService");
    o.EnableServiceRecovery(r => r.RunProgram(1, "notepad.exe"));
});

public class TaskRunner : ServiceControl
{
    private CancellationTokenSource cancellationTokenSource;
    private Task mainTask;

    public bool Start(HostControl hostControl)
    {
        var cancellationToken = cancellationTokenSource.Token;
        this.mainTask = Task.Factory.StartNew(() =>
            {
                try
                {
                    while (!this.cancellationTokenSource.IsCancellationRequested)
                    {
                        // ... service logic ...
                        throw new Exception("oops!");
                    }
                }
                catch (Exception)
                {
                    hostControl.Stop();
                }
            });
        return true;
    }

    public bool Stop(HostControl control)
    {
        this.cancellationTokenSource.Cancel();
        this.mainTask.Wait();
        return true;
    }
}
Staub answered 16/9, 2014 at 21:23 Comment(1)
The service doesn't successfully stop because you are waiting on the task in the Stop method. Remove that line and it exits correctly.Grandioso
T
2

It looks like topshelf catches exceptions and tries to close nicely, this means that that you can get circular logic with closing. Example, you have a thread which throws an exception, topshelf catches it and asks all stoppables to stop. One of these is the service that started the offending thread so it calls thread.join(); this means that it's waiting for itself to finish so it can finish. I think this is what happened with your task.wait().

So, solutions really boil down to:

  • timeout on waits/joins
  • handle exceptions yourself before they get to topshelf
  • don't use topshelf

I ended up with having timeouts which ends the service in a timely fashion and allows my exception logging to run.

Tephra answered 5/7, 2016 at 2:27 Comment(0)
D
1

Try moving the while loop outside the try catch block

public bool Start(HostControl hostControl)
{
    var cancellationToken = cancellationTokenSource.Token;
    this.mainTask = Task.Factory.StartNew(() =>
        {
            while (!this.cancellationTokenSource.IsCancellationRequested)
            {                
                try
                {
                    // ... service logic ...
                    throw new Exception("oops!");
                }
                catch (Exception)
                {
                    hostControl.Stop();
                }
            }
        });
    return true;
}
Dentilingual answered 1/8, 2015 at 20:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.