Skip to content

Use NtQueryVolumeInformationFile instead of GetConsoleMode to check whether a handle is a console - #6483

Open
Yexuan Xiao (YexuanXiao) wants to merge 11 commits into
microsoft:mainfrom
YexuanXiao:print-getfiletype
Open

Yexuan Xiao (YexuanXiao) wants to merge 11 commits into
microsoft:mainfrom
YexuanXiao:print-getfiletype

Conversation

@YexuanXiao

@YexuanXiao Yexuan Xiao (YexuanXiao) commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Earlier this year I looked into the Windows console in depth, and since it's open source, I had the opportunity to analyze how it's implemented.

Put simply, when program call GetConsoleMode (as well as any other console API, and when reading from or writing to the console handle), the following happens:

  1. The app communicates with condrv.sys
  2. condrv.sys communicates with conhost.exe (or openconsole.exe, which is the distributed version of conhost.exe) through a pipe-like file, where condrv.sys produces the following messages:
    https://github.com/microsoft/terminal/blob/6676c6f938f7e98ad680f0cecedd3bafb6529734/dep/Console/condrv.h#L33-L40
  3. conhost.exe processes these messages and write back

NtQueryVolumeInformationFile is not a console API; it merely queries the device (condrv.sys) info provided when the fs driver is registered, so it is much faster than GetConsoleMode.
This usage of NtQueryVolumeInformationFile has been supported since Windows 8, see rust-lang/rust#164170.

[print.fun] 10.2 says that on Windows, GetConsoleMode is used to determine whether a handle refers to a console, so an LWG issue may be needed. However, since there is no observable difference, this patch does not need to wait for standards.

I just ran a simple test, and NtQueryVolumeInformationFile is 100 times faster than GetConsoleMode.

test program:

#include <iostream>
#include <chrono>

#include <windows.h>
#include <winternl.h>
#include <winioctl.h>

#pragma comment(lib, "ntdll.lib")

extern "C" {

    // Exported by ntdll.lib and can most efficiently query whether a HANDLE references a console.
    // Since the user mode header isn't provided, all needed declarations are declared manually.
    NTSYSAPI NTSTATUS NTAPI NtQueryVolumeInformationFile(
        HANDLE handle, PIO_STATUS_BLOCK ioStatus, PVOID information, ULONG length, ULONG informationClass);

} // extern "C"

// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_file_fs_device_information
typedef struct _FILE_FS_DEVICE_INFORMATION {
    DEVICE_TYPE DeviceType;
    ULONG Characteristics;
} FILE_FS_DEVICE_INFORMATION, * PFILE_FS_DEVICE_INFORMATION;

// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ne-wdm-_fsinfoclass
#define FileFsDeviceInformation 4

bool _Is_console_handle(HANDLE handle) {
    IO_STATUS_BLOCK iosb;
    FILE_FS_DEVICE_INFORMATION info;
    const auto status = NtQueryVolumeInformationFile(handle, &iosb, &info, sizeof(info), FileFsDeviceInformation);
    return (status >= 0) & (info.DeviceType == FILE_DEVICE_CONSOLE);
}

int main() {
    constexpr int N = 1000000;
    HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD mode;

    auto t0 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < N; ++i)
        (void)_Is_console_handle(h);
    auto t1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < N; ++i)
        (void)GetConsoleMode(h, &mode);
    auto t2 = std::chrono::high_resolution_clock::now();

    auto ftNs = std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0).count() / N;
    auto cmNs = std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1).count() / N;

    std::cout << ftNs << " ns and " << cmNs << " ns\n";
}

I wrote a benchmark for std::print, but the results fluctuate a lot.

@YexuanXiao
Yexuan Xiao (YexuanXiao) requested a review from a team as a code owner October 9, 2026 16:17
Copilot AI balanced review requested due to automatic review settings October 9, 2026 16:17
@azure-pipelines

This comment was marked as resolved.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

FILE_TYPE_CHAR also matches non-console devices such as NUL, causing incorrect WriteConsoleW calls.

