问题
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
- an exception emanated from a handler
- 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
andkilled
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