Using single io_context to run mutlple processes with timeout

105 Views Asked by At

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.

2

There are 2 best solutions below

5
sehe On BEST ANSWER

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

8
bbalazs On

Thread creation takes time, your ioc.run() is executed before the threads start using the io_context, therefore the run() returns immediately.

This is what you might want:

boost::asio::io_context ctx;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> workGuard(ctx.get_executor());

ctx.run();

Call workGuard.reset() on other thread if you want the io_context::run() stop.

Side note: it's not advised to use anything from a namespace called detail. It's usually the playground of the given library, might be changed over time without any notice.

Update

It's also necessary to increase the timeout. 20ms is way too short to execute a child process. It works perfectly with 2s.