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>
38 #ifndef CXX_GLOBAL_CSTD
47 /* The forkedcall controller code handles finished child processes in a
50 1. It uses the SIGCHLD signal emitted by the system when the child process
51 finishes to reap the resulting zombie. The handler routine also
52 updates an internal list of completed children.
53 2. The signals associated with these completed children are then emitted
54 as part of the main LyX event loop.
56 The guiding philosophy is that zombies are a global resource that should
57 be reaped as soon as possible whereas an internal list of dead children
58 is not. Indeed, to emit the signals within the asynchronous handler
59 routine would result in unsafe code.
61 The signal handler is guaranteed to be safe even though it may not be
64 int completed_child_status;
65 sig_atomic_t completed_child_pid;
68 void child_handler(int)
70 // Clean up the child process.
71 completed_child_pid = wait(&completed_child_status);
74 (See the signals tutorial at http://tinyurl.com/3h82w.)
77 1. wait(2) is guaranteed to be async-safe.
78 2. child_handler handles only SIGCHLD signals so all subsequent
79 SIGCHLD signals are blocked from entering the handler until the
80 existing signal is processed.
82 This handler performs 'half' of the necessary clean up after a
83 completed child process. It prevents us leaving a stream of zombies
84 behind but does not go on to tell the main LyX program to finish the
85 clean-up by emitting the stored signal. That would most definitely
88 The only problem with the above is that the global stores
89 completed_child_status, completed_child_pid may be overwritten before
90 the clean-up is completed in the main loop.
92 However, the code in child_handler can be extended to fill an array of
93 completed processes. Everything remains safe so long as no 'unsafe'
94 functions are called. (See the list of async-safe functions at
95 http://tinyurl.com/3h82w.)
102 // This variable may need to be resized in the main program
103 // as and when a new process is forked. This resizing must be
104 // protected with sigprocmask
105 std::vector<child_data> reaped_children;
106 sig_atomic_t current_child = -1;
109 void child_handler(int)
111 child_data & store = reaped_children[++current_child];
112 // Clean up the child process.
113 store.pid = wait(&store.status);
116 That is, we build up a list of completed children in anticipation of
117 the main loop then looping over this list and invoking any associated
118 callbacks etc. The nice thing is that the main loop needs only to
119 check the value of 'current_child':
121 if (current_child != -1)
122 handleCompletedProcesses();
124 handleCompletedProcesses now loops over only those child processes
125 that have completed (ie, those stored in reaped_children). It blocks
126 any subsequent SIGCHLD signal whilst it does so:
128 // Used to block SIGCHLD signals.
129 sigset_t newMask, oldMask;
131 ForkedcallsController::ForkedcallsController()
133 reaped_children.resize(50);
134 signal(SIGCHLD, child_handler);
136 sigemptyset(&oldMask);
137 sigemptyset(&newMask);
138 sigaddset(&newMask, SIGCHLD);
141 void ForkedcallsController::handleCompletedProcesses()
143 if (current_child == -1)
146 // Block the SIGCHLD signal.
147 sigprocmask(SIG_BLOCK, &newMask, &oldMask);
149 for (int i = 0; i != 1+current_child; ++i) {
150 child_data & store = reaped_children[i];
151 // Go on to handle the child process
155 // Unblock the SIGCHLD signal and restore the old mask.
156 sigprocmask(SIG_SETMASK, &oldMask, 0);
159 VoilĂ ! An efficient, elegant and *safe* mechanism to handle child processes.
165 void child_handler(int)
167 ForkedcallsController & fcc = ForkedcallsController::get();
170 typedef vector<ForkedcallsController::Data>::size_type size_type;
171 if (size_type(fcc.current_child + 1) >= fcc.reaped_children.size())
174 ForkedcallsController::Data & store =
175 fcc.reaped_children[++fcc.current_child];
176 // Clean up the child process.
177 store.pid = wait(&store.status);
183 // Ensure, that only one controller exists inside process
184 ForkedcallsController & ForkedcallsController::get()
186 static ForkedcallsController singleton;
191 ForkedcallsController::ForkedcallsController()
192 : reaped_children(50), current_child(-1)
194 signal(SIGCHLD, child_handler);
196 sigemptyset(&oldMask);
197 sigemptyset(&newMask);
198 sigaddset(&newMask, SIGCHLD);
202 // open question: should we stop childs here?
203 // Asger says no: I like to have my xdvi open after closing LyX. Maybe
204 // I want to print or something.
205 ForkedcallsController::~ForkedcallsController()
207 signal(SIGCHLD, SIG_DFL);
211 void ForkedcallsController::addCall(ForkedProcess const & newcall)
213 forkedCalls.push_back(newcall.clone());
215 if (forkedCalls.size() > reaped_children.size()) {
216 // Block the SIGCHLD signal.
217 sigprocmask(SIG_BLOCK, &newMask, &oldMask);
219 reaped_children.resize(2*reaped_children.size());
221 // Unblock the SIGCHLD signal and restore the old mask.
222 sigprocmask(SIG_SETMASK, &oldMask, 0);
227 ForkedcallsController::iterator
228 ForkedcallsController::find_pid(pid_t pid)
230 return find_if(forkedCalls.begin(), forkedCalls.end(),
231 bind(equal_to<pid_t>(),
232 bind(&Forkedcall::pid, _1),
237 // Kill the process prematurely and remove it from the list
238 // within tolerance secs
239 void ForkedcallsController::kill(pid_t pid, int tolerance)
241 ListType::iterator it = find_pid(pid);
242 if (it == forkedCalls.end())
245 (*it)->kill(tolerance);
246 forkedCalls.erase(it);
250 // Check the list of dead children and emit any associated signals.
251 void ForkedcallsController::handleCompletedProcesses()
253 if (current_child == -1)
256 // Block the SIGCHLD signal.
257 sigprocmask(SIG_BLOCK, &newMask, &oldMask);
259 for (int i = 0; i != 1 + current_child; ++i) {
260 Data & store = reaped_children[i];
262 if (store.pid == -1) {
263 // Might happen perfectly innocently, eg as a result
264 // of the system (3) call.
266 lyxerr << "LyX: Error waiting for child: "
267 << strerror(errno) << endl;
271 ListType::iterator it = find_pid(store.pid);
272 if (it == forkedCalls.end())
273 // Eg, child was run in blocking mode
276 ListType::value_type child = (*it);
277 bool remove_it = false;
279 if (WIFEXITED(store.status)) {
280 // Ok, the return value goes into retval.
281 child->setRetValue(WEXITSTATUS(store.status));
284 } else if (WIFSIGNALED(store.status)) {
285 // Child died, so pretend it returned 1
286 child->setRetValue(1);
289 } else if (WIFSTOPPED(store.status)) {
290 lyxerr << "LyX: Child (pid: " << store.pid
291 << ") stopped on signal "
292 << WSTOPSIG(store.status)
293 << ". Waiting for child to finish." << endl;
296 lyxerr << "LyX: Something rotten happened while "
297 << "waiting for child " << store.pid << endl;
299 // Child died, so pretend it returned 1
300 child->setRetValue(1);
306 forkedCalls.erase(it);
313 // Unblock the SIGCHLD signal and restore the old mask.
314 sigprocmask(SIG_SETMASK, &oldMask, 0);
317 } // namespace support