Skip to content

ReadConsole returns TRUE after CancelIoEx #17791

@alabuzhev

Description

@alabuzhev

Windows Terminal version

Latest source

Windows build number

10.0.19045.4780

Other Software

No

Steps to reproduce

  1. Compile the code:
Code
#ifndef UNICODE
#define UNICODE
#endif

#ifndef _UNICODE
#define _UNICODE
#endif

#include <chrono>
#include <iostream>
#include <thread>
#include <string>

#include <windows.h>

using namespace std::literals;

int main()
{
	const auto In = GetStdHandle(STD_INPUT_HANDLE);

	std::thread Thread([&]
	{
		std::this_thread::sleep_for(3s);
		CancelIoEx(In, {});
	});

	std::wcout << L"Do not touch anything for about 3s" << std::endl;

	wchar_t Buffer[1024];
	DWORD NumberOfCharsRead{};
	const auto Result = ReadConsole(In, Buffer, ARRAYSIZE(Buffer), &NumberOfCharsRead, {});
	const auto Error = GetLastError();

	std::wcout << L"Result: " << Result << std::endl;
	std::wcout << L"Error:  " << Error << std::endl;
	std::wcout << L"Chars:  " << NumberOfCharsRead << std::endl;
	std::wcout << L"Data:   " << std::wstring(Buffer, NumberOfCharsRead) << std::endl;

	Thread.join();

	std::wcout << L"Now enter something: " << std::endl;

	if (ReadConsole(In, Buffer, ARRAYSIZE(Buffer), &NumberOfCharsRead, {}))
	{
		std::wcout << L"You have entered: " << std::wstring(Buffer, NumberOfCharsRead) << std::endl;
	}
}
  1. Run it (anywhere: WT, OpenConsole, conhost)
  2. Inspect the output

Expected Behavior

The program reads the console input in a blocking way.
If there is no input, another thread issues CancelIoEx after 3 seconds.

Since the ReadConsole did not read anything, it should return FALSE:

  • It is logical.
  • Even Raymond Chen says so:

    If you had used Read­File instead of fgets, the read would have failed with error code ERROR_OPERATION_ABORTED, as documented by Cancel­Io­Ex.

So the program should print Result: 0.

Actual Behavior

ReadConsole does not read anything, does not update NumberOfCharsRead, but returns TRUE.

It also leaves the input in somewhat inconsistent state, which you can see by typing something after the cancellation: the first input will be discarded. I think this was already mentioned here: #12143 (comment).

The incorrect return value is much worse though: it is a common pattern to leave NumberOfCharsRead uninitialized, because either ReadConsole succeeds and initializes it, or it fails and it makes no sense to look there anyway.
In the code above I initialized it, but if I didn't do so an uninitialized read would've occurred, from both NumberOfCharsRead and Buffer.

Notably the last error is correctly set to 995 - ERROR_OPERATION_ABORTED - "The I/O operation has been aborted because of either a thread exit or an application request.", but who checks the last error on successful calls?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area-ServerDown in the muck of API call servicing, interprocess communication, eventing, etc.Issue-BugIt either shouldn't be doing this or needs an investigation.Priority-1A description (P1)Product-MetaThe product is the management of the products.Tracking-ExternalThis bug isn't resolved, but it's following an external workitem.zInbox-BugIgnore me!

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions