3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup Nielsen
7 * \author Angus Leeming
9 * Full author contact details are available in file CREDITS.
11 * A class for the control of child processes launched using
12 * fork() and execvp().
17 #include "support/forkedcontr.h"
18 #include "support/forkedcall.h"
22 #include <boost/bind.hpp>
40 #ifndef CXX_GLOBAL_CSTD
49 /* The forkedcall controller code handles finished child processes in a
52 1. It uses the SIGCHLD signal emitted by the system when the child process
53 finishes to reap the resulting zombie. The handler routine also
54 updates an internal list of completed children.
55 2. The signals associated with these completed children are then emitted
56 as part of the main LyX event loop.
58 The guiding philosophy is that zombies are a global resource that should
59 be reaped as soon as possible whereas an internal list of dead children
60 is not. Indeed, to emit the signals within the asynchronous handler
61 routine would result in unsafe code.
63 The signal handler is guaranteed to be safe even though it may not be
66 int completed_child_status;
67 sig_atomic_t completed_child_pid;
70 void child_handler(int)
72 // Clean up the child process.
73 completed_child_pid = wait(&completed_child_status);
76 (See the signals tutorial at http://tinyurl.com/3h82w.)
79 1. wait(2) is guaranteed to be async-safe.
80 2. child_handler handles only SIGCHLD signals so all subsequent
81 SIGCHLD signals are blocked from entering the handler until the
82 existing signal is processed.
84 This handler performs 'half' of the necessary clean up after a
85 completed child process. It prevents us leaving a stream of zombies
86 behind but does not go on to tell the main LyX program to finish the
87 clean-up by emitting the stored signal. That would most definitely
90 The only problem with the above is that the global stores
91 completed_child_status, completed_child_pid may be overwritten before
92 the clean-up is completed in the main loop.
94 However, the code in child_handler can be extended to fill an array of
95 completed processes. Everything remains safe so long as no 'unsafe'
96 functions are called. (See the list of async-safe functions at
97 http://tinyurl.com/3h82w.)
104 // This variable may need to be resized in the main program
105 // as and when a new process is forked. This resizing must be
106 // protected with sigprocmask
107 std::vector<child_data> reaped_children;
108 sig_atomic_t current_child = -1;
111 void child_handler(int)
113 child_data & store = reaped_children[++current_child];
114 // Clean up the child process.
115 store.pid = wait(&store.status);
118 That is, we build up a list of completed children in anticipation of
119 the main loop then looping over this list and invoking any associated
120 callbacks etc. The nice thing is that the main loop needs only to
121 check the value of 'current_child':
123 if (current_child != -1)
124 handleCompletedProcesses();
126 handleCompletedProcesses now loops over only those child processes
127 that have completed (ie, those stored in reaped_children). It blocks
128 any subsequent SIGCHLD signal whilst it does so:
130 // Used to block SIGCHLD signals.
131 sigset_t newMask, oldMask;
133 ForkedcallsController::ForkedcallsController()
135 reaped_children.resize(50);
136 signal(SIGCHLD, child_handler);
138 sigemptyset(&oldMask);
139 sigemptyset(&newMask);
140 sigaddset(&newMask, SIGCHLD);
143 void ForkedcallsController::handleCompletedProcesses()
145 if (current_child == -1)
148 // Block the SIGCHLD signal.
149 sigprocmask(SIG_BLOCK, &newMask, &oldMask);
151 for (int i = 0; i != 1+current_child; ++i) {
152 child_data & store = reaped_children[i];
153 // Go on to handle the child process
157 // Unblock the SIGCHLD signal and restore the old mask.
158 sigprocmask(SIG_SETMASK, &oldMask, 0);
161 VoilĂ ! An efficient, elegant and *safe* mechanism to handle child processes.
167 void child_handler(int)
169 ForkedcallsController & fcc = ForkedcallsController::get();
172 typedef vector<ForkedcallsController::Data>::size_type size_type;
173 if (size_type(fcc.current_child + 1) >= fcc.reaped_children.size())
176 ForkedcallsController::Data & store =
177 fcc.reaped_children[++fcc.current_child];
178 // Clean up the child process.
179 store.pid = wait(&store.status);
185 // Ensure, that only one controller exists inside process
186 ForkedcallsController & ForkedcallsController::get()
188 static ForkedcallsController singleton;
193 ForkedcallsController::ForkedcallsController()
194 : reaped_children(50), current_child(-1)
196 signal(SIGCHLD, child_handler);
198 sigemptyset(&oldMask);
199 sigemptyset(&newMask);
200 sigaddset(&newMask, SIGCHLD);
204 // open question: should we stop childs here?
205 // Asger says no: I like to have my xdvi open after closing LyX. Maybe
206 // I want to print or something.
207 ForkedcallsController::~ForkedcallsController()
209 signal(SIGCHLD, SIG_DFL);
213 void ForkedcallsController::addCall(ForkedProcess const & newcall)
215 forkedCalls.push_back(newcall.clone());
217 if (forkedCalls.size() > reaped_children.size()) {
218 // Block the SIGCHLD signal.
219 sigprocmask(SIG_BLOCK, &newMask, &oldMask);
221 reaped_children.resize(2*reaped_children.size());
223 // Unblock the SIGCHLD signal and restore the old mask.
224 sigprocmask(SIG_SETMASK, &oldMask, 0);
229 ForkedcallsController::iterator
230 ForkedcallsController::find_pid(pid_t pid)
232 return find_if(forkedCalls.begin(), forkedCalls.end(),
233 bind(equal_to<pid_t>(),
234 bind(&Forkedcall::pid, _1),
239 // Kill the process prematurely and remove it from the list
240 // within tolerance secs
241 void ForkedcallsController::kill(pid_t pid, int tolerance)
243 ListType::iterator it = find_pid(pid);
244 if (it == forkedCalls.end())
247 (*it)->kill(tolerance);
248 forkedCalls.erase(it);
252 // Check the list of dead children and emit any associated signals.
253 void ForkedcallsController::handleCompletedProcesses()
255 if (current_child == -1)
258 // Block the SIGCHLD signal.
259 sigprocmask(SIG_BLOCK, &newMask, &oldMask);
261 for (int i = 0; i != 1 + current_child; ++i) {
262 Data & store = reaped_children[i];
264 if (store.pid == -1) {
265 // Might happen perfectly innocently, eg as a result
266 // of the system (3) call.
268 lyxerr << "LyX: Error waiting for child: "
269 << strerror(errno) << endl;
273 ListType::iterator it = find_pid(store.pid);
274 if (it == forkedCalls.end())
275 // Eg, child was run in blocking mode
278 ListType::value_type child = (*it);
279 bool remove_it = false;
281 if (WIFEXITED(store.status)) {
282 // Ok, the return value goes into retval.
283 child->setRetValue(WEXITSTATUS(store.status));
286 } else if (WIFSIGNALED(store.status)) {
287 // Child died, so pretend it returned 1
288 child->setRetValue(1);
291 } else if (WIFSTOPPED(store.status)) {
292 lyxerr << "LyX: Child (pid: " << store.pid
293 << ") stopped on signal "
294 << WSTOPSIG(store.status)
295 << ". Waiting for child to finish." << endl;
298 lyxerr << "LyX: Something rotten happened while "
299 << "waiting for child " << store.pid << endl;
301 // Child died, so pretend it returned 1
302 child->setRetValue(1);
308 forkedCalls.erase(it);
315 // Unblock the SIGCHLD signal and restore the old mask.
316 sigprocmask(SIG_SETMASK, &oldMask, 0);
319 } // namespace support