2

I have a main program that uses boost process library to spawn a child process that prints

Hello World !

on its stdout every 5 seconds.

I would like to read/monitor the stdout of the child process in the main process when it becomes available along with performing other operations within the main program.

I have tried out the examples for boost asynchronous IO (http://www.boost.org/doc/libs/1_66_0/doc/html/boost_process/tutorial.html) but all these seem to block the main program until the child process has exited.

Do we need to read the childs stdout in a separate thread ? Can someone please provide an example where the main program can do other things at the same time instead of blocking for stdout from th child ?

sehe
  • 374,641
  • 47
  • 450
  • 633
sbunny
  • 433
  • 6
  • 25
  • Please show your code. The `bp::child` from the link you provided should do the trick. –  Mar 08 '18 at 20:14
  • The example code blocks (I think) due to the call to [`boost::asio::io_service::run()`](http://www.boost.org/doc/libs/1_64_0/doc/html/boost_asio/reference/io_service/run/overload1.html). Depending on your needs you might want to replace that with ``intermittent'' calls to [`boost::asio::io_service::poll`](http://www.boost.org/doc/libs/1_64_0/doc/html/boost_asio/reference/io_service/poll/overload1.html). However, without seeing your code (or knowing precisely *why* blocking is a problem) it's difficult to comment further. – G.M. Mar 08 '18 at 20:27
  • @G.M. If you use asynchronous call chains, there's no need for interleaving the event loop, in the spirit of asynchronous operations. – sehe Mar 08 '18 at 21:17
  • I'm answering this type of question quite regularly here. I like to provide different ideas in the answers, so compare and pick from: https://stackoverflow.com/questions/tagged/boost-process?sort=newest&pageSize=50 – sehe Mar 08 '18 at 21:21
  • @sehe Point taken. I should've ``brushed up'' on my `boost` before commenting. – G.M. Mar 08 '18 at 21:30

1 Answers1

11

I have tried out the examples for boost asynchronous IO (http://www.boost.org/doc/libs/1_66_0/doc/html/boost_process/tutorial.html) but all these seem to block the main program until the child process has exited.

Look again. All the samples under Asynchronous I/O should help you select a method that works for you.

Do we need to read the childs stdout in a separate thread ? Can someone please provide an example where the main program can do other things at the same time instead of blocking for stdout from th child ?

No, you don't need to. Though you can and depending on what you're trying to achieve, it might be the easiest thing to do.

Synchronous

You failed to tell us what you want to be able to do, so let's assume you just want the output printed:

Live On Coliru:

bp::child c("/bin/bash", std::vector<std::string> { "-c", "for a in {1..10}; do sleep 2; echo 'Hello World !'; done" });
c.wait();

That's synchronous, so you can't do work in the meantime

Using a Reader Thread

That's like:

Live On Coliru:

#include <boost/process.hpp>
#include <boost/process/async.hpp>
#include <iostream>

namespace bp = boost::process;

int main() {
    bp::ipstream output;
    std::thread reader([&output] {
        std::string line;
        while (std::getline(output, line))
            std::cout << "Received: '" << line << "'" << std::endl;
    });

    bp::child c("/bin/bash",
        std::vector<std::string> { "-c", "for a in {1..10}; do sleep 2; echo 'Hello World ('$a')!'; done" },
        bp::std_out > output);

    while (c.running()) {
        std::this_thread::sleep_for(std::chrono::milliseconds(2793));
        std::cout << "(main thread working)" << std::endl;
    }

    std::cout << "(done)" << std::endl;
    c.wait();

    output.pipe().close();
    reader.join();
}

Prints (Live On Coliru):

Received: 'Hello World (1)!'
(main thread working)
Received: 'Hello World (2)!'
(main thread working)
Received: 'Hello World (3)!'
Received: 'Hello World (4)!'
(main thread working)
Received: 'Hello World (5)!'
(main thread working)
Received: 'Hello World (6)!'
(main thread working)
Received: 'Hello World (7)!'
Received: 'Hello World (8)!'
(main thread working)
Received: 'Hello World (9)!'
(main thread working)
Received: 'Hello World (10)!'
(main thread working)
(done)

Asynchronous IO

Using no threads (well, just the main thread), could look like:

Live On Coliru

#include <boost/process.hpp>
#include <boost/process/async.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#include <iostream>
#include <iomanip>

namespace bp = boost::process;

struct OtherWork {
    using clock = std::chrono::high_resolution_clock;

    OtherWork(boost::asio::io_context& io) : timer(io) { }

    void start() {
        timer.expires_at(clock::time_point::max());
        loop();
    }

    void stop() {
        timer.expires_at(clock::time_point::min());
    }

  private:
    void loop() {
        if (timer.expires_at() == clock::time_point::min()) {
            std::cout << "(done)" << std::endl;
            return;
        }

        timer.expires_from_now(std::chrono::milliseconds(2793));
        timer.async_wait([=](boost::system::error_code ec) {
            if (!ec) {
                std::cout << "(other work in progress)" << std::endl;
                start();
            } else {
                std::cout << "(" << ec.message() << ")" << std::endl;
            }
        });
    }

    boost::asio::high_resolution_timer timer;
};

int main() {
    boost::asio::io_context io;
    bp::async_pipe output(io);

    OtherWork mainwork{io};

    bp::child c("/bin/bash", std::vector<std::string> { "-c", "for a in {1..10}; do sleep 2; echo 'Hello World ('$a')!'; done" },
            bp::std_out > output, io, bp::on_exit([&mainwork,&output](auto...) {
                    output.close();
                    mainwork.stop();
                }));

    std::function<void()> readloop = [&,buffer=std::array<char, 32>{}]() mutable {
        output.async_read_some(bp::buffer(buffer), [&](boost::system::error_code ec, size_t transferred) {
                if (transferred) {
                    std::cout << "Received: '";
                    while (transferred && buffer[transferred-1] == '\n') // strip newline(s)
                        --transferred;
                    std::cout.write(buffer.data(), transferred);
                    std::cout << "'" << std::endl;
                }

                if (ec)
                    std::cout << "Output pipe: " << ec.message() << std::endl;
                else
                    readloop();
            });
    };

    mainwork.start();
    readloop();
    io.run();
}

Prints Live On Coliru

Received: 'Hello World (1)!'
(other work in progress)
Received: 'Hello World (2)!'
(other work in progress)
Received: 'Hello World (3)!'
Received: 'Hello World (4)!'
(other work in progress)
Received: 'Hello World (5)!'
(other work in progress)
Received: 'Hello World (6)!'
(other work in progress)
Received: 'Hello World (7)!'
Received: 'Hello World (8)!'
(other work in progress)
Received: 'Hello World (9)!'
(other work in progress)
Received: 'Hello World (10)!'
Output pipe: End of file
Child exited with code=0(Success)
(Operation canceled)
sehe
  • 374,641
  • 47
  • 450
  • 633
  • In the reader thread example, I am unable to get how the reader thread is printing since the stdout of the child process isn't redirected to the ipstream object when spawning the child process. Changing this to `bp::child c("/bin/bash", std::vector { "-c", "for a in {1..10}; do sleep 2; echo 'Hello World ($a)!'; done" }, bp::std_out > output);` returns the below output: `Received: 'Hello World (1)!' (main thread working) Received: 'Hello World (2)!' (main thread working) Received: 'Hello World (3)!' execution expired` – sbunny Mar 09 '18 at 07:07
  • You're right. I had some copy and past errors. Fixed now. (See [live](http://coliru.stacked-crooked.com/a/a76e3298e410e4fe)) "Execution expired" is due to time limiting on Coliru – sehe Mar 09 '18 at 07:44