1

I have tried to use boost::childprocess with an async_pipe as shown in the code example below, while expecting since there is a wait method, that the call to run would not wait for the called executable to finish before continuing to the line where I call wait(). My aim is namely to start the same executable multiple times in order to test in GTest an instance counting method (implemented based on boost managed shared memory segment). But here fore I need the call to io_service::run(), to not wait for the called executable to finish as it does right now. Can someone tell me where I am using it wrong please? Or if this is the wrong way to unit test my function? I have been trying to find the solution for quite some time! Here is a sample of how I call one instance of the executable:

int CallChildProcess_Style9() {

std::string strCmdLine = "E:\\file.exe --Debug MainStartUps_Off --Lock 3";
boost::asio::io_service m_oIOS;
std::vector<char>       m_oAsyncBuffer_Out;
bp::async_pipe          m_oAsyncPipe_Out(m_oIOS);
std::error_code         build_ec;
size_t                  nReadSize(0);
boost::scoped_ptr<boost::process::child>  m_pChildProcess(nullptr);

m_pChildProcess.reset(new bp::child(strCmdLine.data(), bp::std_out > m_oAsyncPipe_Out, build_ec));

m_oAsyncBuffer_Out.resize(1024*8);

boost::asio::async_read(m_oAsyncPipe_Out, boost::asio::buffer(m_oAsyncBuffer_Out),
    [&](const boost::system::error_code &ec, std::size_t size) { nReadSize = size; });

size_t iii = m_oIOS.run();

m_pChildProcess->wait();
m_oAsyncBuffer_Out.resize(nReadSize);

std::string strBuf(m_oAsyncBuffer_Out.begin(), m_oAsyncBuffer_Out.begin() + nReadSize);

int         result = m_pChildProcess->exit_code();

m_oAsyncPipe_Out.close();

m_oIOS.reset();

return result;

}

Igor R.
  • 14,716
  • 2
  • 49
  • 83
Chrysmac
  • 28
  • 5

1 Answers1

0

Using io_service

To be using async_pipe, you need to supply the io_service instance to the parameter keywords of bp::child:

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

namespace bp = boost::process;

int CallChildProcess_Style9() {

    std::string strCmdLine = "/bin/cat";
    boost::asio::io_service m_oIOS;
    std::vector<char> m_oAsyncBuffer_Out;
    bp::async_pipe m_oAsyncPipe_Out(m_oIOS);
    std::error_code build_ec;

    size_t nReadSize(0);
    boost::scoped_ptr<boost::process::child> m_pChildProcess(nullptr);

    std::vector<std::string> const args = { "/home/sehe/Projects/stackoverflow/test.cpp" };
    m_pChildProcess.reset(new bp::child(strCmdLine, args, bp::std_out > m_oAsyncPipe_Out, build_ec, m_oIOS));

    std::cout << "Launched: " << build_ec.message() << std::endl;

    m_oAsyncBuffer_Out.resize(1024 * 8);

    boost::asio::async_read(m_oAsyncPipe_Out, boost::asio::buffer(m_oAsyncBuffer_Out),
        [&](const boost::system::error_code &ec, std::size_t size) {
            std::cout << "read completion handler: size = " << size << " (" << ec.message() << ")" << std::endl;
            nReadSize = size;
        });

    std::cout << "read started" << std::endl;
    size_t iii = m_oIOS.run();

    std::cout << "io_service stopped" << std::endl;
    std::cout << "initiate child::wait" << std::endl;
    m_pChildProcess->wait();
    std::cout << "wait completed" << std::endl;

    std::string const strBuf(m_oAsyncBuffer_Out.data(), nReadSize);

    int result = m_pChildProcess->exit_code();

    m_oAsyncPipe_Out.close();

    m_oIOS.reset();

    return result;
}

int main() {
    CallChildProcess_Style9();
}

Prints

http://coliru.stacked-crooked.com/a/8a9bc6bed3dd5e0a

Launched: Success
read started
read completion handler: size = 1589 (End of file)
io_service stopped
initiate child::wait
wait completed

Hanging Up The Child

Even with that fixed, async_pipe::async_read only reads until the buffer is full or EOF is reached. If the child process outputs more than the buffer size (8k in your sample) then it will get stuck and never finish.

