IO (Windows): fixes blocking forever on incomplete terminal responses

Same issue as on Unix: ffGetTerminalResponse() waits for input only before
the first ReadFile(). If the terminal answers only part of the request
(e.g. one of the two OSC 10/11 replies), the next ReadFile() blocks forever.

Wait with the same timeout before every subsequent ReadFile() as well, and
restore the console mode on that error path. While waiting, also skip
key-up records: ReadFile() returns no data for them, so one left alone in
the buffer would end the wait and the following ReadFile() would block.
This commit is contained in:
ihsandeniz
2026-10-26 17:04:39 +03:00
committed by Carter Li
parent 408646df1c
commit 2f68ce7f74
+43 -23
View File
@@ -414,6 +414,35 @@ void ffListFilesRecursively(const char* path, bool pretty) {
listFilesRecursively(folder.length, &folder, 0, nullptr, pretty); listFilesRecursively(folder.length, &folder, 0, nullptr, pretty);
} }
static const char* waitForTerminalResponse(HANDLE hInput) {
while (true) {
NTSTATUS status = NtWaitForSingleObject(hInput, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) FF_IO_TERM_RESP_WAIT_MS * -10000 });
if (status != STATUS_WAIT_0) {
FF_DEBUG("NtWaitForSingleObject() failed or timeout: %s", ffDebugNtStatus(status));
return "NtWaitForSingleObject() failed or timeout";
}
// Ignore all unexpected input events
INPUT_RECORD record;
DWORD len = 0;
if (!PeekConsoleInputW(hInput, &record, 1, &len)) {
return nullptr;
}
// Key-up events carry no input for `ReadFile`; leaving one in the buffer would make the
// wait return while the next `ReadFile` still blocks
if (
record.EventType == KEY_EVENT &&
record.Event.KeyEvent.bKeyDown &&
record.Event.KeyEvent.uChar.UnicodeChar != L'\r' &&
record.Event.KeyEvent.uChar.UnicodeChar != L'\n') {
return nullptr;
}
ReadConsoleInputW(hInput, &record, 1, &len);
}
}
const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...) { const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...) {
HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
FF_AUTO_CLOSE_FD HANDLE hConin = INVALID_HANDLE_VALUE; FF_AUTO_CLOSE_FD HANDLE hConin = INVALID_HANDLE_VALUE;
@@ -440,29 +469,10 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
WriteFile(hOutput, request, (DWORD) strlen(request), &bytes, nullptr); WriteFile(hOutput, request, (DWORD) strlen(request), &bytes, nullptr);
} }
while (true) { const char* error = waitForTerminalResponse(hInput);
NTSTATUS status = NtWaitForSingleObject(hInput, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) FF_IO_TERM_RESP_WAIT_MS * -10000 }); if (error) {
if (status != STATUS_WAIT_0) { SetConsoleMode(hInput, inputMode);
FF_DEBUG("NtWaitForSingleObject() failed or timeout: %s", ffDebugNtStatus(status)); return error;
SetConsoleMode(hInput, inputMode);
return "NtWaitForSingleObject() failed or timeout";
}
// Ignore all unexpected input events
INPUT_RECORD record;
DWORD len = 0;
if (!PeekConsoleInputW(hInput, &record, 1, &len)) {
break;
}
if (
record.EventType == KEY_EVENT &&
record.Event.KeyEvent.uChar.UnicodeChar != L'\r' &&
record.Event.KeyEvent.uChar.UnicodeChar != L'\n') {
break;
} else {
ReadConsoleInputW(hInput, &record, 1, &len);
}
} }
va_list args; va_list args;
@@ -472,6 +482,16 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
uint32_t bytesRead = 0; uint32_t bytesRead = 0;
while (true) { while (true) {
// The response may arrive in several chunks; a terminal that sends an incomplete
// response would otherwise block `ReadFile` forever
if (bytesRead > 0 && (error = waitForTerminalResponse(hInput))) {
va_end(args);
if (hasInputMode) {
SetConsoleMode(hInput, inputMode);
}
return error;
}
DWORD bytes = 0; DWORD bytes = 0;
BOOL readOk = ReadFile(hInput, buffer + bytesRead, (DWORD) (sizeof(buffer) - 1 - bytesRead), &bytes, nullptr); BOOL readOk = ReadFile(hInput, buffer + bytesRead, (DWORD) (sizeof(buffer) - 1 - bytesRead), &bytes, nullptr);
if (!readOk || bytes == 0) { if (!readOk || bytes == 0) {