2002-02-27 09:59:52 +00:00
|
|
|
|
/**
|
|
|
|
|
* \file forkedcontr.C
|
2002-09-25 10:03:41 +00:00
|
|
|
|
* This file is part of LyX, the document processor.
|
|
|
|
|
* Licence details can be found in the file COPYING.
|
2002-02-27 09:59:52 +00:00
|
|
|
|
*
|
|
|
|
|
* \author Asger Alstrup Nielsen
|
|
|
|
|
* \author Angus Leeming
|
|
|
|
|
*
|
2003-08-23 00:17:00 +00:00
|
|
|
|
* Full author contact details are available in file CREDITS.
|
2002-09-25 10:03:41 +00:00
|
|
|
|
*
|
2002-03-21 17:09:55 +00:00
|
|
|
|
* A class for the control of child processes launched using
|
2002-02-27 09:59:52 +00:00
|
|
|
|
* fork() and execvp().
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
2004-11-07 13:22:51 +00:00
|
|
|
|
#include "support/forkedcontr.h"
|
|
|
|
|
#include "support/forkedcall.h"
|
2002-02-27 09:59:52 +00:00
|
|
|
|
|
2004-03-24 17:38:54 +00:00
|
|
|
|
#include "debug.h"
|
2002-05-29 16:21:03 +00:00
|
|
|
|
|
2004-11-06 16:14:22 +00:00
|
|
|
|
#include <boost/bind.hpp>
|
2002-05-29 16:21:03 +00:00
|
|
|
|
|
2002-02-27 09:59:52 +00:00
|
|
|
|
#include <cerrno>
|
|
|
|
|
#include <cstdlib>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
|
2004-11-06 16:14:22 +00:00
|
|
|
|
using boost::bind;
|
|
|
|
|
|
2002-02-27 09:59:52 +00:00
|
|
|
|
using std::endl;
|
2004-11-06 16:14:22 +00:00
|
|
|
|
using std::equal_to;
|
2002-02-27 09:59:52 +00:00
|
|
|
|
using std::find_if;
|
2004-03-24 17:38:54 +00:00
|
|
|
|
|
2003-10-06 15:43:21 +00:00
|
|
|
|
using std::string;
|
2004-03-24 17:38:54 +00:00
|
|
|
|
using std::vector;
|
2003-09-08 00:33:41 +00:00
|
|
|
|
|
2002-02-27 09:59:52 +00:00
|
|
|
|
#ifndef CXX_GLOBAL_CSTD
|
2004-03-24 17:38:54 +00:00
|
|
|
|
using std::signal;
|
2002-02-27 09:59:52 +00:00
|
|
|
|
using std::strerror;
|
|
|
|
|
#endif
|
|
|
|
|
|
2003-09-08 00:33:41 +00:00
|
|
|
|
|
2003-06-30 23:56:22 +00:00
|
|
|
|
namespace lyx {
|
|
|
|
|
namespace support {
|
|
|
|
|
|
2004-03-24 17:38:54 +00:00
|
|
|
|
/* The forkedcall controller code handles finished child processes in a
|
|
|
|
|
two-stage process.
|
|
|
|
|
|
|
|
|
|
1. It uses the SIGCHLD signal emitted by the system when the child process
|
|
|
|
|
finishes to reap the resulting zombie. The handler routine also
|
|
|
|
|
updates an internal list of completed children.
|
|
|
|
|
2. The signals associated with these completed children are then emitted
|
|
|
|
|
as part of the main LyX event loop.
|
|
|
|
|
|
|
|
|
|
The guiding philosophy is that zombies are a global resource that should
|
|
|
|
|
be reaped as soon as possible whereas an internal list of dead children
|
|
|
|
|
is not. Indeed, to emit the signals within the asynchronous handler
|
|
|
|
|
routine would result in unsafe code.
|
|
|
|
|
|
|
|
|
|
The signal handler is guaranteed to be safe even though it may not be
|
|
|
|
|
atomic:
|
|
|
|
|
|
|
|
|
|
int completed_child_status;
|
|
|
|
|
sig_atomic_t completed_child_pid;
|
|
|
|
|
|
|
|
|
|
extern "C"
|
|
|
|
|
void child_handler(int)
|
|
|
|
|
{
|
|
|
|
|
// Clean up the child process.
|
|
|
|
|
completed_child_pid = wait(&completed_child_status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(See the signals tutorial at http://tinyurl.com/3h82w.)
|
|
|
|
|
|
|
|
|
|
It's safe because:
|
|
|
|
|
1. wait(2) is guaranteed to be async-safe.
|
|
|
|
|
2. child_handler handles only SIGCHLD signals so all subsequent
|
|
|
|
|
SIGCHLD signals are blocked from entering the handler until the
|
|
|
|
|
existing signal is processed.
|
|
|
|
|
|
|
|
|
|
This handler performs 'half' of the necessary clean up after a
|
|
|
|
|
completed child process. It prevents us leaving a stream of zombies
|
|
|
|
|
behind but does not go on to tell the main LyX program to finish the
|
|
|
|
|
clean-up by emitting the stored signal. That would most definitely
|
|
|
|
|
not be safe.
|
|
|
|
|
|
|
|
|
|
The only problem with the above is that the global stores
|
|
|
|
|
completed_child_status, completed_child_pid may be overwritten before
|
|
|
|
|
the clean-up is completed in the main loop.
|
|
|
|
|
|
|
|
|
|
However, the code in child_handler can be extended to fill an array of
|
|
|
|
|
completed processes. Everything remains safe so long as no 'unsafe'
|
|
|
|
|
functions are called. (See the list of async-safe functions at
|
|
|
|
|
http://tinyurl.com/3h82w.)
|
|
|
|
|
|
|
|
|
|
struct child_data {
|
|
|
|
|
pid_t pid;
|
|
|
|
|
int status;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// This variable may need to be resized in the main program
|
|
|
|
|
// as and when a new process is forked. This resizing must be
|
|
|
|
|
// protected with sigprocmask
|
|
|
|
|
std::vector<child_data> reaped_children;
|
|
|
|
|
sig_atomic_t current_child = -1;
|
|
|
|
|
|
|
|
|
|
extern "C"
|
|
|
|
|
void child_handler(int)
|
|
|
|
|
{
|
|
|
|
|
child_data & store = reaped_children[++current_child];
|
|
|
|
|
// Clean up the child process.
|
|
|
|
|
store.pid = wait(&store.status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
That is, we build up a list of completed children in anticipation of
|
|
|
|
|
the main loop then looping over this list and invoking any associated
|
|
|
|
|
callbacks etc. The nice thing is that the main loop needs only to
|
|
|
|
|
check the value of 'current_child':
|
|
|
|
|
|
|
|
|
|
if (current_child != -1)
|
|
|
|
|
handleCompletedProcesses();
|
|
|
|
|
|
|
|
|
|
handleCompletedProcesses now loops over only those child processes
|
|
|
|
|
that have completed (ie, those stored in reaped_children). It blocks
|
|
|
|
|
any subsequent SIGCHLD signal whilst it does so:
|
|
|
|
|
|
|
|
|
|
// Used to block SIGCHLD signals.
|
|
|
|
|
sigset_t newMask, oldMask;
|
|
|
|
|
|
|
|
|
|
ForkedcallsController::ForkedcallsController()
|
|
|
|
|
{
|
|
|
|
|
reaped_children.resize(50);
|
|
|
|
|
signal(SIGCHLD, child_handler);
|
|
|
|
|
|
|
|
|
|
sigemptyset(&oldMask);
|
|
|
|
|
sigemptyset(&newMask);
|
|
|
|
|
sigaddset(&newMask, SIGCHLD);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ForkedcallsController::handleCompletedProcesses()
|
|
|
|
|
{
|
|
|
|
|
if (current_child == -1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Block the SIGCHLD signal.
|
|
|
|
|
sigprocmask(SIG_BLOCK, &newMask, &oldMask);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i != 1+current_child; ++i) {
|
|
|
|
|
child_data & store = reaped_children[i];
|
|
|
|
|
// Go on to handle the child process
|
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unblock the SIGCHLD signal and restore the old mask.
|
|
|
|
|
sigprocmask(SIG_SETMASK, &oldMask, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Voil<EFBFBD>! An efficient, elegant and *safe* mechanism to handle child processes.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
extern "C"
|
|
|
|
|
void child_handler(int)
|
|
|
|
|
{
|
|
|
|
|
ForkedcallsController & fcc = ForkedcallsController::get();
|
2004-03-26 23:55:33 +00:00
|
|
|
|
|
|
|
|
|
// Be safe
|
2004-03-27 18:50:49 +00:00
|
|
|
|
typedef vector<ForkedcallsController::Data>::size_type size_type;
|
|
|
|
|
if (size_type(fcc.current_child + 1) >= fcc.reaped_children.size())
|
2004-03-26 23:55:33 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2004-03-24 17:38:54 +00:00
|
|
|
|
ForkedcallsController::Data & store =
|
|
|
|
|
fcc.reaped_children[++fcc.current_child];
|
|
|
|
|
// Clean up the child process.
|
|
|
|
|
store.pid = wait(&store.status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace anon
|
|
|
|
|
|
|
|
|
|
|
2002-02-27 09:59:52 +00:00
|
|
|
|
// Ensure, that only one controller exists inside process
|
|
|
|
|
ForkedcallsController & ForkedcallsController::get()
|
|
|
|
|
{
|
|
|
|
|
static ForkedcallsController singleton;
|
|
|
|
|
return singleton;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ForkedcallsController::ForkedcallsController()
|
2004-03-24 17:38:54 +00:00
|
|
|
|
: reaped_children(50), current_child(-1)
|
2002-02-27 09:59:52 +00:00
|
|
|
|
{
|
2004-03-24 17:38:54 +00:00
|
|
|
|
signal(SIGCHLD, child_handler);
|
2002-03-21 17:09:55 +00:00
|
|
|
|
|
2004-03-24 17:38:54 +00:00
|
|
|
|
sigemptyset(&oldMask);
|
|
|
|
|
sigemptyset(&newMask);
|
|
|
|
|
sigaddset(&newMask, SIGCHLD);
|
2002-02-27 09:59:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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()
|
|
|
|
|
{
|
2004-03-24 17:38:54 +00:00
|
|
|
|
signal(SIGCHLD, SIG_DFL);
|
2002-02-27 09:59:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2002-10-31 12:42:26 +00:00
|
|
|
|
void ForkedcallsController::addCall(ForkedProcess const & newcall)
|
2002-02-27 09:59:52 +00:00
|
|
|
|
{
|
2004-03-23 14:39:41 +00:00
|
|
|
|
forkedCalls.push_back(newcall.clone());
|
2004-03-24 17:38:54 +00:00
|
|
|
|
|
|
|
|
|
if (forkedCalls.size() > reaped_children.size()) {
|
|
|
|
|
// Block the SIGCHLD signal.
|
|
|
|
|
sigprocmask(SIG_BLOCK, &newMask, &oldMask);
|
|
|
|
|
|
|
|
|
|
reaped_children.resize(2*reaped_children.size());
|
|
|
|
|
|
|
|
|
|
// Unblock the SIGCHLD signal and restore the old mask.
|
|
|
|
|
sigprocmask(SIG_SETMASK, &oldMask, 0);
|
|
|
|
|
}
|
2002-02-27 09:59:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2004-11-16 20:41:38 +00:00
|
|
|
|
ForkedcallsController::iterator
|
|
|
|
|
ForkedcallsController::find_pid(pid_t pid)
|
2002-02-27 09:59:52 +00:00
|
|
|
|
{
|
2004-11-16 20:41:38 +00:00
|
|
|
|
return find_if(forkedCalls.begin(), forkedCalls.end(),
|
|
|
|
|
bind(equal_to<pid_t>(),
|
|
|
|
|
bind(&Forkedcall::pid, _1),
|
|
|
|
|
pid));
|
2004-03-24 17:38:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Kill the process prematurely and remove it from the list
|
|
|
|
|
// within tolerance secs
|
|
|
|
|
void ForkedcallsController::kill(pid_t pid, int tolerance)
|
|
|
|
|
{
|
|
|
|
|
ListType::iterator it = find_pid(pid);
|
|
|
|
|
if (it == forkedCalls.end())
|
|
|
|
|
return;
|
2002-02-27 09:59:52 +00:00
|
|
|
|
|
2004-03-24 17:38:54 +00:00
|
|
|
|
(*it)->kill(tolerance);
|
|
|
|
|
forkedCalls.erase(it);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check the list of dead children and emit any associated signals.
|
|
|
|
|
void ForkedcallsController::handleCompletedProcesses()
|
|
|
|
|
{
|
|
|
|
|
if (current_child == -1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Block the SIGCHLD signal.
|
|
|
|
|
sigprocmask(SIG_BLOCK, &newMask, &oldMask);
|
|
|
|
|
|
2004-11-16 20:41:38 +00:00
|
|
|
|
for (int i = 0; i != 1 + current_child; ++i) {
|
2004-03-24 17:38:54 +00:00
|
|
|
|
Data & store = reaped_children[i];
|
|
|
|
|
|
|
|
|
|
if (store.pid == -1) {
|
2004-03-27 18:50:49 +00:00
|
|
|
|
// Might happen perfectly innocently, eg as a result
|
|
|
|
|
// of the system (3) call.
|
|
|
|
|
if (errno)
|
|
|
|
|
lyxerr << "LyX: Error waiting for child: "
|
|
|
|
|
<< strerror(errno) << endl;
|
2004-03-24 17:38:54 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2002-02-27 09:59:52 +00:00
|
|
|
|
|
2004-03-24 17:38:54 +00:00
|
|
|
|
ListType::iterator it = find_pid(store.pid);
|
2004-03-26 23:55:33 +00:00
|
|
|
|
if (it == forkedCalls.end())
|
|
|
|
|
// Eg, child was run in blocking mode
|
|
|
|
|
continue;
|
2002-02-27 09:59:52 +00:00
|
|
|
|
|
2004-11-16 20:41:38 +00:00
|
|
|
|
ListType::value_type child = (*it);
|
2004-03-24 17:38:54 +00:00
|
|
|
|
bool remove_it = false;
|
2002-02-27 09:59:52 +00:00
|
|
|
|
|
2004-03-24 17:38:54 +00:00
|
|
|
|
if (WIFEXITED(store.status)) {
|
2002-02-27 09:59:52 +00:00
|
|
|
|
// Ok, the return value goes into retval.
|
2004-11-16 20:41:38 +00:00
|
|
|
|
child->setRetValue(WEXITSTATUS(store.status));
|
2002-02-27 09:59:52 +00:00
|
|
|
|
remove_it = true;
|
|
|
|
|
|
2004-03-24 17:38:54 +00:00
|
|
|
|
} else if (WIFSIGNALED(store.status)) {
|
2002-02-27 09:59:52 +00:00
|
|
|
|
// Child died, so pretend it returned 1
|
2004-11-16 20:41:38 +00:00
|
|
|
|
child->setRetValue(1);
|
2002-02-27 09:59:52 +00:00
|
|
|
|
remove_it = true;
|
|
|
|
|
|
2004-03-24 17:38:54 +00:00
|
|
|
|
} else if (WIFSTOPPED(store.status)) {
|
|
|
|
|
lyxerr << "LyX: Child (pid: " << store.pid
|
2002-02-27 09:59:52 +00:00
|
|
|
|
<< ") stopped on signal "
|
2004-03-24 17:38:54 +00:00
|
|
|
|
<< WSTOPSIG(store.status)
|
2002-02-27 09:59:52 +00:00
|
|
|
|
<< ". Waiting for child to finish." << endl;
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
lyxerr << "LyX: Something rotten happened while "
|
2004-11-16 20:41:38 +00:00
|
|
|
|
<< "waiting for child " << store.pid << endl;
|
2002-02-27 09:59:52 +00:00
|
|
|
|
|
|
|
|
|
// Child died, so pretend it returned 1
|
2004-11-16 20:41:38 +00:00
|
|
|
|
child->setRetValue(1);
|
2002-02-27 09:59:52 +00:00
|
|
|
|
remove_it = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (remove_it) {
|
2004-11-16 20:41:38 +00:00
|
|
|
|
child->emitSignal();
|
2004-03-23 14:39:41 +00:00
|
|
|
|
forkedCalls.erase(it);
|
2002-02-27 09:59:52 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2004-03-24 17:38:54 +00:00
|
|
|
|
// Reset the counter
|
|
|
|
|
current_child = -1;
|
2002-02-27 09:59:52 +00:00
|
|
|
|
|
2004-03-24 17:38:54 +00:00
|
|
|
|
// Unblock the SIGCHLD signal and restore the old mask.
|
|
|
|
|
sigprocmask(SIG_SETMASK, &oldMask, 0);
|
2002-02-27 09:59:52 +00:00
|
|
|
|
}
|
2003-06-30 23:56:22 +00:00
|
|
|
|
|
|
|
|
|
} // namespace support
|
|
|
|
|
} // namespace lyx
|