Implementación de una clase que ejecuta un proceso hijo con tiempo de espera usando el proceso boost en modo asíncrono

En el siguiente código, estoy tratando de implementar un programa que ejecuta un procesa y devuelve su código de retorno, stdout y stderr. También tiene un concepto de tiempo de espera después del cual se mata el proceso hijo. Esta clase se usará como parte de una aplicación multiproceso.

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

A continuación se muestra el código que estoy usando para probarlo

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

Proporcione sus valiosos comentarios y sugerencias.

Comentarios

  • ¡Bienvenido a Code Review! Dice que ' está intentando implementar el programa. ¿Funciona hasta ahora? ¿Tiene alguna inquietud sobre su código? ' está bien si no ' t, siempre que el código funcione según lo previsto.
  • @Mast El código funciona para los casos en los que lo he probado. Pero me pregunto si hay condiciones de carrera y otros errores.
  • ¡Bien! ¿Podría incluir esas pruebas?

Respuesta

Todavía me estoy acostumbrando a Boost::Process, así que tal vez me haya equivocado. Sin embargo, ¿realmente necesita c.wait() después de ios.run()?

Según su documentación , el control se bloqueará en ios.run() hasta que io_context finalice o, por supuesto, si su deadline_timer expira.

Responder

Pruebas faltantes

I no ve ninguna prueba para los comandos que deberían agotarse. Un ejemplo obvio podría ser sleep 2, si lo ejecutamos con un tiempo de espera de 1. Una prueba más rigurosa sería un comando que ignora todas las señales (de las que se pueden ignorar). Debemos asegurarnos de que si SIGTERM no funciona, obtendrá SIGKILL eventualmente.

Pruebas basadas en datos

No me gusta ver bucles dentro de los casos de prueba. ¿Boost Test tiene provisión para ¿Repetir pruebas con datos diferentes? La mayoría de los marcos de prueba lo hacen; emitido desde Boost.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *