Skip to content

Properly implement WaitForData for ReadConsoleInput#18228

Merged
lhecker merged 4 commits intomainfrom
dev/lhecker/15859-read-console-input
Dec 3, 2024
Merged

Properly implement WaitForData for ReadConsoleInput#18228
lhecker merged 4 commits intomainfrom
dev/lhecker/15859-read-console-input

Conversation

@lhecker
Copy link
Copy Markdown
Member

@lhecker lhecker commented Nov 21, 2024

There were two bugs:

  • Ever since the conhost v1 -> v2 rewrite the readDataDirect.cpp
    implementation incorrectly passed false as the wait flag.
    The unintentional mistake is obvious in hindsight as the
    check for CONSOLE_STATUS_WAIT makes no sense in this case.
  • The ConPTY integration into InputBuffer was done incorrectly,
    as it would unconditionally wake up the readers/waiters without
    checking if the buffer is now actually non-empty.

Closes #15859

Validation Steps Performed

Test code:

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

int main() {
    HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
    INPUT_RECORD buf[128];
    DWORD read;

    SetConsoleMode(
        in,
        ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT
    );

    for (int i = 0; ReadConsoleInputW(in, buf, 128, &read); ++i) {
        printf("%d read=%lu\n", i, read);
    }

    return 0;
}

Run it under Windows Terminal and type any input. >50% of all
inputs will result in read=0. This is fixed after this PR.

@lhecker lhecker force-pushed the dev/lhecker/15859-read-console-input branch from dab80c8 to 2388624 Compare November 21, 2024 00:43
@microsoft-github-policy-service microsoft-github-policy-service bot added Issue-Bug It either shouldn't be doing this or needs an investigation. Area-Server Down in the muck of API call servicing, interprocess communication, eventing, etc. Priority-2 A description (P2) Product-Conhost For issues in the Console codebase labels Nov 21, 2024

_vtInputShouldSuppress = true;
auto resetVtInputSuppress = wil::scope_exit([&]() { _vtInputShouldSuppress = false; });
const auto wakeup = _wakeupReadersOnExit();
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Much cleaner and safer. Also, it's more correct. We would previously call WakeUpReadersWaitingForData unconditionally but the storage could still be empty. This error has probably been in conhost since v1.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I can't remember 4 years ago why _vtInputShouldSuppress and input wake muffling was added...

Copy link
Copy Markdown
Member Author

@lhecker lhecker Dec 3, 2024

Choose a reason for hiding this comment

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

It was due to the "circular" control flow, as _termInput was set up with the callback (boo!) _HandleTerminalInputCallback. Additionally, the Prepend was implemented with a series of 2 "Append"s. The first one would process the input and call into _termInput which would then invoke _HandleTerminalInputCallback. This meant that the event was flagged and the waiters woken up before the code had finished to append the 2nd half. To fix this, the flag was added to make the callback stop waking the waiters up.

This PR solves the "knot" by moving all of the waking logic out of the internal append-function and into the outermost, public functions. Now they have perfect control over when to wake up something and when to not do it.

// - inEvents - Series of input records to insert into the buffer
// Return Value:
// - <none>
void InputBuffer::_HandleTerminalInputCallback(const TerminalInput::StringType& text)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The second fix. It's also redundant with _writeString, especially with _vtInputShouldSuppress kicked out.

amountToRead,
false,
false,
true,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The first fix.

