* \author Asger Alstrup Nielsen
* \author Angus Leeming
*
- * Full author contact details are available in file CREDITS
+ * Full author contact details are available in file CREDITS.
*
* A class for the control of child processes launched using
* fork() and execvp().
#include <config.h>
-#include "forkedcontr.h"
-#include "forkedcall.h"
-#include "lyxfunctional.h"
+#include "support/forkedcontr.h"
+#include "support/forkedcall.h"
+
#include "debug.h"
-#include "frontends/Timeout.h"
+#ifdef _WIN32
+# include <sstream>
+# include <windows.h>
+
+#else
+# include <cerrno>
+# include <csignal>
+# include <cstdlib>
+# ifdef HAVE_UNISTD_H
+# include <unistd.h>
+# endif
+# include <sys/wait.h>
+
+# ifndef CXX_GLOBAL_CSTD
+ using std::signal;
+ using std::strerror;
+# endif
+#endif
#include <boost/bind.hpp>
-#include <cerrno>
-#include <cstdlib>
-#include <unistd.h>
-#include <sys/wait.h>
+using boost::bind;
-using std::vector;
using std::endl;
+using std::equal_to;
using std::find_if;
-#ifndef CXX_GLOBAL_CSTD
-using std::strerror;
+using std::string;
+using std::vector;
+
+namespace lyx {
+namespace support {
+
+#if defined(_WIN32)
+string const getChildErrorMessage()
+{
+ DWORD const error_code = ::GetLastError();
+
+ HLOCAL t_message = 0;
+ bool const ok = ::FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ 0, error_code,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &t_message, 0, 0
+ ) != 0;
+
+ std::ostringstream ss;
+ ss << "LyX: Error waiting for child: " << error_code;
+
+ if (ok) {
+ ss << ": " << (LPTSTR)t_message;
+ ::LocalFree(t_message);
+ } else
+ ss << ": Error unknown.";
+
+ return ss.str().c_str();
+}
#endif
+
// Ensure, that only one controller exists inside process
ForkedcallsController & ForkedcallsController::get()
{
ForkedcallsController::ForkedcallsController()
-{
- timeout_ = new Timeout(100, Timeout::ONETIME);
-
- timeout_->timeout
- .connect(boost::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()
-{
- for (ListType::iterator it = forkedCalls.begin();
- it != forkedCalls.end(); ++it) {
- delete *it;
- }
-
- delete timeout_;
-}
+{}
void ForkedcallsController::addCall(ForkedProcess const & newcall)
{
- if (!timeout_->running())
- timeout_->start();
-
forkedCalls.push_back(newcall.clone());
- childrenChanged();
}
-// Timer-call
-// Check the list and, if there is a stopped child, emit the signal.
-void ForkedcallsController::timer()
+// Check the list of dead children and emit any associated signals.
+void ForkedcallsController::handleCompletedProcesses()
{
- ListType::size_type start_size = forkedCalls.size();
-
- for (ListType::iterator it = forkedCalls.begin();
- it != forkedCalls.end(); ++it) {
- ForkedProcess * actCall = *it;
+ ListType::iterator it = forkedCalls.begin();
+ ListType::iterator end = forkedCalls.end();
+ while (it != end) {
+ ForkedProcessPtr actCall = *it;
+ bool remove_it = false;
+#if defined(_WIN32)
+ HANDLE const hProcess = HANDLE(actCall->pid());
+
+ DWORD const wait_status = ::WaitForSingleObject(hProcess, 0);
+
+ switch (wait_status) {
+ case WAIT_TIMEOUT:
+ // Still running
+ break;
+ case WAIT_OBJECT_0: {
+ DWORD exit_code = 0;
+ if (!GetExitCodeProcess(hProcess, &exit_code)) {
+ lyxerr << "GetExitCodeProcess failed waiting for child\n"
+ << getChildErrorMessage() << std::endl;
+ // Child died, so pretend it returned 1
+ actCall->setRetValue(1);
+ } else {
+ actCall->setRetValue(exit_code);
+ }
+ remove_it = true;
+ break;
+ }
+ case WAIT_FAILED:
+ lyxerr << "WaitForSingleObject failed waiting for child\n"
+ << getChildErrorMessage() << std::endl;
+ actCall->setRetValue(1);
+ remove_it = true;
+ break;
+ }
+#else
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: "
} else if (waitrpid == 0) {
// Still running. Move on to the next child.
- continue;
} else if (WIFEXITED(stat_loc)) {
// Ok, the return value goes into retval.
actCall->setRetValue(1);
remove_it = true;
}
+#endif
if (remove_it) {
- // Emit signal and remove the item from the list
- actCall->emitSignal();
- delete actCall;
- // erase returns the next iterator, so decrement it
- // to continue the loop.
- ListType::iterator prev = it;
- --prev;
forkedCalls.erase(it);
- it = prev;
- }
- }
-
- if (!forkedCalls.empty() && !timeout_->running()) {
- timeout_->start();
- }
-
- if (start_size != forkedCalls.size())
- childrenChanged();
-}
-
-
-// Return a vector of the pids of all the controlled processes.
-vector<pid_t> const ForkedcallsController::getPIDs() const
-{
- vector<pid_t> pids;
-
- if (forkedCalls.empty())
- return pids;
-
- pids.resize(forkedCalls.size());
+ actCall->emitSignal();
- vector<pid_t>::iterator vit = pids.begin();
- for (ListType::const_iterator lit = forkedCalls.begin();
- lit != forkedCalls.end(); ++lit, ++vit) {
- *vit = (*lit)->pid();
+ /* start all over: emiting the signal can result
+ * in changing the list (Ab)
+ */
+ it = forkedCalls.begin();
+ } else {
+ ++it;
+ }
}
-
- std::sort(pids.begin(), pids.end());
- return pids;
}
-// Get the command string of the process.
-string const ForkedcallsController::getCommand(pid_t pid) const
+ForkedcallsController::iterator
+ForkedcallsController::find_pid(pid_t pid)
{
- ListType::const_iterator it =
- find_if(forkedCalls.begin(), forkedCalls.end(),
- lyx::compare_memfun(&Forkedcall::pid, pid));
-
- if (it == forkedCalls.end())
- return string();
-
- return (*it)->command();
+ return find_if(forkedCalls.begin(), forkedCalls.end(),
+ bind(equal_to<pid_t>(),
+ bind(&Forkedcall::pid, _1),
+ pid));
}
// within tolerance secs
void ForkedcallsController::kill(pid_t pid, int tolerance)
{
- ListType::iterator it =
- find_if(forkedCalls.begin(), forkedCalls.end(),
- lyx::compare_memfun(&Forkedcall::pid, pid));
-
+ ListType::iterator it = find_pid(pid);
if (it == forkedCalls.end())
return;
(*it)->kill(tolerance);
forkedCalls.erase(it);
-
- if (forkedCalls.empty()) {
- timeout_->stop();
- }
}
+
+} // namespace support
+} // namespace lyx