Running a process using boost process in async mode with timeout

余生颓废 提交于 2020-06-27 16:35:31

问题


In the following code, I am trying to implement a program that runs a shell command and get the stdio, stderr and return code. I am doing it using boost process in the async mode as advised here.

namespace bp = boost::process;
class Process {

public:
    Process(std::string & cmd, const int timeout);
    void run();

private:
    void timeout_handler();

    const std::string command;
    const int timeout;

    bool killed;
    bool stopped;

    std::string stdOut;
    std::string stdErr;
    int returnStatus;

    boost::asio::io_service ios;
    boost::process::group group;
    boost::asio::deadline_timer deadline_timer;
};

Process::Process(std::string & cmd, const int timeout):
    command(cmd),
    timeout(timeout),
    deadline_timer(ios)
{}

void Process::timeout_handler()
{
    if (stopped)
    return;

    if (deadline_timer.expires_at() <= boost::asio::deadline_timer::traits_type::now())
    {
        std::cout << "Time Up!" << std::endl;
        group.terminate();
        std::cout << "Killed the process and all its decendents" << std::endl;
        killed = true;
        stopped = true;
        deadline_timer.expires_at(boost::posix_time::pos_infin);
    }
    deadline_timer.async_wait(std::bind(&Process::timeout_handler, this));
}

void Process::run()
{

    std::future<std::string> dataOut;
    std::future<std::string> dataErr;

    bp::child c(command, bp::std_in.close(), bp::std_out > dataOut, bp::std_err > dataErr, ios, group);
    deadline_timer.expires_from_now(boost::posix_time::seconds(timeout));
    deadline_timer.async_wait(std::bind(&Process::timeout_handler, this));

    ios.run();
    c.wait();

    stdOut = dataOut.get();
    stdErr = dataErr.get();
    returnStatus = c.exit_code();
}

int main(int argc, char** argv)
{
    if(argc < 2)
    {
    std::cout << "Usage: \na.out <command>" << std::endl;
    exit(1);
    }
    std::vector<std::string> arguments(argv + 1, argv + argc);

    std::string command;
    for( const auto & tok : arguments)
    {
        command += tok + " ";
    }

    std::cout << command << std::endl;
    Process p(command, 10);
    p.run();
    return 0;
}

Now, the above code returns only after deadline_timer expires. What I want is that the child process should exit if it finishes before the timer expires or it (along with all the child processes it forks), should be terminated. Please point out the mistake in my code.


回答1:


The mistake is indeed very simple: you should cancel the deadline timer!

io_service::run() will not return unless

  1. an exception emanated from a handler
  2. no more work is queued.

While the dead line timer is in progress, that means the second condition isn't met. So io_service::run() waits for it because you asked it to.

Other notes:

  • use the error code to detect timer cancellation instead of racy time comparisons
  • no need to loop-chain the timer (in fact, that's asking for bugs where the io_service never completes)
  • your code failed to initialize stopped and killed

Live On Coliru

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

namespace bp = boost::process;
class Process {

  public:
    Process(std::string &cmd, const int timeout);
    void run();

  private:
    void timeout_handler(boost::system::error_code ec);

    const std::string command;
    const int timeout;

    bool killed = false;
    bool stopped = false;

    std::string stdOut;
    std::string stdErr;
    int returnStatus = 0;

    boost::asio::io_service ios;
    boost::process::group group;
    boost::asio::deadline_timer deadline_timer;
};

Process::Process(std::string &cmd, const int timeout) : command(cmd), timeout(timeout), deadline_timer(ios) {}

void Process::timeout_handler(boost::system::error_code ec) {
    if (stopped)
        return;

    if (ec == boost::asio::error::operation_aborted)
        return;

    if (deadline_timer.expires_at() <= boost::asio::deadline_timer::traits_type::now()) {
        std::cout << "Time Up!" << std::endl;
        group.terminate(); // NOTE: anticipate errors
        std::cout << "Killed the process and all its decendants" << std::endl;
        killed = true;
        stopped = true;
        deadline_timer.expires_at(boost::posix_time::pos_infin);
    }
    //NOTE: don't make it a loop
    //deadline_timer.async_wait(boost::bind(&Process::timeout_handler, this, boost::asio::placeholders::error));
}

void Process::run() {

    std::future<std::string> dataOut;
    std::future<std::string> dataErr;

    deadline_timer.expires_from_now(boost::posix_time::seconds(timeout));
    deadline_timer.async_wait(boost::bind(&Process::timeout_handler, this, boost::asio::placeholders::error));

    bp::child c(command, bp::std_in.close(), bp::std_out > dataOut, bp::std_err > dataErr, ios, group, 
            bp::on_exit([=](int e, std::error_code ec) {
                // TODO handle errors
                std::cout << "on_exit: " << ec.message() << " -> " << e << std::endl;
                deadline_timer.cancel();
                returnStatus = e;
            }));

    ios.run();

    stdOut = dataOut.get();
    stdErr = dataErr.get();

    c.wait();

    returnStatus = c.exit_code();
}

int main(int argc, char **argv) {
    if (argc < 2) {
        std::cout << "Usage: \na.out <command>" << std::endl;
        exit(1);
    }
    std::vector<std::string> arguments(argv + 1, argv + argc);

    std::string command;
    for (const auto &tok : arguments) {
        command += tok + " ";
    }

    std::cout << command << std::endl;
    Process p(command, 2);
    p.run();
}

Prints e.g.

 $ ./sotest 'echo hello'

echo hello 
on_exit: Success -> 0

 $ ./sotest 'sleep 1'

sleep 1 
on_exit: Success -> 0

 $ ./sotest 'sleep 3'

sleep 3 
Time Up!
Killed the process and all its decendants
on_exit: Success -> 9


来源:https://stackoverflow.com/questions/52624004/running-a-process-using-boost-process-in-async-mode-with-timeout

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!