Comment on lines -200 to -208
if (fIsWaitAllowed)
{
hr = ConsoleWaitQueue::s_CreateWait(m, waiter.release());
if (SUCCEEDED(hr))
{
*pbReplyPending = TRUE;
hr = CONSOLE_STATUS_WAIT;
}
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Makes no sense IMO to check this here if we can just pass down fIsWaitAllowed and avoid a waiter from being created in the first place.

OpenConsole.sln Outdated
Comment on lines +2307 to +2314
{7615F03F-E56D-4DB4-B23D-BD4FB80DB36F}.AuditMode|Any CPU.ActiveCfg = Debug|Win32
{7615F03F-E56D-4DB4-B23D-BD4FB80DB36F}.AuditMode|ARM64.ActiveCfg = Release|ARM64
{7615F03F-E56D-4DB4-B23D-BD4FB80DB36F}.AuditMode|x64.ActiveCfg = Release|x64
{7615F03F-E56D-4DB4-B23D-BD4FB80DB36F}.AuditMode|x86.ActiveCfg = Release|Win32
{7615F03F-E56D-4DB4-B23D-BD4FB80DB36F}.AuditMode|Any CPU.ActiveCfg = AuditMode|x64
{7615F03F-E56D-4DB4-B23D-BD4FB80DB36F}.AuditMode|Any CPU.Build.0 = AuditMode|x64
{7615F03F-E56D-4DB4-B23D-BD4FB80DB36F}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
{7615F03F-E56D-4DB4-B23D-BD4FB80DB36F}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
{7615F03F-E56D-4DB4-B23D-BD4FB80DB36F}.AuditMode|x64.ActiveCfg = AuditMode|x64
{7615F03F-E56D-4DB4-B23D-BD4FB80DB36F}.AuditMode|x64.Build.0 = AuditMode|x64
{7615F03F-E56D-4DB4-B23D-BD4FB80DB36F}.AuditMode|x86.ActiveCfg = AuditMode|Win32
{7615F03F-E56D-4DB4-B23D-BD4FB80DB36F}.AuditMode|x86.Build.0 = AuditMode|Win32
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Double checking) intentional?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Huh, I was certain I reverted this...

INPUT_READ_HANDLE_DATA& readHandleState,
const bool IsUnicode,
const bool IsPeek,
const bool IsWaitAllowed,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: update the function comment above

// Note:
// - The console lock must be held when calling this routine.
// - will throw on failure
void InputBuffer::_WriteBuffer(const std::span<const INPUT_RECORD>& inEvents, _Out_ size_t& eventsWritten, _Out_ bool& setWaitEvent)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: setWaitEvent is still documented above. It should be removed.

@lhecker lhecker merged commit 5c55144 into main Dec 3, 2024
@lhecker lhecker deleted the dev/lhecker/15859-read-console-input branch December 3, 2024 21:12
@github-project-automation github-project-automation bot moved this to To Cherry Pick in 1.22 Servicing Pipeline May 7, 2025
@github-project-automation github-project-automation bot moved this to To Cherry Pick in Inbox Servicing Pipeline May 7, 2025
@DHowett DHowett moved this from To Cherry Pick to Cherry Picked in 1.22 Servicing Pipeline May 7, 2025
DHowett pushed a commit that referenced this pull request May 7, 2025
There were two bugs:
* Ever since the conhost v1 -> v2 rewrite the `readDataDirect.cpp`
  implementation incorrectly passed `false` as the wait flag.
  The unintentional mistake is obvious in hindsight as the
  check for `CONSOLE_STATUS_WAIT` makes no sense in this case.
* The ConPTY integration into `InputBuffer` was done incorrectly,
  as it would unconditionally wake up the readers/waiters without
  checking if the buffer is now actually non-empty.

Closes #15859

## Validation Steps Performed
Test code:
```cpp
#include <Windows.h>
#include <stdio.h>

int main() {
    HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
    INPUT_RECORD buf[128];
    DWORD read;

    SetConsoleMode(
        in,
        ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT
    );

    for (int i = 0; ReadConsoleInputW(in, buf, 128, &read); ++i) {
        printf("%d read=%lu\n", i, read);
    }

    return 0;
}
```
Run it under Windows Terminal and type any input. >50% of all
inputs will result in `read=0`. This is fixed after this PR.

(cherry picked from commit 5c55144)
Service-Card-Id: PVTI_lADOAF3p4s4AmhmQzgaHLrQ
Service-Version: 1.22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-Server Down in the muck of API call servicing, interprocess communication, eventing, etc. Issue-Bug It either shouldn't be doing this or needs an investigation. Priority-2 A description (P2) Product-Conhost For issues in the Console codebase

Projects

Status: Cherry Picked
Status: To Cherry Pick

Development

Successfully merging this pull request may close these issues.

ReadConsoleInput can read 0 characters and return success and it almost certainly shouldn't

3 participants