1

I'm trying to read the std output of an external process (pgdump ) that I started with CreateProcess. I got this working with anonymous pipes but then the output is blocked and no like when I execute it via the commandline( missing end output). I have read numerous posts and discovered that I need CreateNamedPipes with WaitForSingleObject but I can't seem to get it to work. This is my working code with anonymous pipe but blocked and I'm missing the end of the output

#include <QDebug>
#include <QString>

#include <windows.h>
#include <sstream>
#include <iostream>
#include <random>

int main()
{   
    #define BUFFERSIZE 256

    std::string program = "\"C:\\Program Files (x86)\\PostgreSQL\\10\\bin\\pg_dump.exe\"" +
            std::string( " --dbname=postgresql://postgresUser:PostGresql13@127.0.0.1:5432/employee -j1 -Fd -b -v -f "
                         "C:\\development\\myproject\\Build\\debug\\1-export.psql");

    HANDLE hReadStdOut = NULL;
    HANDLE hWriteStdOut = NULL;


    SECURITY_ATTRIBUTES saAttr;
    PROCESS_INFORMATION piProcInfo;
    STARTUPINFO siStartInfo;

    ZeroMemory( &saAttr, sizeof( SECURITY_ATTRIBUTES ));
    ZeroMemory( &piProcInfo, sizeof( PROCESS_INFORMATION ));
    ZeroMemory( &siStartInfo, sizeof( STARTUPINFO ));

    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;

    OVERLAPPED sOverlap;


    if( !CreatePipe(&hReadStdOut, &hWriteStdOut, &saAttr, 0) )
    {
        std::ostringstream os;
        os << GetLastError();

        qDebug() << "create pipe error : " << QString::fromStdString( os.str());
    }


    TCHAR* szCmdline = new TCHAR[ program.size() + 1];
    szCmdline[ program.size()] = 0;
    std::copy( program.begin(), program.end(), szCmdline );

    siStartInfo.cb = sizeof(STARTUPINFO);
    siStartInfo.hStdError = hWriteStdOut;
    siStartInfo.hStdOutput = hWriteStdOut;
    siStartInfo.hStdInput = NULL;
    siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

    BOOL bSuccess = CreateProcess( NULL, szCmdline, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &siStartInfo,&piProcInfo );

    if ( ! bSuccess )
    {
        std::ostringstream os;
        os << GetLastError();

        qDebug() << "create process error : " << QString::fromStdString( os.str());
    }
    else
    {
        CloseHandle( hWriteStdOut );

        DWORD err;
        DWORD nBytesRead;
        char buf[BUFFERSIZE + 1];

        int i(1);

        for(;;)
        {
            std::cout << "iteration " << std::to_string( i ) << std::endl;

            if( !ReadFile( hReadStdOut, buf, sizeof( buf), &nBytesRead, NULL) || !nBytesRead )
            {}

            if( GetLastError() == ERROR_SUCCESS )
            {
            }

            std::cout.flush();
            buf[nBytesRead] = '\0';
            std::string string_ = buf;
            std::cout << string_ << std::endl;

            std::size_t found = string_.find("contents of");

            if( !nBytesRead )
                break;

            i++;
        }

        CloseHandle(piProcInfo.hProcess);
        CloseHandle(piProcInfo.hThread);
    }

    return 0;
}
xyfix
  • 61
  • 1
  • 7
  • 4
    `siStartInfo.hStdError = pipe;//hWriteStdOut; siStartInfo.hStdOutput = pipe;//hWriteStdOut;` - there is no `pipe` variable in the code you have shown. As for the blocking issue, you can use `PeekNamedPipe()` with an anonymous pipe to detect how much data is available to read before you then read it. In any case, you say you are having trouble using a named pipe, but there is no named pipe in this code. Have a look at [Overlapped I/O on anonymous pipe](https://stackoverflow.com/questions/60645/) – Remy Lebeau Oct 08 '19 at 21:51
  • @RemyLebeau, yes you're right I forgot to remove some left over code from one of my failed attempts, I didn't post any attempts with the named pipe, because that didn't work so I thought a good start would be with my working code so maybe some one can modify that so I would understand what I'm doing wrong – xyfix Oct 08 '19 at 23:21
  • 1
    exist only one, common, pipe type in windows. are pipe have name or not - has no effect on his behavior. so no any different between named and anonymous (unnamed) pipe. deadlock on synchronous pipes frequently was because all i/o operation on synchronous file is serialized - new not begin, until old not finished. because this usual use 2 different pipe pairs for input and output, for avoid deadlock. but in your case you have no input pipe - unclear where it. asynchronous pipes never have deadlock problem because i/o not serialized. but clients almost never ready for work with asynchronous pipe – RbMm Oct 09 '19 at 00:08
  • 1
    so solution here can be use asynchronous pipe on server side and synchronous end on client. in this case usually we can use single pipe pair (duplex pipes) instead 2. but again - because your client *pg_dump.exe* can only write to pipe - unclear where deadlock or what – RbMm Oct 09 '19 at 00:11
  • @RbMm, can you tell me how to modify my existing code or the one mentioned in my other post? – xyfix Oct 16 '19 at 22:43

1 Answers1

0

For example, if your pg_dump.exe just write one line to standard output. There are several ways:

  1. std::cout << "Hello World!\n";
  2. printf("Hello World!\n");
  3. std::cout << "Hello World!\n"<< std::endl;
  4. WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "Hello World!\n", 14, &dwWritten, NULL);

