c++ - 从管道读取随机失败

标签 c++ windows

我正在为命令行可执行文件编写集成测试驱动程序。我控制驱动程序和可执行文件,因此我可以保证它们的行为 - 例如,可执行文件永远不会从标准输入读取,它只接受命令行参数,执行其操作,然后将输出写入文件和标准输出。

我希望捕获进程的退出代码和标准输出以进行验证。

这是我正在使用的代码:

#include <Windows.h>

class Pipe {
    HANDLE ReadHandle;
    HANDLE writehandle;
public:
    Pipe() {
        SECURITY_ATTRIBUTES saAttr;
        saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
        saAttr.bInheritHandle = TRUE;
        saAttr.lpSecurityDescriptor = NULL;
        CreatePipe(&ReadHandle, &writehandle, &saAttr, 0);
    }
    HANDLE WriteHandle() {
        return writehandle;
    }
    std::string Contents() {
        CloseHandle(writehandle);
        DWORD dwRead;
        CHAR chBuf[1024];
        BOOL bSuccess = FALSE;

        std::string result;
        for (;;)
        {
            bSuccess = ReadFile(ReadHandle, chBuf, 1024, &dwRead, NULL);
            if (!bSuccess) break;
            result += std::string(chBuf, chBuf + dwRead);
            if (dwRead < 1024)
                break;
        }
        return result;
    }
    ~Pipe() {
        CloseHandle(ReadHandle);
    }
};
Wide::Driver::ProcessResult Wide::Driver::StartAndWaitForProcess(std::string name, std::vector<std::string> args, Util::optional<unsigned> timeout)
{
    ProcessResult result;
    Pipe stdoutpipe;
    PROCESS_INFORMATION info = { 0 };
    STARTUPINFO startinfo = { sizeof(STARTUPINFO) };
    std::string final_args = name;
    for (auto arg : args)
         final_args += " " + arg;
    startinfo.hStdOutput = stdoutpipe.WriteHandle();
    startinfo.hStdError = INVALID_HANDLE_VALUE;
    startinfo.hStdInput = INVALID_HANDLE_VALUE;
    startinfo.dwFlags |= STARTF_USESTDHANDLES;
    auto proc = CreateProcess(
        name.c_str(),
        &final_args[0],
        nullptr,
        nullptr,
        TRUE,
        NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,
        nullptr,
        nullptr,
        &startinfo,
        &info
         );
    if (!proc) {
        DWORD dw = GetLastError();
        const char* message;
        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            nullptr, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&message, 0, nullptr);
        std::string err = message;
        LocalFree((void*)message);
        throw std::runtime_error(err);        
    }
    if (timeout == 0)
        timeout = INFINITE;

    result.std_out = stdoutpipe.Contents();
    if (WaitForSingleObject(info.hProcess, timeout ? *timeout : INFINITE) == WAIT_TIMEOUT)
         TerminateProcess(info.hProcess, 1);

    DWORD exit_code;
    GetExitCodeProcess(info.hProcess, &exit_code);
    CloseHandle(info.hProcess);
    CloseHandle(info.hThread);
    result.exitcode = exit_code;
    if (exit_code != 0)
        return result;
    return result;
}

我以这种方式运行了 259 个集成测试。有些比其他需要更长的时间。当我运行该套件时,大约 1-3 个会失败 - 每次都有不同的失败。我查看了调试器中的结果,标准输出在中途被切断。如果我不 try catch 标准输出,则所有测试每次都会成功,因此我知道它是基于标准输出捕获的。

指定了超时,但它是一个非常慷慨的 60 秒 - 比正常运行测试所需的时间长得多。我为每个测试生成一个新进程。

如何以更可靠的方式捕获标准输出,而不会出现随机故障?

最后一点,运行套件需要很长时间才能捕获调试器中的故障,因此可能需要一段时间才能处理任何进一步信息的请求。

最佳答案

我对此有一个理论,但我并不完全确定。关键在于读取进程的标准输出的循环条件。

std::string result;
for (;;)
{
    bSuccess = ReadFile(ReadHandle, chBuf, 1024, &dwRead, NULL);
    if (!bSuccess) break;
    result += std::string(chBuf, chBuf + dwRead);
    if (dwRead < 1024)
        break;
}
return result;

这里实际上有一个隐含的假设。 ReadFile 是一个阻塞 API,因此我们假设它会一直阻塞,直到获得我们请求的数据或输入结束。但我假设事实上,ReadFile 可能会在它具有我们要求的大小的 block 之前返回,即使管道尚未终止。这将导致输入读取循环终止。

由于父进程不再读取标准输出,尝试写入标准输出的子进程可能会阻塞等待某人清除缓冲区——实际上是一个死锁,因为没有人会这样做。因此,超时会触发并终止进程,记录失败。

MSDN 文档是这样说的:

The ReadFile function returns when one of the following conditions occur:

    The number of bytes requested is read.
    A write operation completes on the write end of the pipe.
    An asynchronous handle is being used and the read is occurring asynchronously.
    An error occurs.

没有说当写入操作完成并且请求的字节数可用时它将返回。事实上,它没有对写入操作做出任何评论,从而使您请求的字节数可用。因此,即使同步调用,它也能以半异步方式有效地运行。

我将循环重写如下:

std::string result;
for (;;)
{
    bSuccess = ReadFile(ReadHandle, chBuf, 1024, &dwRead, NULL);
    if (!bSuccess || dwRead == 0) break;
    result += std::string(chBuf, chBuf + dwRead);
}
return result;

到目前为止,我无法通过此循环重现故障(并且测试完成速度明显更快)。

关于c++ - 从管道读取随机失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34966180/

相关文章:

windows - 为什么有些网络 API 能够接受远程连接,而有些则不能?

C++,什么时候应该返回引用?

c++ - 如何通过 #define 函数向格式化字符串函数调用添加参数

c# - 将 DateTime.Now 转换为有效的 Windows 文件名

Java 不会在应用程序关闭时释放所有资源

windows - Windows 上的 Git 在原点大写文件名,在本地小写

windows - 如何更改 Outlook 2010 中的 "modified date"

c++ - 如何解决这个图像处理问题——分类

c++ - 排除 OpenGL 中两个圆的重叠区域中的点

C++ 可变参数宏和模板