2

So I have a multithreaded C++ console application in which I want to handle the console close event in order to perform cleanup.

I have something to this effect:

bool running = true;
ServerSocket* server;
std::mutex mutex;
BOOL WINAPI HandlerRoutine(DWORD)
{
    running = false;
    server->shutdown();
    std::lock_guard<std::mutex> guard(mutex);
    return TRUE;
}

int main()
{
    std::lock_guard<std::mutex> guard(mutex);
    SetConsoleCtrlHandler(&HandlerRoutine, TRUE);
    try {
        ServerSocket server(27015);
        ::server = &server;
        while (running)
        {
            TCPSocket* client = server.accept(true);
        }
    }
    catch (const ServerSocket::ServerShutdownException&)
    {
        return 0;
    }
}

If I return from HandlerRoutine my program gets terminated unceremoniously, so I have to wait for main() to end.

However, after main ends I get an exception telling me a mutex was destroyed while busy, thrown from dynamic atexit destructor for 'mutex'(). This leads me to believe that static and global variables are destroyed as soon as main returns, leaving my handler function hanging around with invalid globals.

Is this the standard specified behaviour, and if so, any idea about how I can achieve my desired effect?

axnsan
  • 888
  • 10
  • 16

5 Answers5

4

In this scenario I would simply leak the mutex object. You don't want the destructor called prior to termination of the last thread, and there's no point in calling it during termination of the last thread.

std::mutex& mutex = *new mutex; // freed by OS at process exit
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 3
    Wow, someone making use of the memory leak operator. – R. Martinho Fernandes May 12 '14 at 15:24
  • 1
    But even simpler would be not to use a mutex at all. Remember how the handler is called, what you are doing is actually the opposite of what you would want. You're blocking on the mutex until the main process releases it by exiting. Which means the extra thread that Windows creates to process the signal runs on when the main thread is already dead. What you would really want is set `running` to false and let the main thread exit, killing the otherwise useless handler thread on its way out (if it has not already exited). – Damon May 12 '14 at 16:14
  • @damon: yes I can't see why the console handler is using a mutex. I'm addressing the question when a mutex is needed past the end of main, perhaps by a background print task or something like that. – Ben Voigt May 12 '14 at 16:56
  • 1
    @BenVoigt: You're certainly right on that part. The funny thing about the original program is, though, that it's the mutex which is _causing_ the problem in the question when the program would work much better without it, just setting the flag and letting the thread die whenever it does. :-) – Damon May 12 '14 at 18:30
  • Try running this [sample code](http://pastebin.com/xT3QwVej) as a windows console application, and end the program by closing the console. You will see why the handler needs to block. – axnsan May 12 '14 at 18:49
  • @axnsan: So the ["If none of the handlers returns TRUE, the default handler is called."](http://msdn.microsoft.com/en-us/library/windows/desktop/ms682066%28v=vs.85%29.aspx) isn't working as promised (i.e. the default handler is being called anyway)? Still, having the handler thread do a `WaitForSingleObject` on the main thread seems cleaner than using a mutex. `SetConsoleCtrlHandler` is highly OS-specific to begin with. – Ben Voigt May 12 '14 at 18:51
  • [MSDN docs for HandlerRoutine callback](http://msdn.microsoft.com/en-us/library/windows/desktop/ms683242%28v=vs.85%29.aspx): `Return TRUE. In this case, no other handler functions are called and the system terminates the process.` You do have a point about mixing std::mutex with WINAPI functions though. – axnsan May 12 '14 at 19:01
  • @axnsan: Well, you don't have to return. You could `ExitThread()` in the handler. And also see http://stackoverflow.com/a/21504556/103167 – Ben Voigt May 12 '14 at 19:07
  • @BenVoigt: is `ExitThread()` safe in a console control handler? I'd have expected the outcome to be undefined, e.g., Windows might decide to terminate the application. I'd have thought `Sleep(INFINITE)` would be safer. – Harry Johnston May 13 '14 at 04:36
1

You can try boost::application.

Here the example wait_for_termination_request.cpp

Sergei Nikulov
  • 5,029
  • 23
  • 36
0

Yes, your deduction is correct. Seems like the best option would be to unregister your handler and then wait for it to finish before returning from main(). But if that's not an option for whatever reason, something else you could do is to wrap all your globals in a struct:

struct Globals
{
   bool running;
   ServerSocket* server;
   std::mutex mutex;
};

Have a single, global shared_ptr to an instance of that struct:

std::shared_ptr<Globals> globals = std::make_shared<Globals>();

Make a copy of the shared_ptr in your handler:

BOOL WINAPI HandlerRoutine(DWORD)
{
    std::shared_ptr<Globals> myGlobals = globals;
    ...
}

And rely exclusively on myGlobals within the handler (there is no guarantee that the globals pointer itself will remain valid for the entire lifetime of the thread). That way everything is kept alive until everyone is done with it.

This assumes, of course, that globals is still valid when HandlerRoutine begins. If that's not the case (i.e. if the system can call the handler after main returns but before the process ends), then I'll delete this answer.

dlf
  • 9,045
  • 4
  • 32
  • 58
  • Your first idea wouldn't work because the system terminates my process as soon as the close handler returns. Your second idea is quite nice, however I'll go with Ben's solution for simplicty and elegance. Sadly, I don't yet have enough reputation to give you at least an upvote :( – axnsan May 12 '14 at 15:30
  • I'll survive. :) I'm not sure I understand why your process gets terminated that way, but I'm not familiar with the API you're using either. – dlf May 12 '14 at 15:41
  • It's a windows console application, and I'm using the windows API. ServerSocket and TCPSocket are my own classes that I included in an attempt to illustrate my purpose. See [the MSDN documentation for SetConsoleCtrlHandler](http://msdn.microsoft.com/en-us/library/windows/desktop/ms686016%28v=vs.85%29.aspx) if you want to understand why my process gets terminated like that. Basically it's because the user closes my console host and I get a notification to perform cleanup before being obliterated. – axnsan May 12 '14 at 16:01
0

I'd be tempted to play ping pong with mutexes. Have not one, but two mutexes.

The first is held by mymain (a copy of your main basically). main does nothing but call mymain.

The second is held by HandlerRoutine, and aquired by main after returning from mymain.

If you shut down without the HandlerRoutine being called, you simply fall off the end of main.

If you shut down after the HandlerRoutine is called, your main blocks on it finishing.

Simply planning to leak the mutex is insufficient, as if HandlerRoutine is called during the period that main was already planing to shutdown, its server->shutdown could be accessing invalid memory.

Some work on the second mutax (that HandlerRoutine accesses) needs to be done to deal with race conditions (being called -- or reaching the lock -- after main has already exited, and the process is cleaning up global variables?). Storing the HandlerRoutine mutex in a pointer, and using lock-free techniques to access it extremely carefully, possibly involving spin locks.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

To expand on the comments mentioning that the mutex is unnecessary, this is one alternative:

BOOL WINAPI HandlerRoutine(DWORD)
{
  running = false;
  server->shutdown();
  Sleep(INFINITE);
  return TRUE; // just to stop the compiler complaining
}
Harry Johnston
  • 35,639
  • 6
  • 68
  • 158