/** * \file forkedcontr.C * This file is part of LyX, the document processor. * Licence details can be found in the file COPYING. * * \author Asger Alstrup Nielsen * \author Angus Leeming * * Full author contact details are available in file CREDITS. * * A class for the control of child processes launched using * fork() and execvp(). */ #include #include "forkedcontr.h" #include "forkedcall.h" #include "lyxfunctional.h" #include "debug.h" #include "frontends/Timeout.h" #include #include #include #include #include #include using boost::bind; using std::endl; using std::find_if; using std::string; #ifndef CXX_GLOBAL_CSTD using std::strerror; #endif namespace lyx { namespace support { // Ensure, that only one controller exists inside process ForkedcallsController & ForkedcallsController::get() { static ForkedcallsController singleton; return singleton; } ForkedcallsController::ForkedcallsController() { timeout_ = new Timeout(100, Timeout::ONETIME); timeout_->timeout .connect(bind(&ForkedcallsController::timer, this)); } // open question: should we stop childs here? // Asger says no: I like to have my xdvi open after closing LyX. Maybe // I want to print or something. ForkedcallsController::~ForkedcallsController() { delete timeout_; } void ForkedcallsController::addCall(ForkedProcess const & newcall) { if (!timeout_->running()) timeout_->start(); forkedCalls.push_back(newcall.clone()); } // Timer-call // Check the list and, if there is a stopped child, emit the signal. void ForkedcallsController::timer() { ListType::iterator it = forkedCalls.begin(); ListType::iterator end = forkedCalls.end(); while (it != end) { ForkedProcess * actCall = it->get(); pid_t pid = actCall->pid(); int stat_loc; pid_t const waitrpid = waitpid(pid, &stat_loc, WNOHANG); bool remove_it = false; if (waitrpid == -1) { lyxerr << "LyX: Error waiting for child: " << strerror(errno) << endl; // Child died, so pretend it returned 1 actCall->setRetValue(1); remove_it = true; } else if (waitrpid == 0) { // Still running. Move on to the next child. } else if (WIFEXITED(stat_loc)) { // Ok, the return value goes into retval. actCall->setRetValue(WEXITSTATUS(stat_loc)); remove_it = true; } else if (WIFSIGNALED(stat_loc)) { // Child died, so pretend it returned 1 actCall->setRetValue(1); remove_it = true; } else if (WIFSTOPPED(stat_loc)) { lyxerr << "LyX: Child (pid: " << pid << ") stopped on signal " << WSTOPSIG(stat_loc) << ". Waiting for child to finish." << endl; } else { lyxerr << "LyX: Something rotten happened while " "waiting for child " << pid << endl; // Child died, so pretend it returned 1 actCall->setRetValue(1); remove_it = true; } if (remove_it) { actCall->emitSignal(); forkedCalls.erase(it); /* start all over: emiting the signal can result * in changing the list (Ab) */ it = forkedCalls.begin(); } else { ++it; } } if (!forkedCalls.empty() && !timeout_->running()) { timeout_->start(); } } // Kill the process prematurely and remove it from the list // within tolerance secs void ForkedcallsController::kill(pid_t pid, int tolerance) { typedef boost::indirect_iterator iterator; iterator begin = boost::make_indirect_iterator(forkedCalls.begin()); iterator end = boost::make_indirect_iterator(forkedCalls.end()); iterator it = find_if(begin, end, lyx::compare_memfun(&Forkedcall::pid, pid)); if (it == end) return; it->kill(tolerance); forkedCalls.erase(it.base()); if (forkedCalls.empty()) timeout_->stop(); } } // namespace support } // namespace lyx