Search code examples
boostparallel-processingboost-asioboost-process

How to test an instance counter by asynchronous run of a boost childprocess?


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;

}


Solution

  • 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.