E.g.: replacing the command like this:

std::string strCmdLine = "/usr/bin/yes";

Results in

Live On Coliru

Launched: Success
read started
read completion handler: size = 8192 (Success)
io_service stopped
initiate child::wait

At which it will hang till infinity. This is not because yes has infinite output. Any command having large output will hang (e.g. /bin/cat /etc/dictionaries-common/words hangs in the same way). You can prove this by looking at the strace output:

$ sudo strace -p $(pgrep yes)

strace: Process 21056 attached
write(1, "/home/sehe/Projects/stackoverflo"..., 8170

The easiest way to "fix" this would be to close the output sink after you filled up your output buffer:

boost::asio::async_read(m_oAsyncPipe_Out, boost::asio::buffer(m_oAsyncBuffer_Out),
    [&](const boost::system::error_code &ec, std::size_t size) {
        std::cout << "read completion handler: size = " << size << " (" << ec.message() << ")" << std::endl;
        nReadSize = size;
        m_oAsyncPipe_Out.close();
    });

This requires you to anticipate that the child exited before you call wait() so wait() might fail:

Live On Coliru

Launched: Success
read started
read completion handler: size = 8192 (Success)
io_service stopped
initiate child::wait
wait completed (Success)

Taking A Step Back: What Do You Need?

It looks, though, that you might be complicating. If you're happy limiting the output to 8k, and all you need is to have multiple copies, why bother with async io?

Any child is already asynchronous, and you can just pass the buffer:

Live On Coliru

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

namespace bp   = boost::process;
using Args     = std::vector<std::string>;
using Buffer8k = std::array<char, 8192>;

int main() {
    auto first_out  = std::make_unique<Buffer8k>(),
         second_out = std::make_unique<Buffer8k>();

    *first_out = {};
    *second_out = {};

    boost::asio::io_service svc;

    bp::child first("/bin/echo", Args{"-n", "first"}, bp::std_out > boost::asio::buffer(*first_out), svc);
    bp::child second("/bin/echo", Args{"-n", "second"}, bp::std_out >boost::asio::buffer(*second_out), svc);

    std::cout << "Launched" << std::endl;

    svc.run();
    first.wait();
    second.wait();

    std::string const strFirst(first_out->data()); // uses NUL-termination (assumes text output)
    std::string const strSecond(second_out->data()); // uses NUL-termination (assumes text output)

    std::cout << strFirst << "\n";
    std::cout << strSecond << "\n";

    return first.exit_code();
}

Prints

Launched
first
second

More Examples

Because I can't really be sure about what you need, look at other examples that I wrote to actually show live async IO, where you might need to respond to particular output of one process.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thank you for the very extensive answer! – Chrysmac Mar 25 '18 at 05:57
  • I have tried your version of CallChildProcess_Style9() but for some reason, it stills block at run(), and then goes to wait() after the executable is finished, I know this because the executable is actually a Qt-Application and can also start a MySQL server, if the later is not yet running. And this time, it executed all these possible steps and finished the Executable tasks, before reaching wait(). – Chrysmac Mar 25 '18 at 06:06
  • And when I try to run the last example Main() (using the same test executable), I get an exception in win_iocp_handle_service.ipp at " impl.handle_ = INVALID_HANDLE_VALUE;". I didn't change the code though, just the executable and arguments. I had this error before when I tried this method, which led me to try the async_read method in the first place. I will also have a look at the links you posted. – Chrysmac Mar 25 '18 at 06:06
  • Looks like there is a bug in the windows implementation maybe. Also, yes `io_service::run` blocks. I assumed you run that elsewhere for the real program (based on the name it looks like it is actually a class member instead of a local variable). Note the linked answers which go much deeper into using cold processes asynchronously – sehe Mar 25 '18 at 12:37
  • Sorry for this late comment as I had to work on another project asap. But it seems there was already everything necessary, especially with your first answer about needing to supply the io_service. Just that I was calling the run method before starting the next instance(s) of the executable. Furthermore, it seems my real difficulty, is that the Instance counter in the memory shared segment is not being properly incremented between the simultaneously started instances as expected. Thanks for the help, as I understood the child_process steps a further more! – Chrysmac Mar 30 '18 at 01:54