Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TopShelf service stuck in 'Stopping' state on exception

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;
    }
}
like image 976
mindlessgoods Avatar asked Nov 10 '22 01:11

mindlessgoods


1 Answers

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.

like image 117
Lloyd Stockman Avatar answered Nov 14 '22 22:11

Lloyd Stockman