1 open finding
What changed in this PR

Optimizes Windows console detection by using GetFileType.

Changes:

  • Replaces GetConsoleMode with a file-type check.
File Description
stl/​src/​print.cpp Changes console-handle detection logic.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread stl/src/print.cpp Resolved
@YexuanXiao Yexuan Xiao (YexuanXiao) changed the title Use GetFileType instead of GetConsoleMode to check whether a handle is a console Use NtQueryVolumeInformationFile instead of GetConsoleMode to check whether a handle is a console Oct 9, 2026
@YexuanXiao
Yexuan Xiao (YexuanXiao) marked this pull request as ready for review October 9, 2026 23:53
Copilot AI balanced review requested due to automatic review settings October 9, 2026 23:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Failed native queries can read uninitialized output, and the critical NUL fallback behavior lacks regression coverage.

2 open findings
1 resolved since last review

🧠 Review effort: Balanced

Comment thread stl/src/print.cpp Resolved
Comment thread stl/src/print.cpp
// return value of GetConsoleMode().
DWORD _Console_mode;
const bool _Is_unicode_console = GetConsoleMode(_Console_handle, &_Console_mode) != 0;
const bool _Is_unicode_console = _Is_console_handle(_Console_handle);
Copilot AI balanced review requested due to automatic review settings October 10, 2026 00:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Pending native I/O can cause an uninitialized read, and the key NUL regression scenario remains untested.

1 open finding
2 resolved since last review
Previously missed (2)

In code that hasn't changed since last review

Medium severity Avoid treating STATUS_PENDING as successful handle query

stl/​src/​print.cpp:43

NT_SUCCESS-style checking also accepts STATUS_PENDING (a positive status). For an asynchronously opened handle, the information buffer is not valid until the I/O completes, so this can read uninitialized info and potentially classify the handle from garbage. Since console handles complete this query synchronously, require immediate success here (or explicitly wait and inspect iosb.Status).

Low severity Document redirecting benchmark payload away from report output

benchmarks/​src/​print.cpp:14

This benchmark emits its payload to stdout, so a normal invocation mixes a very large amount of Lorem Ipsum with the benchmark report and makes the results difficult to retrieve. The existing stdout benchmark documents the required output redirection at benchmarks/src/efficient_nonlocking_print.cpp:4-5; add equivalent invocation guidance here or separate the reporter output programmatically.

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 17, 2026 03:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A missing typedef alias prevents print.cpp from compiling.

2 open issues
1 resolved since last review

Review effort: Balanced

Comment thread stl/src/print.cpp Resolved
Copilot AI balanced review requested due to automatic review settings October 17, 2026 03:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The native Windows API change warrants human validation of runtime compatibility before approval.

1 open issue
1 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 17, 2026 04:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The Windows runtime behavior change warrants human validation of console detection and redirected-output compatibility.

1 open issue

Review effort: Balanced

Comment thread stl/src/print.cpp Resolved
Comment thread stl/src/print.cpp Resolved
Copilot AI balanced review requested due to automatic review settings October 21, 2026 11:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Windows handle compatibility and the previously requested regression coverage still need human verification.

1 open issue

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 22, 2026 03:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new helper uses an undefined NT_SUCCESS macro, preventing print.cpp from compiling.

2 open issues

Review effort: Balanced

Comment thread stl/src/print.cpp Resolved
@BillyONeal

Copy link
Copy Markdown
Member

Whoops, sorry for spam. I still haven't internalized GitHub's new layout where new updates are on top :)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Build compatibility of the status check and regression coverage for redirected non-console output remain unresolved.

1 open issue
1 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 30, 2026 01:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The undefined NT_SUCCESS macro prevents print.cpp from compiling, and the new device classifier lacks regression coverage for /utf-8 output to NUL.

1 open issue

Review effort: Balanced

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Must go faster

Projects

Status: Initial Review

Development

Successfully merging this pull request may close these issues.

6 participants