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 "forkedcontr.h"
18 #include "forkedcall.h"
19 #include "lyxfunctional.h"
23 #include <boost/iterator/indirect_iterator.hpp>
36 #ifndef CXX_GLOBAL_CSTD
45 /* The forkedcall controller code handles finished child processes in a
48 1. It uses the SIGCHLD signal emitted by the system when the child process
49 finishes to reap the resulting zombie. The handler routine also
50 updates an internal list of completed children.
51 2. The signals associated with these completed children are then emitted
52 as part of the main LyX event loop.
54 The guiding philosophy is that zombies are a global resource that should
55 be reaped as soon as possible whereas an internal list of dead children
56 is not. Indeed, to emit the signals within the asynchronous handler
57 routine would result in unsafe code.
59 The signal handler is guaranteed to be safe even though it may not be
62 int completed_child_status;
63 sig_atomic_t completed_child_pid;
66 void child_handler(int)
68 // Clean up the child process.
69 completed_child_pid = wait(&completed_child_status);
72 (See the signals tutorial at http://tinyurl.com/3h82w.)
75 1. wait(2) is guaranteed to be async-safe.
76 2. child_handler handles only SIGCHLD signals so all subsequent
77 SIGCHLD signals are blocked from entering the handler until the
78 existing signal is processed.
80 This handler performs 'half' of the necessary clean up after a
81 completed child process. It prevents us leaving a stream of zombies
82 behind but does not go on to tell the main LyX program to finish the
83 clean-up by emitting the stored signal. That would most definitely
86 The only problem with the above is that the global stores
87 completed_child_status, completed_child_pid may be overwritten before
88 the clean-up is completed in the main loop.
90 However, the code in child_handler can be extended to fill an array of
91 completed processes. Everything remains safe so long as no 'unsafe'
92 functions are called. (See the list of async-safe functions at
93 http://tinyurl.com/3h82w.)
100 // This variable may need to be resized in the main program
101 // as and when a new process is forked. This resizing must be
102 // protected with sigprocmask
103 std::vector<child_data> reaped_children;
104 sig_atomic_t current_child = -1;
107 void child_handler(int)
109 child_data & store = reaped_children[++current_child];
110 // Clean up the child process.
111 store.pid = wait(&store.status);
114 That is, we build up a list of completed children in anticipation of
115 the main loop then looping over this list and invoking any associated
116 callbacks etc. The nice thing is that the main loop needs only to
117 check the value of 'current_child':
119 if (current_child != -1)
120 handleCompletedProcesses();
122 handleCompletedProcesses now loops over only those child processes
123 that have completed (ie, those stored in reaped_children). It blocks
124 any subsequent SIGCHLD signal whilst it does so:
126 // Used to block SIGCHLD signals.
127 sigset_t newMask, oldMask;
129 ForkedcallsController::ForkedcallsController()
131 reaped_children.resize(50);
132 signal(SIGCHLD, child_handler);
134 sigemptyset(&oldMask);
135 sigemptyset(&newMask);
136 sigaddset(&newMask, SIGCHLD);
139 void ForkedcallsController::handleCompletedProcesses()
141 if (current_child == -1)
144 // Block the SIGCHLD signal.
145 sigprocmask(SIG_BLOCK, &newMask, &oldMask);
147 for (int i = 0; i != 1+current_child; ++i) {
148 child_data & store = reaped_children[i];
149 // Go on to handle the child process
153 // Unblock the SIGCHLD signal and restore the old mask.
154 sigprocmask(SIG_SETMASK, &oldMask, 0);
157 VoilĂ ! An efficient, elegant and *safe* mechanism to handle child processes.
163 void child_handler(int)
165 ForkedcallsController & fcc = ForkedcallsController::get();
166 ForkedcallsController::Data & store =
167 fcc.reaped_children[++fcc.current_child];
168 // Clean up the child process.
169 store.pid = wait(&store.status);
175 // Ensure, that only one controller exists inside process
176 ForkedcallsController & ForkedcallsController::get()
178 static ForkedcallsController singleton;
183 ForkedcallsController::ForkedcallsController()
184 : reaped_children(50), current_child(-1)
186 signal(SIGCHLD, child_handler);
188 sigemptyset(&oldMask);
189 sigemptyset(&newMask);
190 sigaddset(&newMask, SIGCHLD);
194 // open question: should we stop childs here?
195 // Asger says no: I like to have my xdvi open after closing LyX. Maybe
196 // I want to print or something.
197 ForkedcallsController::~ForkedcallsController()
199 signal(SIGCHLD, SIG_DFL);
203 void ForkedcallsController::addCall(ForkedProcess const & newcall)
205 forkedCalls.push_back(newcall.clone());
207 if (forkedCalls.size() > reaped_children.size()) {
208 // Block the SIGCHLD signal.
209 sigprocmask(SIG_BLOCK, &newMask, &oldMask);
211 reaped_children.resize(2*reaped_children.size());
213 // Unblock the SIGCHLD signal and restore the old mask.
214 sigprocmask(SIG_SETMASK, &oldMask, 0);
219 ForkedcallsController::iterator ForkedcallsController::find_pid(pid_t pid)
221 typedef boost::indirect_iterator<ListType::iterator> iterator;
223 iterator begin = boost::make_indirect_iterator(forkedCalls.begin());
224 iterator end = boost::make_indirect_iterator(forkedCalls.end());
225 iterator it = find_if(begin, end,
226 lyx::compare_memfun(&Forkedcall::pid, pid));
232 // Kill the process prematurely and remove it from the list
233 // within tolerance secs
234 void ForkedcallsController::kill(pid_t pid, int tolerance)
236 ListType::iterator it = find_pid(pid);
237 if (it == forkedCalls.end())
240 (*it)->kill(tolerance);
241 forkedCalls.erase(it);
245 // Check the list of dead children and emit any associated signals.
246 void ForkedcallsController::handleCompletedProcesses()
248 if (current_child == -1)
251 // Block the SIGCHLD signal.
252 sigprocmask(SIG_BLOCK, &newMask, &oldMask);
254 for (int i = 0; i != 1+current_child; ++i) {
255 Data & store = reaped_children[i];
257 if (store.pid == -1) {
258 lyxerr << "LyX: Error waiting for child: "
259 << strerror(errno) << endl;
263 ListType::iterator it = find_pid(store.pid);
264 BOOST_ASSERT(it != forkedCalls.end());
266 ForkedProcess & child = *it->get();
267 bool remove_it = false;
269 if (WIFEXITED(store.status)) {
270 // Ok, the return value goes into retval.
271 child.setRetValue(WEXITSTATUS(store.status));
274 } else if (WIFSIGNALED(store.status)) {
275 // Child died, so pretend it returned 1
276 child.setRetValue(1);
279 } else if (WIFSTOPPED(store.status)) {
280 lyxerr << "LyX: Child (pid: " << store.pid
281 << ") stopped on signal "
282 << WSTOPSIG(store.status)
283 << ". Waiting for child to finish." << endl;
286 lyxerr << "LyX: Something rotten happened while "
287 "waiting for child " << store.pid << endl;
289 // Child died, so pretend it returned 1
290 child.setRetValue(1);
296 forkedCalls.erase(it);
303 // Unblock the SIGCHLD signal and restore the old mask.
304 sigprocmask(SIG_SETMASK, &oldMask, 0);
307 } // namespace support