Question

Here is a slimmed down example of the code I'm working with:

void SleepIntr()
{
    printf("Entering sleep...\n");
    SleepEx(10000, TRUE);
    printf("Sleep done...\n");
}
static BOOL WINAPI ctrl_handler(DWORD dwCtrlType) {
    printf("ctrl_handler pressed\n");
    // Wake program up!
    return TRUE;
}
int _tmain(int argc, _TCHAR* argv[])
{
    SetConsoleCtrlHandler(ctrl_handler, TRUE);
    SleepIntr();
    printf("awake!\n"); 
    return 0;
}

The idea is that when ctr+c is pressed, the sleep should be interrupted and the program should continue executing and print "awake!" Or at least give me a stack trace of where the execution was when ctrl+c was pressed.

The SleepEx call could be any system call that blocks, such a blocking read from a socket.

What should I write in my ctrl_handler to have the call interrupted? Short of terminating the whole process I've found nothing. If it is impossible then I'll accept that as an answer but I really want to know why.

Here is the equivalent program coded for Linux, using nanosleep instead of SleepEx:

#include <signal.h>
#include <stdio.h>
#include <time.h>
void ctrl_c (int sid, siginfo_t *info, void *data) {
    printf("Im a ctrl_c\n");
}
int main (int argc, char *argv[]) {
    struct sigaction act = {0};
    act.sa_sigaction = &ctrl_c;
    sigaction(SIGINT, &act, NULL);
    struct timespec t, t2;
    t.tv_sec = 5;
    t.tv_nsec = 0;
    printf("sleep start\n");
    nanosleep (&t, &t2);
    printf("sleep end\n");
    return 0;
}

It works exactly like I want it. Ctrl+c in nanosleep immediately interrupts it and execution continues by printing "sleep end". Why can't something similar work for SleepEx on Windows?

EDIT Second argument to SleepEx should of course be TRUE. That was a typo.

Was it helpful?

Solution

There's no getting around the fact that you need to know what you're interrupting, and that the code has to be designed to be interrupted.

In the particular example you've chosen, replace the sleep with a timed wait on an event. You can then interrupt it by setting the event.

If you're making a synchronous I/O call, you can use CancelSynchronousIo but of course the code handling the I/O then has to properly handle the cancellation.

If you're in an alertable wait you can queue an APC to the thread.

If you're in a message loop you can post a message or have the loop use MsgWaitForMultipleObjectsEx.

Additional: demo code showing demonstrating how to wake SleepEx

#include <Windows.h>
#include <stdio.h>
#include <tchar.h>

HANDLE mainthread;

VOID CALLBACK DummyAPCProc(
  _In_  ULONG_PTR dwParam
)
{
    printf("User APC\n");
    return;
}

void SleepIntr()
{
    printf("Entering sleep...\n");
    SleepEx(10000, TRUE);
    printf("Sleep done...\n");
}

static BOOL WINAPI ctrl_handler(DWORD dwCtrlType) {
    printf("ctrl_handler pressed\n");
    // Wake program up!
    if (!QueueUserAPC(DummyAPCProc, mainthread, NULL))
    {
        printf("QueueUserAPC: %u\n", GetLastError());
        return TRUE;
    }
    return TRUE;
}

int _tmain(int argc, _TCHAR* argv[])
{
    if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &mainthread, GENERIC_ALL, FALSE, 0))
    {
        printf("DuplicateHandle: %u\n", GetLastError());
        return 0;
    }

    SetConsoleCtrlHandler(ctrl_handler, TRUE);
    SleepIntr();
    printf("awake!\n"); 
    return 0;
}   
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top