c++boostboost-asioboost-process

Using single io_context to run mutlple processes with timeout


I've slightly changed the example found here to run all processes from a single io_context object which is defined in main, and delivered to each task, runs from a different thread.

I was expecting all tasks to complete successfully since they should take less than the timeout. However, I'm getting timeout on all the tasks. Any idea why ?

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

using duration = std::chrono::system_clock::duration;
namespace asio = boost::asio;
using namespace std::chrono_literals;

std::string ExecuteProcess(boost::filesystem::path  exe,
                           std::vector<std::string> args, //
                           duration                 time, //
                           std::error_code&         ec, 
               asio::io_context&        ioc) {
    namespace bp = boost::process;
    std::future<std::string> data, err_output;

    bp::group g;
    bp::child child(exe, args, ioc, g, bp::error(ec),
                    bp::std_in.null(),  //
                    bp::std_out > data, //
                    bp::std_err > err_output);

    if (std::error_code ignore; child.running()) {
        g.terminate(ignore);
    }

    if (data.wait_for(time) == std::future_status::ready) {
        ec.clear();
        return data.get();
    }

    ec = make_error_code(asio::error::timed_out);
    return {};
}

int main() {
    constexpr duration timeout = 20ms;
    constexpr auto     script2  = "sleep 0.00005; echo -n 'Hello world'";
    constexpr auto     script1 = "/usr/bin/curl http://httpbin.org/ip -m 5";

    asio::io_context         ioc;
 
    boost::asio::detail::thread_group collect_threads;
    for (int i = 0 ; i < 20 ; i++) {
        collect_threads.create_thread([&]() {
            std::error_code ec_;
            auto s = ExecuteProcess("/bin/bash",    {"-c", script2}, timeout, ec_, ioc);
            std::cout << "got " << ec_.message() << ": " << s << std::endl;
        });
    }
    ioc.run();
}

P.S: For the timeout implementation, I was also considering the option to use result = waitpid(pid, &status, WNOHANG) instead of std::future. However, when I've finished waiting and the process stdout seems correct, the check child.exit_code() return 383 which means that the process still running - not sure why.


Solution

  • It took me forever to see, but... you actively terminate the child, before waiting for any result ¯\(ツ)

    With some re-ordering, and other minor tweaks:

    Live On Coliru

    #include <boost/asio.hpp>
    #include <boost/process.hpp>
    #include <iostream>
    
    using duration = std::chrono::system_clock::duration;
    namespace asio = boost::asio;
    using namespace std::chrono_literals;
    
    std::string ExecuteProcess(boost::filesystem::path  exe,
                               std::vector<std::string> args, //
                               duration                 time, //
                               std::error_code&         ec,   //
                               asio::io_context&        ioc) {
        namespace bp = boost::process;
        std::future<std::string> data, err_output;
    
        auto const deadline = std::chrono::steady_clock::now() + time;
    
        bp::group  g;
        ec.clear();
        bp::child child(exe, args, ioc, g, bp::error(ec), bp::std_in.null(), bp::std_out > data,
                        bp::std_err > err_output);
    
        if (ec)
            return {};
    
        if (data.wait_until(deadline) == std::future_status::ready)
            return data.get();
    
        if (std::error_code ignore; child.running(ignore))
            g.terminate(ignore);
    
        ec = make_error_code(asio::error::timed_out); // TODO FIXME
        return {};
    }
    
    int main() {
        constexpr duration              timeout = 20ms;
        [[maybe_unused]] constexpr auto script1 = "/usr/bin/curl http://httpbin.org/ip -m 5";
        [[maybe_unused]] constexpr auto script2 = R"(delay="0.0$RANDOM"; sleep "$delay"; echo -n "sanity restored after $delay")";
    
        asio::io_context ioc;
    
        auto        work = make_work_guard(ioc); // prevent running out of work
        std::thread io_thread([&ioc] { ioc.run(); });
    
        for (int i = 0; i < 20; i++) {
            std::error_code ec;
    
            auto s = ExecuteProcess("/bin/bash", {"-c", script2}, timeout, ec, ioc);
            std::cout << "got " << ec.message() << ": " << quoted(s) << std::endl;
        }
    
        work.reset(); // allow running out of work
        io_thread.join();
    }
    

    Prints e.g.

    enter image description here