Add support for asynchronous child processes on Windows.

git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/branches/BRANCH_1_3_X@9843 a592a061-630c-0410-9148-cb99ea01b6c8
This commit is contained in:
Angus Leeming 2005-04-20 17:36:15 +00:00
parent 8dc7d275c5
commit 906b29c383
5 changed files with 190 additions and 80 deletions

View File

@ -1,3 +1,27 @@
2005-04-19 Angus Leeming <leeming@lyx.org>
* forkedcall.C: protect system-specific headers with preprocessor
guards.
(running): don't call waitpid() on Windows.
(waitForChild): add Windows-specific code to wait for a child process
to finish.
(generateChild): add Windows-specific code to spawn the child in the
first place.
* forkedcontr.[Ch]: remove cruft from the days of the dialog to kill
child processes. Class no longer derives from trackable, no longer
emits a global signal when a child process is reaped.
(getPIDs, getCommand): removed.
* forkedcontr.C: protect system-specific headers with preprocessor
guards. Add Windows-specific code to wait for a child process
asynchronously.
(getChildErrorMessage): new function, wrapping the Windows native API.
(timer): use WaitForSingleObject and GetExitCodeProcess on Windows.
* kill.C (kill): add Ruurd's Windows-specific code to terminate
a child process.
2005-04-19 Angus Leeming <leeming@lyx.org> 2005-04-19 Angus Leeming <leeming@lyx.org>
* package.C (get_temp_dir): call GetLongPathName on Windows. * package.C (get_temp_dir): call GetLongPathName on Windows.

View File

