2

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?

Community
  • 1
  • 1
eang
  • 1,615
  • 7
  • 21
  • 41
  • 2
    Are you sure the problems you are seeing isnät related to the lack of flushing of `cout`, based on the fact that you are returning `true` from the cleanup, so the code terminates without calling the flush that you'd normally see in `exit`? – Mats Petersson Sep 12 '13 at 21:08
  • Just replaced `'\n'` with `std::endl`, the output is the same. – eang Sep 12 '13 at 21:12
  • Then I'm afraid I have no further suggestion. – Mats Petersson Sep 12 '13 at 21:15
  • @Mats Could you simply run the test program and check the output? – eang Sep 12 '13 at 21:33
  • Not without setting up a Windows system... ;) And I'm not really willing to do that just for the sake of helping you, I'm afraid... – Mats Petersson Sep 12 '13 at 21:34
  • Oh, obviously not. Thank you anyway. – eang Sep 12 '13 at 21:37
  • 2
    I'm afraid you're slightly mislead. The documentation for a handler proc return value concerning imminent shutdown is *not* for ctrl-c handling. It is for `CTRL_CLOSE_EVENT`, `CTRL_LOGOFF_EVENT`, and `CTRL_SHUTDOWN_EVENT`. A regular ctrl-C handler should have no issues with handling the ctrl-c, eating the notification, and setting the appropriate graceful shutdown. I'll try and put together a sample for you if I get a chance. – WhozCraig Sep 13 '13 at 06:35
  • 1
    @MatsPetersson: The solution is to keep the handler running (waiting) till the main finishes. As mentioned in the linked question, when the handler returns it kills it. You only have 10s though, that was added in windows 7+. – unixsnob Jun 06 '17 at 10:57

2 Answers2

3

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.

WhozCraig
  • 65,258
  • 11
  • 75
  • 141
  • I'm not sure what do you mean with "peg one of your cores", but with this code the problem is the same: the main thread is killed after the second iteration of the for that I used in the original question's code. – eang Sep 13 '13 at 14:41
  • @ital I'm fairly sure I know why that happens. By setting `running=0` *prior* to the for-loop you're instructing `main()` to shut down. Since the handler is running in a different thread (the documentation says it will do so) you have introduced a lifetime-concurrency issue around `std::cout`. The standard requires it only be available during the lifetime of `main()`. Once `main()`returns on the main-thread, the RT-lib is closing it down. In your code, following the model I have above, what happens when you set `running` to zero *after* your for-loop rather than *before* ? – WhozCraig Sep 13 '13 at 14:51
  • Just tested (for-loop in the handler and `running=false` after it) , unfortunately the behavior doesn't change: the first two `cout` are ok, the third one is stopped in the middle. – eang Sep 13 '13 at 15:20
  • @ital ok something is different. Can you post your current code as-written in an update to your question, at the end, tagged with **UPDATE** to discern it from your other attempts. I must be missing something in your code, or you are missing something in mine. – WhozCraig Sep 13 '13 at 15:25
2

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.

Igor Skochinsky
  • 24,629
  • 2
  • 72
  • 109
  • Same here, it seems that the main thread is killed quickly also using the "unix" handling way. – eang Sep 13 '13 at 14:42