Pregunta

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?

¿Fue útil?

Solución

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

Otros consejos

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.

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.

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
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top