5

I need a program to communicate with a subprocess that is relying on in- and output. The problem is that I am apparently not able to use QProcess correctly.

The code further down should create a QProcess, start it and enter the main while loop. In there it prints all the output created by the subprocess to the console and subsequently asks the user for input which is then passed to the subprocess via write(...).

Originally I had two problems emerging from this scenario:

  1. The printf's of the subprocess could not be read by the parent process.
  2. scanf in the subprocess is not receiving the strings sent via write.

As for (1), I came to realize that this is a problem caused by the buffering of the subprocess' stdout. This problem can be solved easily with fflush(stdout) calls or manipulations regarding its flushing behavior.

The second problem is the one I can't wrap my head around. write gets called and even returns the correct number of sent bytes. The subprocess, however, is not continuing its excecution, because no new data is written to its output. The scanf seems not to be receiving the data sent. The output given by the program is:

Subprocess should have started.
124 bytes available!
Attempting to read:
Read: This is a simple demo application.
Read: It solely reads stdin and echoes its contents.
Read: Input exit to terminate.
Read: ---------
Awaiting user input: test
Written 5 bytes
No line to be read...
Awaiting user input: 

I am seriously stuck right here. Google + heavy thinking having failed on me, I want to pass this on to you as my last beacon of hope. In case I am just failing to see the forest for all the trees, my apologies.

In case this information is necessary: I am working on 64bit MacOS X using Qt5 and the clang compiler. The subprocess-code is compiled with gcc on the same machine.

Thank you very much in advance,

NR

Main-Code:

int main() {
    // Command to execute the subprocess
    QString program = "./demo";
    QProcess sub;
    sub.start(program, QProcess::Unbuffered | QProcess::ReadWrite);

    // Check, whether the subprocess is starting correctly.
    if (!sub.waitForStarted()) {
        std::cout << "Subprocess could not be started!" << std::endl;
        sub.close();
        return 99;
    }

    std::cout << "Subprocess should have started." << std::endl;

    // Check, if the subprocess has written its starting message to the output.
    if (!sub.waitForReadyRead()) {
        std::cout << "No data available for reading. An error must have occurred." << std::endl;
        sub.close();
        return 99;
    }

    while (1) {
        // Try to read the subprocess' output
        if (!sub.canReadLine()) {
            std::cout << "No line to be read..." << std::endl;
        } else {
            std::cout << sub.bytesAvailable() << " bytes available!" << std::endl;
            std::cout << "Attempting to read..." << std::endl;
            while (sub.canReadLine()) {
                QByteArray output = sub.readLine();
                std::cout << "Read: " << output.data();
            }
        }

        std::cout << "Awaiting user input: ";
        std::string input;
        getline(std::cin, input);

        if (input.compare("exit") == 0) break;

        qint64 a = sub.write(input.c_str());
        qint64 b = sub.write("\n");
        sub.waitForBytesWritten();
        std::cout << "Written " << a + b << " bytes" << std::endl;
    }

    std::cout << "Terminating..." << std::endl;
    sub.close();
}

Subprocess-Code:

int main() {
    printf("This is a simple demo application.\n");
    printf("It reads stdin and echoes its contents.\n");
    printf("Input \"exit\" to terminate.\n");

    while (1) {
        char str[256];
        printf("Input: ");
        fflush(stdout);
        scanf("%s", str);

        if (strcmp(str, "exit") == 0) return 0;

        printf("> %s\n", str);
    }
}

P.s: Since this is my first question on SO, please tell me if something is wrong concerning the asking style.


Solution

After many many more trials & errors, I managed to come up with a solution to the problem. Adding a call to waitForReadyRead() causes the main process to wait until new output is written by the subprocess. The working code is:

...
sub.waitForBytesWritten();
std::cout << "Written " << a + b << " bytes" << std::endl;
// Wait for new output
sub.waitForReadyRead();
...

I still don't have a clue why it works this way. I guess it somehow relates to the blocking of the main process by getline() vs blocking by waitForReadyRead(). To me it appears as if getline() blocks everything, including the subprocess, causing the scanf call never to be processed due to race conditions.

It would be great, if someone who understands could drop an explanation.

Thank you for your help :)

NR

