eang
eang

Reputation: 1645

Cleanup code in Win32 console program

This question is based on the following question: Handle CTRL+C on Win32

I'm working on a multithread server, running on Linux and Windows. I can't use boost or other frameworks, only std c++.

I have a problem with the cleanup code on the win32 side. The linux side is working fine: when I want to shutdown the server, I send SIGINT (with CTRL+C), the signal handler sets a global variable and the main pthread executes the cleanup instructions (joining other pthreads, freeing heap memory, etc.).

On windows it looks not so simple to get the same behavior. I have written a simple test program to understand how the signal handlers works in windows.

#include <iostream>
#include <windows.h>

bool running;

BOOL WINAPI consoleHandler(DWORD signal) {

    if (signal == CTRL_C_EVENT) {
        running = false;
        std::cout << "[CTRL+C]\n";
        return TRUE;
    }

    return FALSE;
} 

int main(int argc, char **argv) {

    running = true;

    if (!SetConsoleCtrlHandler(consoleHandler, TRUE)) {

            std::cerr << "Error: " << GetLastError() << '\n';
            return -1;
    }

    std::cout << "Main thread working hard...\n";
    while (running) { ; }

    for (int i = 0; i < 20; i++)
        std::cout << "This is the " << i << "th fake cleanup instruction\n";

    return 0;
}

The output is the following:

$ test.exe
Main thread working hard...
[CTRL+C]
This is the 0th fake cleanup instruction
This is the 1th fake cleanup instruction

So the main thread is killed quickly, only after two instruction. In the previous question one of the suggestion was to move the cleanup code in the handler, but is not really helping:

suppose that the handler function looks like this:

BOOL WINAPI consoleHandler(DWORD signal) {

    if (signal == CTRL_C_EVENT) {
        running = false;
        std::cout << "[CTRL+C]\n";

        for (int i = 0; i < 20; i++)    
            std::cout << "This is the " << i << "th fake cleanup instruction\n";

        return TRUE;
    }

    return FALSE;
} 

Now the behavior is even worse! The output is:

$ test.exe
Main thread working hard...
[CTRL+C]
This is the

According to MSDN, it seems that the process is always killed:

A HandlerRoutine can perform any necessary cleanup, then take one of the following actions:

  • Call the ExitProcess function to terminate the process.
  • Return FALSE. If none of the registered handler functions returns TRUE, the default handler terminates the process.
  • Return TRUE. In this case, no other handler functions are called and the system terminates

the process.

Am I missing something obvious? What's the proper way to terminate a win32 console process and executes its cleanup code?

Upvotes: 2

Views: 2060

Answers (2)

Igor Skochinsky
Igor Skochinsky

Reputation: 25268

On Windows you can use a signal handler as well:

static void shutdown(int signum)
{
  printf("got signal #%d, terminating\n", signum);
  // cleanup
  _exit(1);
}

signal(SIGINT, shutdown);
signal(SIGTERM, shutdown);
signal(SIGSEGV, shutdown);

Ctrl-C is mapped to SIGINT just like on Linux.

This won't handle the user closing the console window using mouse, however.

Upvotes: 2

WhozCraig
WhozCraig

Reputation: 66194

This is one way to do it, though I would suggest you use an event HANDLE and WaitForSingleObject, as it would tend to be considerably more "yielding". I left the high velocity spin-loop in this just for you to peg one of your cores while still seeing the handler is intercepted.

I took the liberty of modifying your running state to be atomically evaluated and set respectively, as I didn't want the optimizer throwing out the eval in the main loop.

#include <iostream>
#include <cstdlib>
#include <windows.h>

// using an event for monitoring
LONG running = 1;

BOOL WINAPI consoleHandler(DWORD signal)
{
    if (signal == CTRL_C_EVENT) 
    {
        std::out << "Received Ctrl-C; shutting down..." << std::endl;
        InterlockedExchange(&running, 0);
        return TRUE;
    }
    return FALSE;
} 

int main(int argc, char **argv) 
{
    if (!SetConsoleCtrlHandler(consoleHandler, TRUE)) 
    {
        std::cerr << "Error: " << GetLastError() << '\n';
        return EXIT_FAILURE;
    }

    std::cout << "Main thread working hard...\n";
    while (InterlockedCompareExchange(&running, 0, 0) == 1);

    std::cout << "Graceful shutdown received. Shutting down now." << std::endl;

    return 0;
}

Output (note: I pressed ctrl-C, in case it wasn't obvious)

Main thread working hard...
Received Ctrl-C; shutting down...
Graceful shutdown received. Shutting down now.

Note: I tested this in debug and release in both 64 and 32 bit processes, no issues. And you can run it from the VS debugger. Just select "Continue" when informed you can continue if you have a handler installed, which you do.

Upvotes: 3

Related Questions