If the pg_dump.exe exit after write the line, the ReadFile will not blocked and return with the line "Hello World!".

However, if the pg_dump.exe doesn't exit after write the line and continue other work. The first two write ways (1., 2.) will result in ReadFile blocked. But if you use the third or fourth way the ReadFile will block and return with the line "Hello World!".

What's the difference here is the first two write ways (std::cout << "Hello World!\n"; and printf("Hello World!\n");) has no flush operation at end of write but the third or fourth way has. std::endl and WriteFile flush the output buffer.

Summary:

Flush the output to cause it to be written to the underlying stream (which may be a file, a terminal or a pipe). Standard output is flushed at the following conditions:

  1. When the program ends normally.
  2. Use std::endl at the end.
  3. Use WriteFile.

You can check if it is your case.

Rita Han
  • 9,574
  • 1
  • 11
  • 24
  • Han, I have no control of the external program, if that is what you mean. I have tried a flush() as you can see, but that did not make any difference( unless I have placed it incorrectly ). I just don't understand why the output of pg_dump.exe is so different ( in speed and number of lines ) than when I use it with CreateProcess and pipes. – xyfix Oct 10 '19 at 14:12
  • @xyfix You can create a simple console app replacing the pg_dump.exe to see if it can reproduce the same issue. – Rita Han Oct 11 '19 at 02:50
  • I've created a simple console app which writes lines in a loop to std::out and every line ends with std::endl. I've also put a flush() in the loop. But with the current code as stated above I still don't get the lines as I see it when I execute it directly in a console. I think my code above needs some adjustments but I can't seem to figure out what – xyfix Oct 11 '19 at 14:51
  • @xyfix Please show your simple console app and your caller app exactly what you used to test. Because what you have showed are all target pg_dump.exe. And did you get nothing from the `ReadFile`? And does your simple console has the same issue: "I got an error 536 "Waiting for a process to open the other end of the pipe" on the readfile."? – Rita Han Oct 14 '19 at 07:14
  • @Han #include #include #include #include int main() { int i(0); for(;;) { auto seed = std::chrono::system_clock::now().time_since_epoch().count(); std::mt19937 mt( seed ); std::uniform_int_distribution dist(1, 500); std::cout << "ii " << dist(mt) << std::endl; std::chrono::milliseconds msec((int)dist(mt)); std::this_thread::sleep_for(msec); std::cout << " i " << i << " this is a test line with endl " << std::endl; i++; } } – xyfix Oct 15 '19 at 00:32
  • @Han, I also get the error message with my simple code – xyfix Oct 15 '19 at 00:36
  • @xyfix What's your `program` string? – Rita Han Oct 15 '19 at 01:30
  • @Han, The program string for the pg_dump is as stated in my posts, for my own program it's just "out.exe". I'm quite sure that the program string is correct because I always get the postgresql dump file in the end but not the std output during the dump process. – xyfix Oct 15 '19 at 06:40
  • @xyfix Strange. It works for me. What's your Windows version? – Rita Han Oct 15 '19 at 07:59
  • @Han, using the code in my first post with the anonymous pipe gives me output as well but not the desired one( it's blocking but pg_dump runs ) , the code in my second post with the named pipe doesn't give me any output ( but pg_dump runs ). So when you say it works for you is it the named pipe (unblocking ) version? OS Name: Microsoft Windows 10 Pro OS Version: 10.0.16299 N/A Build 16299 – xyfix Oct 15 '19 at 08:14
  • @xyfix I am talking about your own program(out.exe) since you said "I also get the error message with my simple code". – Rita Han Oct 15 '19 at 08:37
  • @Han, yes with my simple program and named pipe code I get that. But I also got it with pg_dump and the named pipe code. So both programs don't work with my named pipe code, but both work with my anonymous blocked pipe code – xyfix Oct 15 '19 at 10:02
  • @xyfix Your simple program and anonymous pipe has blocked issue that you miss the end of the output? – Rita Han Oct 16 '19 at 06:39
  • @Han, as you can see ( maybe not the best choice of me ) the simple program consists of an infinite loop so I limited the loop to see if I'm missing something but it don't – xyfix Oct 16 '19 at 06:54