2

There is an extensive discussion of the risk that a write to shared state in a signal handler could be interrupted midway by another signal handler.

I handle CTRL+C in a function that is set up with SetConsoleCtrlHandler(). I suppose that the press of CTRL+C should be considered a signal.

I would like to handle two CTRL+C presses immediately after another differently from just a single CTRL+C press, so I need to track the time. The function clock_gettime() in POSIX 2016 is signal-safe, so sounds like a good choice. However, I am on Windows, which is not POSIX-compliant.

I have seen answers advising to use std::chrono, but I have not found any documentation on whether this is safe to do in a signal handler.

How can I correctly track the passing of time since the last signal inside my SetConsoleCtrlHandler() handler?

I am fine with the time measurements being inaccurate.

3
  • 1
    Are you looking for a solution specifically for Windows or for a portable solution that works for a Posix OS as well as Windows? Is your program single threaded or multithreaded? Commented Aug 22 at 13:53
  • @AdrianMcCarthy It's a multithreaded program, and only needs to work on windows Commented Aug 22 at 14:04
  • 2
    What's wrong with GetTickCount? Commented Aug 22 at 14:33

1 Answer 1

9

I suppose that the press of CTRL+C should be considered a signal.

Yes, but maybe not. Windows supports only a limited set of signals. And of those, SIG_INT is a special case. In fact, the documentation says, "SIG_INT is not supported in any Win32 in application."

SetConsoleCtrlHandler gives console applications the ability to handle events that would be normally be SIG_INT signals on other systems, but, as far as I can tell, it's doesn't use the signal architecture.

The documentation for SetConsoleCtrlHandler and the handler routine callback does not list many limitations on what you can do in the callback.

It does warn that the callback will be executed in a "new" thread. (It appears to queue to a thread pool, so it's not always a new thread.) Thus it's vital that any data that needs to be accessed by the main program and the callback (or by different invocations of the callback) must be synchronized.

But give that it happens in a separate thread, I suspect that, unlike an actual Windows signal handler, calling something like GetTickCount from a console control handler is fair game.

I would still strive to return from the handler as quickly as possible. Despite running in a separate thread, it's unclear under what circumstances the main thread might block on the callback.

Handlers for signals could interrupt your console control handler, but they are subject to the restrictions, so there shouldn't be any interference. From the signal handler's point of view, your console control handler is just like all the other code in your application that could be interrupted.

It seems unlikely the console subsystem would invoke your callback while an actual signal handler is executing. And, if it did, it would be on a separate thread.


#include <Windows.h>
#include <consoleapi.h>  // must come after Windows.h

#include <atomic>
#include <print>

std::atomic_bool thread_was_reused = false;
std::atomic<DWORD> last_ctrl_c_tick_count = 0;
std::atomic_bool last_ctrl_c_was_double = false;

BOOL WINAPI OnCtrlEvent(DWORD event_type) {
    static bool called_on_this_thread_before = false;
    thread_was_reused.store(called_on_this_thread_before);
    called_on_this_thread_before = true;

    if (event_type == CTRL_C_EVENT) {
        auto const tick = ::GetTickCount();
        auto const delta = tick - last_ctrl_c_tick_count.load();
        last_ctrl_c_was_double.store(delta < 250);
        last_ctrl_c_tick_count.store(tick);
        return TRUE;
    }
    return FALSE;
}

int main() {
    ::SetConsoleCtrlHandler(OnCtrlEvent, TRUE);

    ::GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
    ::GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
    std::print("Two rapid Ctrl+C events were generated.\n");
    std::print("thread was re-used:       {}\n", thread_was_reused.load());
    std::print("last Ctrl+C was a double: {}\n", last_ctrl_c_was_double.load());
    std::print("last Ctrl+C tick count:   {}\n", last_ctrl_c_tick_count.load());
    ::Sleep(1234);
    ::GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
    std::print("\nA single Ctrl+C event was generated.\n");
    std::print("thread was re-used:       {}\n", thread_was_reused.load());
    std::print("last Ctrl+C was a double: {}\n", last_ctrl_c_was_double.load());
    std::print("last Ctrl+C tick count:   {}\n", last_ctrl_c_tick_count.load());

    return 0;
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.