2

在下面的代码中,我试图实现一个运行 shell 命令并获取stdio,stderr并返回代码的程序。我正在按照此处boost process建议的async模式使用它。

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;
}

现在,上面的代码只有在deadline_timer过期后才返回。我想要的是,如果子进程在计时器到期之前完成,或者它(连同它派生的所有子进程)应该终止,它应该退出。请指出我代码中的错误。

4

2 回答 2

2

错误确实很简单:你应该取消deadline timer!

io_service::run()不会返回,除非

  1. 从处理程序发出的异常
  2. 没有更多的工作在排队。

当最后期限计时器正在进行时,这意味着不满足第二个条件。所以io_service::run()等待它,因为你要求它。

其他注意事项:

  • 使用错误代码来检测计时器取消而不是激烈的时间比较
  • 无需循环链接计时器(事实上,这要求 io_service 永远不会完成的错误)
  • 您的代码未能初始化stoppedkilled

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();
}

打印例如

 $ ./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
于 2018-10-03T11:41:46.923 回答
0

以下是如何运行超时进程的一个非常简单的逻辑。

std::string cmd = "sleep 20";
int timeout = 5;

boost::process::child c(cmd);
std::error_code ec;
if (!c.wait_for(std::chrono::seconds(timeout), ec)) {
    std::cout << "nTimeout reached. Process terminated after "
              << timeout << " seconds.\n";
      c.terminate(ec);
}      
于 2021-06-25T00:36:32.683 回答