Reputation: 1204
I have a thread which is used for reading from a TCP socket; I receive a message from the server to perform an automatic update. So, now the read thread has to call servicebase Stop()
to trigger the OnStop()
function. However the read thread has to be joined to the main thread in order to properly terminate the service. So now the OnStop()
function is waiting for the read thread to join but the read thread can't join because it's waiting for the Stop() function to finish running.
So basically it looks like this:
public void Start()
{
OnStart(new string[0]);
}
protected override void OnStart(string[] args)
{
stopEventRecv = new AutoResetEvent(false);
RecvThread = new Thread(RecvLoop);
RecvThread.Start();
}
protected override void OnStop()
{
// Doesn't matter because we are about to deadlock
stopEventRecv.Set();
// Dead lock
RecvThread.Join();
}
private void RecvLoop(object arg)
{
while (true)
{
if (stopEventRecv.WaitOne(5000))
{
return;
}
if (!IsConnected())
continue;
// here we get message from server saying to Stop so
// the message is processed and uses a callback where
// ServiceBase.Stop() is called.
// The reason for the callback isn't relevant to the
// question I don't think but i'm mentioning it in case.
// For the sake of the question I'll just call Stop()
// here to demonstrate the problem.
Stop();
}
}
How can I get around this? Is it necessary to join all threads before exiting the service? Would an Abort be OK here instead of a join?
Upvotes: 3
Views: 328
Reputation: 239664
The Service Control Manager (SCM) is responsible for sending commands to services to tell them to Start, Stop, Pause, as well as any custom commands you choose to define.
You write you OnXxx methods to react to the commands that the SCM sends to your service. In order to properly initiate a Stop, you ought to ask the SCM to stop your service, and you can do that using the ServiceController
class's Stop
method. This initiates the Stop but does not wait for it to complete.
This ensures that the SCM is aware that your service is deliberately stopping and didn't exit for any other reason. This is important because if any Recovery actions have been configured for your service, you don't want them to occur in this case.
Upvotes: 3
Reputation: 101473
One option to avoid the deadlock is to not join RecvThread
if Stop
was called from it. If Stop
was called from that thread - you know that it's done all useful work and is about to exit, so join is not required anyway:
if (RecvThread.ManagedThreadId != Thread.CurrentThread.ManagedThreadId)
RecvThread.Join();
Also, I'd personally avoid doing Join
without timeout. Better do Join(TimeSpan.FromSeconds(X))
and if given thread failed to finish during that timeout - do something meaninful (throw exception, abort that thread and so on). Anything is better than having to deadlock without even realizing it.
But better solution is mentioned by Damian in comments: use ServiceController.Stop
method, which will stop all dependent services, if any, and then will send stop signal to your service and return, without waiting for service to actually stop, thus also avoiding a deadlock.
Upvotes: 2