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>
23 #include <boost/iterator/indirect_iterator.hpp>
39 #ifndef CXX_GLOBAL_CSTD
48 /* The forkedcall controller code handles finished child processes in a
51 1. It uses the SIGCHLD signal emitted by the system when the child process
52 finishes to reap the resulting zombie. The handler routine also
53 updates an internal list of completed children.
54 2. The signals associated with these completed children are then emitted
55 as part of the main LyX event loop.
57 The guiding philosophy is that zombies are a global resource that should
58 be reaped as soon as possible whereas an internal list of dead children
59 is not. Indeed, to emit the signals within the asynchronous handler
60 routine would result in unsafe code.
62 The signal handler is guaranteed to be safe even though it may not be
65 int completed_child_status;
66 sig_atomic_t completed_child_pid;
69 void child_handler(int)
71 // Clean up the child process.
72 completed_child_pid = wait(&completed_child_status);
75 (See the signals tutorial at http://tinyurl.com/3h82w.)
78 1. wait(2) is guaranteed to be async-safe.
79 2. child_handler handles only SIGCHLD signals so all subsequent
80 SIGCHLD signals are blocked from entering the handler until the
81 existing signal is processed.
83 This handler performs 'half' of the necessary clean up after a
84 completed child process. It prevents us leaving a stream of zombies
85 behind but does not go on to tell the main LyX program to finish the
86 clean-up by emitting the stored signal. That would most definitely
89 The only problem with the above is that the global stores
90 completed_child_status, completed_child_pid may be overwritten before
91 the clean-up is completed in the main loop.
93 However, the code in child_handler can be extended to fill an array of
94 completed processes. Everything remains safe so long as no 'unsafe'
95 functions are called. (See the list of async-safe functions at
96 http://tinyurl.com/3h82w.)
103 // This variable may need to be resized in the main program
104 // as and when a new process is forked. This resizing must be
105 // protected with sigprocmask
106 std::vector<child_data> reaped_children;
107 sig_atomic_t current_child = -1;
110 void child_handler(int)
112 child_data & store = reaped_children[++current_child];
113 // Clean up the child process.
114 store.pid = wait(&store.status);
117 That is, we build up a list of completed children in anticipation of
118 the main loop then looping over this list and invoking any associated
119 callbacks etc. The nice thing is that the main loop needs only to
120 check the value of 'current_child':
122 if (current_child != -1)
123 handleCompletedProcesses();
125 handleCompletedProcesses now loops over only those child processes
126 that have completed (ie, those stored in reaped_children). It blocks
127 any subsequent SIGCHLD signal whilst it does so:
129 // Used to block SIGCHLD signals.
130 sigset_t newMask, oldMask;
132 ForkedcallsController::ForkedcallsController()
134 reaped_children.resize(50);
135 signal(SIGCHLD, child_handler);
137 sigemptyset(&oldMask);
138 sigemptyset(&newMask);
139 sigaddset(&newMask, SIGCHLD);
142 void ForkedcallsController::handleCompletedProcesses()
144 if (current_child == -1)
147 // Block the SIGCHLD signal.
148 sigprocmask(SIG_BLOCK, &newMask, &oldMask);
150 for (int i = 0; i != 1+current_child; ++i) {
151 child_data & store = reaped_children[i];
152 // Go on to handle the child process
156 // Unblock the SIGCHLD signal and restore the old mask.
157 sigprocmask(SIG_SETMASK, &oldMask, 0);
160 VoilĂ ! An efficient, elegant and *safe* mechanism to handle child processes.
166 void child_handler(int)
168 ForkedcallsController & fcc = ForkedcallsController::get();
171 typedef vector<ForkedcallsController::Data>::size_type size_type;
172 if (size_type(fcc.current_child + 1) >= fcc.reaped_children.size())
175 ForkedcallsController::Data & store =
176 fcc.reaped_children[++fcc.current_child];
177 // Clean up the child process.
178 store.pid = wait(&store.status);
184 // Ensure, that only one controller exists inside process
185 ForkedcallsController & ForkedcallsController::get()
187 static ForkedcallsController singleton;
192 ForkedcallsController::ForkedcallsController()
193 : reaped_children(50), current_child(-1)
195 signal(SIGCHLD, child_handler);
197 sigemptyset(&oldMask);
198 sigemptyset(&newMask);
199 sigaddset(&newMask, SIGCHLD);
203 // open question: should we stop childs here?
204 // Asger says no: I like to have my xdvi open after closing LyX. Maybe
205 // I want to print or something.
206 ForkedcallsController::~ForkedcallsController()
208 signal(SIGCHLD, SIG_DFL);
212 void ForkedcallsController::addCall(ForkedProcess const & newcall)
214 forkedCalls.push_back(newcall.clone());
216 if (forkedCalls.size() > reaped_children.size()) {
217 // Block the SIGCHLD signal.
218 sigprocmask(SIG_BLOCK, &newMask, &oldMask);
220 reaped_children.resize(2*reaped_children.size());
222 // Unblock the SIGCHLD signal and restore the old mask.
223 sigprocmask(SIG_SETMASK, &oldMask, 0);
228 ForkedcallsController::iterator ForkedcallsController::find_pid(pid_t pid)
230 typedef boost::indirect_iterator<ListType::iterator> iterator;
232 iterator begin = boost::make_indirect_iterator(forkedCalls.begin());
233 iterator end = boost::make_indirect_iterator(forkedCalls.end());
234 iterator it = find_if(begin, end,
235 bind(equal_to<pid_t>(),
236 bind(&Forkedcall::pid, _1),
242 // Kill the process prematurely and remove it from the list
243 // within tolerance secs
244 void ForkedcallsController::kill(pid_t pid, int tolerance)
246 ListType::iterator it = find_pid(pid);
247 if (it == forkedCalls.end())
250 (*it)->kill(tolerance);
251 forkedCalls.erase(it);
255 // Check the list of dead children and emit any associated signals.
256 void ForkedcallsController::handleCompletedProcesses()
258 if (current_child == -1)
261 // Block the SIGCHLD signal.
262 sigprocmask(SIG_BLOCK, &newMask, &oldMask);
264 for (int i = 0; i != 1+current_child; ++i) {
265 Data & store = reaped_children[i];
267 if (store.pid == -1) {
268 // Might happen perfectly innocently, eg as a result
269 // of the system (3) call.
271 lyxerr << "LyX: Error waiting for child: "
272 << strerror(errno) << endl;
276 ListType::iterator it = find_pid(store.pid);
277 if (it == forkedCalls.end())
278 // Eg, child was run in blocking mode
281 ForkedProcess & child = *it->get();
282 bool remove_it = false;
284 if (WIFEXITED(store.status)) {
285 // Ok, the return value goes into retval.
286 child.setRetValue(WEXITSTATUS(store.status));
289 } else if (WIFSIGNALED(store.status)) {
290 // Child died, so pretend it returned 1
291 child.setRetValue(1);
294 } else if (WIFSTOPPED(store.status)) {
295 lyxerr << "LyX: Child (pid: " << store.pid
296 << ") stopped on signal "
297 << WSTOPSIG(store.status)
298 << ". Waiting for child to finish." << endl;
301 lyxerr << "LyX: Something rotten happened while "
302 "waiting for child " << store.pid << endl;
304 // Child died, so pretend it returned 1
305 child.setRetValue(1);
311 forkedCalls.erase(it);
318 // Unblock the SIGCHLD signal and restore the old mask.
319 sigprocmask(SIG_SETMASK, &oldMask, 0);
322 } // namespace support