In the following code, I implement a class that starts a process and returns its return code, stdout
and stderr
. It also has a concept of timeout after which the child process is killed. This class will be used as a part of a multi-threaded application.
#ifndef PROCESS_H_
#define PROCESS_H_
//Header
#include <chrono>
#include <thread>
#include <iostream>
#include <boost/process.hpp>
#include <boost/asio.hpp
namespace bp = boost::process;
class Proces
{
public:
Process(const std::string& cmd, const int timeout);
void run();
int getReturnStatus(); //Must be only called after run() exits
std::string getStdOut(); //Must be only called after run() exits
std::string getStdErr(); //Must be only called after run() exits
bool wasKilled(); //Must be only called after run() exits
private:
void initLog();
void timeoutHandler(const boost::system::error_code& ec);
void kill();
const std::string command;
const int timeout;
int returnStatus;
std::string stdOut;
std::string stdErr;
bool killed;
bool stopped;
boost::process::group group;
boost::asio::io_context ioc;
boost::asio::deadline_timer deadline_timer;
};
#endif
//Source
#include "Process.h"
Process::Process(const std::string& cmd, const int timeout):
command(cmd),
timeout(timeout),
returnStatus(0),
stdOut(""),
stdErr(""),
killed(false),
stopped(false),
ioc(),
deadline_timer(ioc)
{
}
void Process::timeoutHandler(const boost::system::error_code& ec)
{
if (stopped || ec == boost::asio::error::operation_aborted)
{
return;
}
std::cout << "Time Up!"<< std::endl;
kill();
deadline_timer.expires_at(boost::posix_time::pos_infin);
}
void Process::run()
{
std::future<std::string> dataOut;
std::future<std::string> dataErr;
std::cout << "Running command: " << command << std::endl;
bp::child c(command, bp::std_in.close(),
bp::std_out > dataOut,
bp::std_err > dataErr, ioc,
group,
bp::on_exit([=](int e, const std::error_code& ec) {
std::cout << "on_exit: " << ec.message() << " -> " << e << std::endl;
deadline_timer.cancel();
returnStatus = e;
}));
deadline_timer.expires_from_now(boost::posix_time::seconds(timeout));
deadline_timer.async_wait(std::bind(&Process::timeoutHandler, this, std::placeholders::_1));
ioc.run();
c.wait();
stdOut = dataOut.get();
stdErr = dataErr.get();
}
//Must be only called after run() exits
int Process::getReturnStatus()
{
return returnStatus;
}
//Must be only called after run() exits
std::string Process::getStdOut()
{
return stdOut;
}
//Must be only called after run() exits
std::string Process::getStdErr()
{
return stdErr;
}
void Process::kill()
{
std::error_code ec;
group.terminate(ec);
if(ec)
{
std::cout << "Error occurred while trying to kill the process: " << ec.message();
throw std::runtime_error(ec.message());
}
std::cout << "Killed the process and all its descendants" << std::endl;
killed = true;
stopped = true;
}
//Must be only called after run() exits
bool Process::wasKilled()
{
return killed;
}
Below is the code I am using to test it
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE ProcessTest
#include <boost/test/unit_test.hpp>
#include "../src/Process.h"
BOOST_AUTO_TEST_CASE( ProcessTest )
{
const std::vector<std::string> commands = {
"ls"
, "pwd"
, "uname -a"
, "cat /proc/cpuinfo"
, "wget https://dl.google.com/dl/earth/client/current/google-earth-pro-stable-current.x86_64.rpm"};
for(const auto& cmd: commands)
{
Process p(cmd, 3600);
p.run();
BOOST_CHECK( p.getReturnStatus() == 0); // #1 continues on error
}
const std::vector<std::string> commandsThatShouldFail = {
"ls doesnotexit"
, "cat /proc/doesnotexist"
, "wget https://dl.google.com/dl/earth/client/current/doesnotxist.rpm"};
for(const auto& cmd: commandsThatShouldFail)
{
Process p(cmd, 3600);
p.run();
BOOST_CHECK( p.getReturnStatus() != 0); // #1 continues on error
}
}
Please provide your valuable comments and suggestions.