On Windows 10, I'm waiting for input from the console using
WaitForSingleObject
( GetStdHandle(STD_INPUT_HANDLE), ... )
and to cancel this waiting using CancelSynchronousIo()
.
But the cancellation does nothing (returns 0
and GetLastError()
is ERROR_NOT_FOUND
).
Any idea what I could be doing wrong?
Should I be able to cancel this waiting for new input on stdin?
(I actually want to do this with any HANDLE
whose GetFileType()
is FILE_TYPE_CHAR
, not only stdin, but stdin is certainly the most important use case and the simplest to test with).
Related discussions I've found:
- Synchronous ReadFile() on stdin cannot be unblocked by CancelSynchronousIo()
- win32: how stop ReadFile (stdin|pipe)
But unfortunately they only discuss ReadFile()
, not WaitForSingleObject()
. I've also tried WaitForMultipleObjects()
(with just a single object in the array), same problem.
(Background: I'm trying to improve input handling in the GHC Haskell compiler runtime.)
CancelSynchronousIo
cancel I/O operations that are issued by the specified thread. more concrete it cancel IRP
packets which associated with specified thread via call IoCancelIrp
. if use undocumented NtCancelSynchronousIoFile
(CancelSynchronousIo
internally call it with IoRequestToCancel = 0
) we can be more selective - cancel only i/o request which used specified IoRequestToCancel
(system check that Irp->UserIosb == IoRequestToCancel
and cancel only this requests)
but WaitForSingleObject
this is not I/O request. this call not create any IRP
which can be canceled. so - no way do this.
however if you use WaitForSingleObjectEx
with bAlertable
set to TRUE
- you can break wait by queue apc to thread by using QueueUserAPC
. also if use NtWaitForSingleObject
instead WaitForSingleObjectEx
we can also alert thread by using undocumented call NtAlertThread
. in this case NtWaitForSingleObject
will break with STATUS_ALERTED
(note that WaitForSingleObjectEx
which internally call NtWaitForSingleObject
do special check for STATUS_ALERTED
and in case this status - again run NtWaitForSingleObject
- as result we can not break WaitForSingleObjectEx
by call NtAlertThread
, but NtWaitForSingleObject
will be breaked.
so if you need break waiting for std input - create additional thread, which must call not CancelSynchronousIo
(this senseless) but QueueUserAPC
or NtAlertThread
(only if you use NtWaitForSingleObject
for wait). and input thread must wait in alertable state. so demo code can look like:
extern "C" NTSYSCALLAPI NTSTATUS NTAPI NtAlertThread(HANDLE ThreadHandle);
VOID NTAPI OnApc(ULONG_PTR Parameter)
{
DbgPrint("OnApc(%p)\n", Parameter);
}
DWORD CALLBACK BreakWaitThread(HANDLE hThread)
{
switch (LONG status = MessageBoxW(0, L"Use Apc(yes) or Alert(No) ?", L"BreakWaitThread",
MB_ICONQUESTION|MB_YESNOCANCEL|MB_DEFBUTTON3))
{
case IDYES:
if (!QueueUserAPC(OnApc, hThread, 0))
{
DbgPrint("QueueUserAPC=%u\n", GetLastError());
}
break;
case IDNO:
if (0 > (status = NtAlertThread(hThread)))
{
DbgPrint("AlertThread=%x\n", status);
}
break;
case IDCANCEL:
DbgPrint("canceled\n");
break;
default:
DbgPrint("MessageBox=%x\n", status);
}
CloseHandle(hThread);
return 0;
}
void ConsoleLoop(HANDLE hStdIn)
{
ULONG NumberOfEvents, NumberOfEventsRead, n;
INPUT_RECORD buf[8], *p;
for (;;)
{
switch (ZwWaitForSingleObject(hStdIn, TRUE, 0))
//switch (WaitForSingleObjectEx(hStdIn, INFINITE, TRUE))
{
case WAIT_OBJECT_0:
while (GetNumberOfConsoleInputEvents(hStdIn, &NumberOfEvents) && NumberOfEvents)
{
do
{
NumberOfEventsRead = min(RTL_NUMBER_OF(buf), NumberOfEvents);
if (ReadConsoleInput(hStdIn, buf, NumberOfEventsRead, &NumberOfEventsRead) && NumberOfEventsRead)
{
n = NumberOfEventsRead;
p = buf;
do
{
if (p->EventType == KEY_EVENT)
{
DbgPrint("%u(%u) %C %x %x %x\n",
p->Event.KeyEvent.bKeyDown,
p->Event.KeyEvent.wRepeatCount,
p->Event.KeyEvent.uChar.UnicodeChar,
p->Event.KeyEvent.wVirtualKeyCode,
p->Event.KeyEvent.wVirtualScanCode,
p->Event.KeyEvent.dwControlKeyState);
if (VK_OEM_PERIOD == p->Event.KeyEvent.wVirtualKeyCode)
{
return ;//if user type '.' return for demo
}
}
} while (p++, --n);
}
else
{
FlushConsoleInputBuffer(hStdIn);
break;
}
} while (NumberOfEvents -= NumberOfEventsRead);
}
continue;
case STATUS_USER_APC:
DbgPrint("\nUSER_APC\n");
return;
case STATUS_ALERTED:
DbgPrint("\nALERTED\n");
return;
case WAIT_FAILED :
DbgPrint("\nWAIT_FAILED=%u\n", GetLastError());
return;
default:
__debugbreak();
return;
}
}
}
void SimpleDemo()
{
if (HANDLE hCurrentThread = OpenThread(THREAD_ALERT|THREAD_SET_CONTEXT , FALSE, GetCurrentThreadId()))
{
ULONG dwThreadId;
HANDLE hThread = CreateThread(0, 0, BreakWaitThread, hCurrentThread, 0, &dwThreadId);
if (hThread)
{
ConsoleLoop(GetStdHandle(STD_INPUT_HANDLE));
PostThreadMessage(dwThreadId, WM_QUIT, 0, 0);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
}
else
{
CloseHandle(hCurrentThread);
}
}
}
Console I/O is difficult to use asynchronously, it is simply not designed for it. See IO Completion Ports (IOCP) and Asynchronous I/O through STDIN, STDOUT and STDERR for some possible workarounds.
If that is not an option for you, then you will have to either:
use WaitForSingleObject()
in a loop with a short timeout. Create a flag variable that your loop can look at on each iteration to break the loop if the flag is set.
use WaitForMutipleObjects()
, giving it 2 HANDLE
s to wait on - one for the console (or whatever), and one for an event object from CreateEvent()
. Then you can signal the event with SetEvent()
when you want to break the wait. The return value of WaitForMutipleObjects()
will tell you which HANDLE
was signaled.