2

I have a Windows service written in C++ that functions as a TCP server listening for incoming connections.

I initialized the server socket and put the accept code in a separate thread. This will accept and process the incoming connections.

However, I also need to stop this thread in case the service receives the STOP signal. So I thought of creating an event object using CreateEvent and waiting for it to be signaled. This waiting would happen in the thread that creates the accept thread. So I could use the TerminateThread function to stop the accept thread when the STOP signal is received.

However, MSDN says that

TerminateThread is a dangerous function that should only be used in the most extreme cases.

How strictly should this be followed and is my approach correct? What could be another way of doing this?

Cygnus
  • 3,222
  • 9
  • 35
  • 65

2 Answers2

2

In Windows, you can wake up a blocking accept call from another thread simply by calling closesocket. The blocking accept call will return -1 and your code has a chance to break out of whatever loop it is in by checking some other exit condition that you have already set (e.g. global variable)

This also works with Mac (and likely BSD derivatives) with the close function, but not Linux. The more universal UNIX solution to this problem is here.

Some pseduo code for the Windows solution below.

SOCKET _listenSocket;
bool _needToExit = false;
HANDLE _hThread;

void MakeListenThreadExit()
{
   _needToExit = true;
   closesocket(_listenSocket);
   _listenSocket = INVALID_SOCKET;

   // wait for the thread to exit
   WaitForSingleObject(_hThread, INFINITE);       
}

DWORD __stdcall ListenThread(void* context)
{
    while (_needToExit == false)
    {            
        SOCKET client = accept(_listenSocket, (sockaddr*)&addr, &addrSize);
        if ((client == -1) || _needToExit)
        {
            break;
        } 
        ProcessClient(client);
    }    
    return 0;
}
Community
  • 1
  • 1
selbie
  • 100,020
  • 15
  • 103
  • 173
  • Never thought of this!! Thanks for the solution. – Cygnus Sep 24 '14 at 07:09
  • This doesn't work and should never be used. You have the following race condition: You are about to call `accept`. You call `closesocket`. Another thread (perhaps in a library you don't control) allocates a socket and gets the same descriptor you just closed. You now call `accept` on the *wrong* socket and steal connections from a library. There is simply no way to ensure that you are still blocked in `accept` when you get around to calling `closesocket`. – David Schwartz Sep 24 '14 at 08:13
  • @DavidSchwartz - It actually **does** work. As to whether or not it *should* be used is up for debate. The OP should carefully consider your point. I plan to double check, but I'm fairly certain that Win32 uses either incrementing socket handle identifiers or a reasonable algorithm to insure that socket handle values aren't re-used quickly - for dealing with these types of bugs. In your race condition you outline, you'd have to create (2^32)-1 socket handles within the race condition to get a duplicate socket handle that was previously closed. – selbie Sep 24 '14 at 08:32
  • @selbie "*I'm fairly certain that ...*" Basing your design decisions on what some particular system happens to do is not acceptable. At best you can say it happened to work on the system you tested it on. Designing based on this kind of assumption is foolish and has caused enormous amounts of real pain to real people when real programs really failed in the real world. Advising people to do nonsense like this without even a mention of its reliance on unspecified behavior is reckless and inexcusable. – David Schwartz Sep 24 '14 at 08:35
  • Uht oh... I just confirmed. Windows **will** re-use socket handles immediately after close. So I am dead wrong on this. – selbie Sep 24 '14 at 08:36
  • @selbie It wouldn't matter. The behavior is either guaranteed or it's not. Nobody cares what some particular machine happens to do unless you're writing software for one particular combination of OS, CPU, compiler, system libraries, ... You either have behavior that is guaranteed or you don't, and you're relying on hope and luck. – David Schwartz Sep 24 '14 at 08:37
  • @DavidSchwartz - If you have a better approach, I'm all ears. My advice back to the OP is that he can consider this solution at his own discretion. But my alternative advice is to have his other thread wake up the blocked accept call by making a `connect` call to it. There are other variations of this solution by using `select` with another socket and/or timeouts. And ultimately, an asynchronous I/O approach is good too. – selbie Sep 24 '14 at 08:41
  • @selbie There are a lot of possibilities. You can make a connection to yourself. You can use non-blocking socket operations. Generally, I strongly advise people *not* to use blocking I/O in multithreaded programs because it's too difficult to get right. – David Schwartz Sep 24 '14 at 08:51
1

In this situation, don't use accept() on a blocking socket. Use a non-blocking socket instead. Then you can use select() with a timeout so your thread can check for a termination condition periodically. Or better, use WSACreateEvent() with WSASelectEvent(). Create two event objects, one to detect client connections, and one to detect thread termination. You can then use WSAWaitForMultipleEvents() to wait on both events at the same time. Use WSASetEvent() to signal the termination event when needed, and call accept() or WSAAccept() whenever the other event is signalled. WSAWaitForMultipleEvents() will tell you which event to act on.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770