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

View File

@ -30,19 +30,27 @@
#include "lyxlib.h"
#include "filetools.h"
#include "os.h"
#include "debug.h"
#include "frontends/Timeout.h"
#include <boost/bind.hpp>
#include <vector>
#include <cerrno>
#include <sys/types.h>
#include <sys/wait.h>
#include <csignal>
#include <cstdlib>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#ifdef _WIN32
# define SIGHUP 1
# define SIGKILL 9
# include <process.h>
# include <windows.h>
#else
# include <cerrno>
# include <csignal>
# include <cstdlib>
# include <unistd.h>
# include <sys/types.h>
# include <sys/wait.h>
#endif
using std::endl;
@ -153,10 +161,12 @@ bool ForkedProcess::running() const
if (!pid())
return false;
#if !defined (_WIN32)
// Un-UNIX like, but we don't have much use for
// knowing if a zombie exists, so just reap it first.
int waitstatus;
waitpid(pid(), &waitstatus, WNOHANG);
#endif
// Racy of course, but it will do.
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.
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;
bool wait = true;
while (wait) {
@ -224,6 +257,7 @@ int ForkedProcess::waitForChild()
wait = false;
}
}
#endif
return retval_;
}
@ -327,7 +361,12 @@ int Forkedcall::generateChild()
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();
if (cpid == 0) {
// Child
@ -338,9 +377,6 @@ int Forkedcall::generateChild()
<< strerror(errno) << endl;
_exit(1);
}
#else
pid_t const cpid = spawnvp(P_SESSION|P_DEFAULT|P_MINIMIZE|P_BACKGROUND,
argv[0], &*argv.begin());
#endif
if (cpid < 0) {

View File

@ -19,25 +19,59 @@
#include "lyxfunctional.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 <boost/bind.hpp>
#include <cerrno>
#include <cstdlib>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/wait.h>
using std::vector;
using std::endl;
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
// Ensure, that only one controller exists inside process
ForkedcallsController & ForkedcallsController::get()
{
@ -75,7 +109,6 @@ void ForkedcallsController::addCall(ForkedProcess const & newcall)
timeout_->start();
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.
void ForkedcallsController::timer()
{
ListType::size_type start_size = forkedCalls.size();
ListType::iterator it = forkedCalls.begin();
ListType::iterator end = forkedCalls.end();
while (it != end) {
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();
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: "
@ -130,6 +191,7 @@ void ForkedcallsController::timer()
actCall->setRetValue(1);
remove_it = true;
}
#endif
if (remove_it) {
forkedCalls.erase(it);
@ -149,44 +211,6 @@ void ForkedcallsController::timer()
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());
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 <boost/signals/signal0.hpp>
#include <boost/signals/trackable.hpp>
#include <sys/types.h> // needed for pid_t
#include <list>
@ -29,7 +26,7 @@
class ForkedProcess;
class Timeout;
class ForkedcallsController : public boost::signals::trackable {
class ForkedcallsController {
public:
/// We need this to avoid warnings.
ForkedcallsController();
@ -51,21 +48,12 @@ public:
*/
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.
* The process is killed within tolerance secs.
* See forkedcall.[Ch] for details.
*/
void kill(pid_t, int tolerance = 5);
/// Signal emitted when the list of current child processes changes.
boost::signal0<void> childrenChanged;
private:
///
ForkedcallsController(ForkedcallsController const &);
@ -81,4 +69,9 @@ private:
Timeout * timeout_;
};
#if defined(_WIN32)
// a wrapper for GetLastError() and FormatMessage().
string const getChildErrorMessage();
#endif
#endif // FORKEDCONTR_H

View File

@ -5,7 +5,40 @@
#include <sys/types.h>
#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)
{
#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);
#endif
}