Properly implement WaitForData for ReadConsoleInput#18228
Conversation
dab80c8 to
2388624
Compare
|
|
||
| _vtInputShouldSuppress = true; | ||
| auto resetVtInputSuppress = wil::scope_exit([&]() { _vtInputShouldSuppress = false; }); | ||
| const auto wakeup = _wakeupReadersOnExit(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I can't remember 4 years ago why _vtInputShouldSuppress and input wake muffling was added...
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
The second fix. It's also redundant with _writeString, especially with _vtInputShouldSuppress kicked out.
| amountToRead, | ||
| false, | ||
| false, | ||
| true, |
| if (fIsWaitAllowed) | ||
| { | ||
| hr = ConsoleWaitQueue::s_CreateWait(m, waiter.release()); | ||
| if (SUCCEEDED(hr)) | ||
| { | ||
| *pbReplyPending = TRUE; | ||
| hr = CONSOLE_STATUS_WAIT; | ||
| } | ||
| } |
There was a problem hiding this comment.
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
| {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 |
There was a problem hiding this comment.
(Double checking) intentional?
There was a problem hiding this comment.
Huh, I was certain I reverted this...
| INPUT_READ_HANDLE_DATA& readHandleState, | ||
| const bool IsUnicode, | ||
| const bool IsPeek, | ||
| const bool IsWaitAllowed, |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
nit: setWaitEvent is still documented above. It should be removed.
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
There were two bugs:
readDataDirect.cppimplementation incorrectly passed
falseas the wait flag.The unintentional mistake is obvious in hindsight as the
check for
CONSOLE_STATUS_WAITmakes no sense in this case.InputBufferwas 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:
Run it under Windows Terminal and type any input. >50% of all
inputs will result in
read=0. This is fixed after this PR.