NRiesterer
  • 95
  • 1
  • 10
  • Have you tried `sub.waitForBytesWritten()` after `sub.write("\n")`? EDIT: http://stackoverflow.com/questions/4949548/qprocess-read-and-write?rq=1 – Iwillnotexist Idonotexist Mar 21 '14 at 22:06
  • I just tried, but it did not change the result. The subprocess is still refusing to produce new output. – NRiesterer Mar 21 '14 at 22:26
  • 1
    Come to think of it, isn't this the expected result so far? You wrote out the data, _did_ sent it to the subprocess and the subprocess _might even have received it and processed it_, but I would expect that the parent process would be much quicker in getting to the `if (!sub.canReadLine()) {` check. If so, then before your subprocess has the time to react, your parent process is back again to waiting for _your_ input, not the subprocess'. Can you write more input and see whether the subprocess has output something by then? – Iwillnotexist Idonotexist Mar 21 '14 at 22:39
  • `sub.canReadLine()` is always false, no matter how much input is written. Calling `sub.bytesAvailable()` always returns the same number as well. – NRiesterer Mar 21 '14 at 23:02
  • 1
    `bool QIODevice::canReadLine () const [virtual]` Returns true if a complete line of data can be read from the device; otherwise returns false. Note that unbuffered devices, which have no way of determining what can be read, always return false. http://qt-project.org/doc/qt-4.8/qiodevice.html#canReadLine And it so happens you declared your `QProcess` as `QProcess::Unbuffered`... – Iwillnotexist Idonotexist Mar 21 '14 at 23:07
  • True. Interesting that the initial prints of the subprocess are still causing the method to return true though. – NRiesterer Mar 21 '14 at 23:12
  • I've also noticed: It's impossible for the subprocess to get served "exit", since `if (input.compare("exit") == 0) break;` comes before the `write()`'s. Your subprocess thus has no way to exit, and if it pleased to do so we will know for sure whether it did in fact ever receive anything at all, because everything's flushed on exit. – Iwillnotexist Idonotexist Mar 21 '14 at 23:35
  • This is true as well. I wrote this program as a demo to solve the write problem (calling `sub.close()` kills it nevertheless). Removing `QProcess::Unbuffered` does not solve it unfortunately. As far as I understand it either the message is not delivered at all or simply not in the right way for the scanf to pick it up. If it was transmitted successfully, there would have to be data ready to be read (sooner or later depending on the delay between the threads), which is definitely not the case. – NRiesterer Mar 21 '14 at 23:46
  • I despair of thinking up another reason. Your code, other than the issues pointed out, should be working. Can you make it any smaller while still demonstrating this bug? Alternately, could you try rewriting it with separate QThreads handling reads/writes from/to the process and QSemaphores for synchronization with the main thread? – Iwillnotexist Idonotexist Mar 22 '14 at 00:05
  • I wrote another version of the program doing the same, but relying on slots and signals. The problem was still present. As stated in the edit of the question, it appears as if `getline()` blocks everything. This results in the subprocess never being able to finish `scanf` due to the rapid execution of the main process' loop. – NRiesterer Mar 22 '14 at 12:36

1 Answers1

3

This will not work. You are waiting for the sent bytes to be written but you are not waiting for the echo. Instead you are entering the getline() function waiting for new user input. Keep in mind that two processes are involved here where each process can be delayed to any degree.

Apart from this you should consider building your Qt application asynchronously (having an event loop) instead of trying the synchronous approach. This way your Qt application can do things in parallel... e.g. reading input or waiting for input from the remote process while still not being blocked and able to accept user input.

Silicomancer
  • 8,604
  • 10
  • 63
  • 130
  • I guess you explained it better than I did in my comment. Maybe a blurb about race conditions. – Iwillnotexist Idonotexist Mar 21 '14 at 22:54
  • So the QProcess is running synchronously with the main process, meaning that getline() effectively blocks the subprocess? – NRiesterer Mar 21 '14 at 23:06
  • 1
    No. You are entering getline() before the other process could send the echo. And while waiting in getline() (which is a blocking function call!) you can not read any delayed echo from the other process. – Silicomancer Mar 21 '14 at 23:10
  • Alright. So the main program is stuck at the getline while the subprocess is printing the echo of the last write. The main process therefore does not show the echo immediately. Now I provide more input, causing the loop to restart. Shouldn't the last print of the subprocess be available now? Because this is not happening. It does not matter how many times I provide input or how long I wait, there is simply nothing happening. – NRiesterer Mar 21 '14 at 23:16
  • Edited the question with the answer. It seems that race conditions really were the culprit. Even though I don't know why, it works now. So thank you guys very much :) – NRiesterer Mar 22 '14 at 12:32