@ -30,19 +30,27 @@
#include "lyxlib.h" #include "lyxlib.h"
#include "filetools.h" #include "filetools.h"
#include "os.h" #include "os.h"
#include "debug.h" #include "debug.h"
#include "frontends/Timeout.h" #include "frontends/Timeout.h"
#include <boost/bind.hpp> #include <boost/bind.hpp>
#include <vector> #include <vector>
#include <cerrno>
#include <sys/types.h> #ifdef _WIN32
#include <sys/wait.h> # define SIGHUP 1
#include <csignal> # define SIGKILL 9
#include <cstdlib> # include <process.h>
#ifdef HAVE_UNISTD_H # include <windows.h>
#include <unistd.h>
#else
# include <cerrno>
# include <csignal>
# include <cstdlib>
# include <unistd.h>
# include <sys/types.h>
# include <sys/wait.h>
#endif #endif
using std::endl; using std::endl;
@ -153,10 +161,12 @@ bool ForkedProcess::running() const
if (!pid()) if (!pid())
return false; return false;
#if !defined (_WIN32)
// Un-UNIX like, but we don't have much use for // Un-UNIX like, but we don't have much use for
// knowing if a zombie exists, so just reap it first. // knowing if a zombie exists, so just reap it first.
int waitstatus; int waitstatus;
waitpid(pid(), &waitstatus, WNOHANG); waitpid(pid(), &waitstatus, WNOHANG);
#endif
// Racy of course, but it will do. // Racy of course, but it will do.
if (lyx::kill(pid(), 0) && errno == ESRCH) if (lyx::kill(pid(), 0) && errno == ESRCH)
@ -196,6 +206,29 @@ int ForkedProcess::waitForChild()
{ {
// We'll pretend that the child returns 1 on all error conditions. // We'll pretend that the child returns 1 on all error conditions.
retval_ = 1; retval_ = 1;
#if defined (_WIN32)
HANDLE const hProcess = HANDLE(pid_);
DWORD const wait_status = ::WaitForSingleObject(hProcess, INFINITE);
switch (wait_status) {
case WAIT_OBJECT_0: {
DWORD exit_code = 0;
if (!GetExitCodeProcess(hProcess, &exit_code)) {
lyxerr << "GetExitCodeProcess failed waiting for child\n"
<< getChildErrorMessage() << std::endl;
} else
retval_ = exit_code;
break;
}
case WAIT_FAILED:
lyxerr << "WaitForSingleObject failed waiting for child\n"
<< getChildErrorMessage() << std::endl;
break;
}
#else
int status; int status;
bool wait = true; bool wait = true;
while (wait) { while (wait) {
@ -224,6 +257,7 @@ int ForkedProcess::waitForChild()
wait = false; wait = false;
} }
} }
#endif
return retval_; return retval_;
} }
@ -327,7 +361,12 @@ int Forkedcall::generateChild()
lyxerr << "</command>" << std::endl; lyxerr << "</command>" << std::endl;
} }
#ifndef __EMX__ #if defined (__EMX__)
pid_t const cpid = spawnvp(P_SESSION|P_DEFAULT|P_MINIMIZE|P_BACKGROUND,
argv[0], &*argv.begin());
#elif defined (_WIN32)
pid_t const cpid = spawnvp(_P_NOWAIT, argv[0], &*argv.begin());
#else // POSIX
pid_t const cpid = ::fork(); pid_t const cpid = ::fork();
if (cpid == 0) { if (cpid == 0) {
// Child // Child
@ -338,9 +377,6 @@ int Forkedcall::generateChild()
<< strerror(errno) << endl; << strerror(errno) << endl;
_exit(1); _exit(1);
} }
#else
pid_t const cpid = spawnvp(P_SESSION|P_DEFAULT|P_MINIMIZE|P_BACKGROUND,
argv[0], &*argv.begin());
#endif #endif
if (cpid < 0) { if (cpid < 0) {

View File

@ -19,25 +19,59 @@
#include "lyxfunctional.h" #include "lyxfunctional.h"
#include "debug.h" #include "debug.h"
#ifdef _WIN32
# include <sstream>
# include <windows.h>
#else
# include <cerrno>
# include <cstdlib>
# include <unistd.h>
# include <sys/wait.h>
# ifndef CXX_GLOBAL_CSTD
using std::strerror;
# endif
#endif
#include "frontends/Timeout.h" #include "frontends/Timeout.h"
#include <boost/bind.hpp> #include <boost/bind.hpp>
#include <cerrno>
#include <cstdlib>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/wait.h>
using std::vector; using std::vector;
using std::endl; using std::endl;
using std::find_if; using std::find_if;
#ifndef CXX_GLOBAL_CSTD
using std::strerror; #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 STRCONV(ss.str());
}
#endif #endif
// Ensure, that only one controller exists inside process // Ensure, that only one controller exists inside process
ForkedcallsController & ForkedcallsController::get() ForkedcallsController & ForkedcallsController::get()
{ {
@ -75,7 +109,6 @@ void ForkedcallsController::addCall(ForkedProcess const & newcall)
timeout_->start(); timeout_->start();
forkedCalls.push_back(newcall.clone()); forkedCalls.push_back(newcall.clone());
childrenChanged();
} }
@ -83,17 +116,45 @@ void ForkedcallsController::addCall(ForkedProcess const & newcall)
// Check the list and, if there is a stopped child, emit the signal. // Check the list and, if there is a stopped child, emit the signal.
void ForkedcallsController::timer() void ForkedcallsController::timer()
{ {
ListType::size_type start_size = forkedCalls.size();
ListType::iterator it = forkedCalls.begin(); ListType::iterator it = forkedCalls.begin();
ListType::iterator end = forkedCalls.end(); ListType::iterator end = forkedCalls.end();
while (it != end) { while (it != end) {
ForkedProcess * actCall = *it; ForkedProcess * 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(); pid_t pid = actCall->pid();
int stat_loc; int stat_loc;
pid_t const waitrpid = waitpid(pid, &stat_loc, WNOHANG); pid_t const waitrpid = waitpid(pid, &stat_loc, WNOHANG);
bool remove_it = false;
if (waitrpid == -1) { if (waitrpid == -1) {
lyxerr << "LyX: Error waiting for child: " lyxerr << "LyX: Error waiting for child: "
@ -130,6 +191,7 @@ void ForkedcallsController::timer()
actCall->setRetValue(1); actCall->setRetValue(1);
remove_it = true; remove_it = true;
} }
#endif
if (remove_it) { if (remove_it) {
forkedCalls.erase(it); forkedCalls.erase(it);
@ -149,44 +211,6 @@ void ForkedcallsController::timer()
if (!forkedCalls.empty() && !timeout_->running()) { if (!forkedCalls.empty() && !timeout_->running()) {
timeout_->start(); 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());
vector<pid_t>::iterator vit = pids.begin();
for (ListType::const_iterator lit = forkedCalls.begin();
lit != forkedCalls.end(); ++lit, ++vit) {
*vit = (*lit)->pid();
}
std::sort(pids.begin(), pids.end());
return pids;
}
// Get the command string of the process.
string const ForkedcallsController::getCommand(pid_t pid) const
{
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();
} }

View File

@ -18,9 +18,6 @@
#include "LString.h" #include "LString.h"
#include <boost/signals/signal0.hpp>
#include <boost/signals/trackable.hpp>
#include <sys/types.h> // needed for pid_t #include <sys/types.h> // needed for pid_t
#include <list> #include <list>
@ -29,7 +26,7 @@
class ForkedProcess; class ForkedProcess;
class Timeout; class Timeout;
class ForkedcallsController : public boost::signals::trackable { class ForkedcallsController {
public: public:
/// We need this to avoid warnings. /// We need this to avoid warnings.
ForkedcallsController(); ForkedcallsController();
@ -51,21 +48,12 @@ public:
*/ */
void timer(); void timer();
/// Return a vector of the pids of all the controlled processes.
std::vector<pid_t> const getPIDs() const;
/// Get the command string of the process.
string const getCommand(pid_t) const;
/** Kill this process prematurely and remove it from the list. /** Kill this process prematurely and remove it from the list.
* The process is killed within tolerance secs. * The process is killed within tolerance secs.
* See forkedcall.[Ch] for details. * See forkedcall.[Ch] for details.
*/ */
void kill(pid_t, int tolerance = 5); void kill(pid_t, int tolerance = 5);
/// Signal emitted when the list of current child processes changes.
boost::signal0<void> childrenChanged;
private: private:
/// ///
ForkedcallsController(ForkedcallsController const &); ForkedcallsController(ForkedcallsController const &);
@ -81,4 +69,9 @@ private:
Timeout * timeout_; Timeout * timeout_;
}; };
#if defined(_WIN32)
// a wrapper for GetLastError() and FormatMessage().
string const getChildErrorMessage();
#endif
#endif // FORKEDCONTR_H #endif // FORKEDCONTR_H

View File

@ -5,7 +5,40 @@
#include <sys/types.h> #include <sys/types.h>
#include <csignal> #include <csignal>
#ifdef _WIN32
#include "debug.h"
#include "os.h"
#include <windows.h>
#include <errno.h>
using std::endl;
#endif //_WIN32
int lyx::kill(int pid, int sig) int lyx::kill(int pid, int sig)
{ {
#ifdef _WIN32 && 0
if (pid == (int)GetCurrentProcessId())
return -(raise(sig));
else{
HANDLE hProcess;
if (!(hProcess =
OpenProcess(PROCESS_ALL_ACCESS, TRUE, pid))) {
lyxerr << "kill OpenProcess failed!" << endl;
return -1;
}
else {
if (!TerminateProcess(hProcess, sig)){
lyxerr << "kill process failed!" << endl;
CloseHandle(hProcess);
return -1;
}
CloseHandle(hProcess);
}
}
return 0;
#else
return ::kill(pid, sig); return ::kill(pid, sig);
#endif
} }