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.
@ -40,7 +64,7 @@
* filetools.[Ch] (LibScriptSearch): backport the 1.4.x code so * filetools.[Ch] (LibScriptSearch): backport the 1.4.x code so
that the function now substitutes the $$s placeholder for the that the function now substitutes the $$s placeholder for the
path to the appropriate LyX support directory. path to the appropriate LyX support directory.
* forkedcall.C (generateChild): backport the 1.4.x code to split * forkedcall.C (generateChild): backport the 1.4.x code to split
the input command up into an array of words and to strip any quotes. the input command up into an array of words and to strip any quotes.
@ -81,7 +105,7 @@
* FileInfo.h: * FileInfo.h:
* chdir.C: * chdir.C:
* forkedcall.C: * forkedcall.C:
* forkedcontr.C: * forkedcontr.C:
* getcwd.C: * getcwd.C:
* lyxsum.C: * lyxsum.C:
@ -191,7 +215,7 @@
file using mktemp and open. file using mktemp and open.
2004-12-14 Angus Leeming <leeming@lyx.org> 2004-12-14 Angus Leeming <leeming@lyx.org>
* os.h, os_os2.C, os_unix.C, os_win32.C: * os.h, os_os2.C, os_unix.C, os_win32.C:
(binpath, binname, getTmpDir): return a const reference rather than (binpath, binname, getTmpDir): return a const reference rather than
a copy of the data. a copy of the data.
@ -213,7 +237,7 @@
2004-12-14 Angus Leeming <leeming@lyx.org> 2004-12-14 Angus Leeming <leeming@lyx.org>
* os.C: Add _WIN32 to the #define. * os.C: Add _WIN32 to the #define.
* systemcall.C (startscript): remove trailing '/n' from request to * systemcall.C (startscript): remove trailing '/n' from request to
start the command in a minimized window under DOS. start the command in a minimized window under DOS.
@ -257,9 +281,9 @@
2003-03-06 Alfredo Braunstein <abraunst@libero.it> 2003-03-06 Alfredo Braunstein <abraunst@libero.it>
* forkedcontr.C (timer): reworked the loop to allow running changes * forkedcontr.C (timer): reworked the loop to allow running changes
on the list. on the list.
2003-02-27 Ling Li <ling@caltech.edu> 2003-02-27 Ling Li <ling@caltech.edu>
* lyxalgo.h (eliminate_duplicates): re-written to avoid the initial * lyxalgo.h (eliminate_duplicates): re-written to avoid the initial

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