What can cause that infinite loop inside ReadFile() and how to fix it?
ProcessPtr process = addProcess("cmd", true);
process->read(true);
process->write("echo Hello\n", true);
process->read(true);
process->write("exit\n", true);
When running cmd:
┌----- The 1st call of ReadFile() succeeded.
v
Microsoft Windows [Version 10.0.18363.592]
(c) 2019 Microsoft Corporation. All rights reserved.
<----- The 2nd call of ReadFile() gets stuck in an infinite loop inside ReadFile().
>echo Hello
Hello
>exit
Code for Reproducing the Exact Same Behavior
Add a breakpoint on the line with ReadFile()
. There is no infinite loop during the first call of it, but an infinite loop occurs during the second call of it.
#include "windows.h"
class _Process; typedef shared_ptr<_Process> ProcessPtr;
class _Process {
friend ProcessPtr addProcess(const char *cmd, bool usePipes_and_noWait);
private:
HANDLE _hStdInPipe_wr = nullptr;
HANDLE _hStdOutPipe_rd = nullptr;
HANDLE _hProcess = nullptr;
HANDLE _hThread = nullptr;
public:
_Process();
~_Process();
void write(const char *data, bool debugOutput);
void read(bool debugOutput);
};
_Process::_Process() {
}
ProcessPtr addProcess(const char *cmd, bool usePipes_and_noWait) {
ProcessPtr process = ProcessPtr(new _Process());
#if defined(__linux__) || defined(__ANDROID__)
// TODO
#elif defined(_WIN32)
// [How to read output from cmd.exe using CreateProcess() and CreatePipe()](https://stackoverflow.com/questions/35969730/how-to-read-output-from-cmd-exe-using-createprocess-and-createpipe)
HANDLE hStdInPipe_rd = nullptr;
HANDLE hStdInPipe_wr = nullptr;
HANDLE hStdOutPipe_rd = nullptr;
HANDLE hStdOutPipe_wr = nullptr;
if (usePipes_and_noWait) {
// Create two pipes
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), nullptr, true };
if (!CreatePipe(&hStdInPipe_rd, &hStdInPipe_wr, &sa, 0)) {
throw string("CreatePipe() failed."); return nullptr;
}
if (!CreatePipe(&hStdOutPipe_rd, &hStdOutPipe_wr, &sa, 0)) {
throw string("CreatePipe() failed."); return nullptr;
}
}
// https://learn.microsoft.com/it-it/windows/win32/procthread/creating-processes
{
STARTUPINFO si = {};
si.cb = sizeof(si);
if (usePipes_and_noWait) {
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = hStdInPipe_rd;
si.hStdOutput = hStdOutPipe_wr;
si.hStdError = hStdOutPipe_wr;
}
PROCESS_INFORMATION pi = {};
if (!CreateProcess(
nullptr,
const_cast<LPSTR>(cmd), // Command line (https://stackoverflow.com/questions/14682480/converting-from-const-char-to-lptstr-without-uses-converstion)
nullptr,
nullptr,
usePipes_and_noWait ? true : false,
0,
nullptr,
nullptr,
&si,
&pi
)) {
throw string("Failed to create process."); return nullptr;
}
if (usePipes_and_noWait) {
// Close pipes we do not need
CloseHandle(hStdOutPipe_wr); hStdOutPipe_wr = nullptr;
CloseHandle(hStdInPipe_rd); hStdInPipe_rd = nullptr;
}
process->_hStdInPipe_wr = hStdInPipe_wr;
process->_hStdOutPipe_rd = hStdOutPipe_rd;
process->_hProcess = pi.hProcess;
process->_hThread = pi.hThread;
if (usePipes_and_noWait == false) {
// Wait until child process exits
WaitForSingleObject(pi.hProcess, INFINITE); // It can cause an infinite loop (https://stackoverflow.com/questions/28459471/createprocess-not-in-primary-thread).
}
}
#endif
return process;
}
_Process::~_Process() {
#if defined(__linux__) || defined(__ANDROID__)
// TODO
#elif defined(_WIN32)
CloseHandle(_hStdInPipe_wr); _hStdInPipe_wr = nullptr;
CloseHandle(_hStdOutPipe_rd); _hStdOutPipe_rd = nullptr;
CloseHandle(_hProcess); _hProcess = nullptr;
CloseHandle(_hThread); _hThread = nullptr;
#endif
}
void _Process::write(const char *data, bool debugOutput) {
size_t dataSize = strlen(data) + 1; // Include the null-terminator.
#if defined(__linux__) || defined(__ANDROID__)
EMC_ERROR("TODO");
#elif defined(_WIN32)
DWORD bytesWritten;
WriteFile(_hStdInPipe_wr, data, static_cast<DWORD>(dataSize), &bytesWritten, nullptr);
if (debugOutput) {
if (bytesWritten) {
setConsoleTextColor(ConsoleTextColor_Yellow);
} else {
setConsoleTextColor(ConsoleTextColor_Red);
}
printf("%s", data);
setConsoleTextColor(ConsoleTextColor_Reset);
}
#endif
}
void _Process::read(bool debugOutput) {
#if defined(__linux__) || defined(__ANDROID__)
EMC_ERROR("TODO");
#elif defined(_WIN32)
// https://stackoverflow.com/questions/35969730/how-to-read-output-from-cmd-exe-using-createprocess-and-createpipe
DWORD bufSize = 1024;
std::vector<char> buf;
buf.resize(bufSize + 1);
DWORD numBytesRead = 0;
while (true) {
bool ok = ReadFile(_hStdOutPipe_rd, &buf[0], bufSize, &numBytesRead, nullptr);
if (!ok || numBytesRead == 0) {
// Error or no more data to read, exit the loop.
break;
}
buf[numBytesRead] = '\0';
if (debugOutput) {
setConsoleTextColor(ConsoleTextColor_Green);
printf("%s", &buf[0]);
setConsoleTextColor(ConsoleTextColor_Reset);
}
}
#endif
}
void main() {
ProcessPtr process = addProcess("cmd", true);
process->read(true);
process->write("echo Hello\n", true);
process->read(true);
process->write("exit\n", true);
}