]> git.lyx.org Git - lyx.git/blob - src/support/forkedcontr.C
make "make distcheck" work
[lyx.git] / src / support / forkedcontr.C
1 /**
2  * \file forkedcontr.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup Nielsen
7  * \author Angus Leeming
8  *
9  * Full author contact details are available in file CREDITS.
10  *
11  * A class for the control of child processes launched using
12  * fork() and execvp().
13  */
14
15 #include <config.h>
16
17 #include "support/forkedcontr.h"
18 #include "support/forkedcall.h"
19
20 #include "debug.h"
21
22 #include <boost/bind.hpp>
23
24 #include <cerrno>
25 #include <cstdlib>
26 #ifdef HAVE_UNISTD_H
27 # include <unistd.h>
28 #endif
29 #include <sys/wait.h>
30
31 using boost::bind;
32
33 using std::endl;
34 using std::equal_to;
35 using std::find_if;
36
37 using std::string;
38 using std::vector;
39
40 #ifndef CXX_GLOBAL_CSTD
41 using std::signal;
42 using std::strerror;
43 #endif
44
45
46 namespace lyx {
47 namespace support {
48
49 /* The forkedcall controller code handles finished child processes in a
50    two-stage process.
51
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.
57
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.
62
63    The signal handler is guaranteed to be safe even though it may not be
64    atomic:
65
66    int completed_child_status;
67    sig_atomic_t completed_child_pid;
68
69    extern "C"
70    void child_handler(int)
71    {
72      // Clean up the child process.
73      completed_child_pid = wait(&completed_child_status);
74    }
75
76    (See the signals tutorial at http://tinyurl.com/3h82w.)
77
78    It's safe because:
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.
83
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
88    not be safe.
89
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.
93
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.)
98
99    struct child_data {
100      pid_t pid;
101      int status;
102    };
103
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;
109
110    extern "C"
111    void child_handler(int)
112    {
113      child_data & store = reaped_children[++current_child];
114      // Clean up the child process.
115      store.pid = wait(&store.status);
116    }
117
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':
122
123    if (current_child != -1)
124      handleCompletedProcesses();
125
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:
129
130    // Used to block SIGCHLD signals.
131    sigset_t newMask, oldMask;
132
133    ForkedcallsController::ForkedcallsController()
134    {
135      reaped_children.resize(50);
136      signal(SIGCHLD, child_handler);
137
138      sigemptyset(&oldMask);
139      sigemptyset(&newMask);
140      sigaddset(&newMask, SIGCHLD);
141    }
142
143    void ForkedcallsController::handleCompletedProcesses()
144    {
145      if (current_child == -1)
146        return;
147
148      // Block the SIGCHLD signal.
149      sigprocmask(SIG_BLOCK, &newMask, &oldMask);
150
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
154        ...
155      }
156
157      // Unblock the SIGCHLD signal and restore the old mask.
158      sigprocmask(SIG_SETMASK, &oldMask, 0);
159    }
160
161    VoilĂ ! An efficient, elegant and *safe* mechanism to handle child processes.
162 */
163
164 namespace {
165
166 extern "C"
167 void child_handler(int)
168 {
169         ForkedcallsController & fcc = ForkedcallsController::get();
170
171         // Be safe
172         typedef vector<ForkedcallsController::Data>::size_type size_type;
173         if (size_type(fcc.current_child + 1) >= fcc.reaped_children.size())
174                 return;
175
176         ForkedcallsController::Data & store =
177                 fcc.reaped_children[++fcc.current_child];
178         // Clean up the child process.
179         store.pid = wait(&store.status);
180 }
181
182 } // namespace anon
183
184
185 // Ensure, that only one controller exists inside process
186 ForkedcallsController & ForkedcallsController::get()
187 {
188         static ForkedcallsController singleton;
189         return singleton;
190 }
191
192
193 ForkedcallsController::ForkedcallsController()
194         : reaped_children(50), current_child(-1)
195 {
196         signal(SIGCHLD, child_handler);
197
198         sigemptyset(&oldMask);
199         sigemptyset(&newMask);
200         sigaddset(&newMask, SIGCHLD);
201 }
202
203
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()
208 {
209         signal(SIGCHLD, SIG_DFL);
210 }
211
212
213 void ForkedcallsController::addCall(ForkedProcess const & newcall)
214 {
215         forkedCalls.push_back(newcall.clone());
216
217         if (forkedCalls.size() > reaped_children.size()) {
218                 // Block the SIGCHLD signal.
219                 sigprocmask(SIG_BLOCK, &newMask, &oldMask);
220
221                 reaped_children.resize(2*reaped_children.size());
222
223                 // Unblock the SIGCHLD signal and restore the old mask.
224                 sigprocmask(SIG_SETMASK, &oldMask, 0);
225         }
226 }
227
228
229 ForkedcallsController::iterator
230  ForkedcallsController::find_pid(pid_t pid)
231 {
232         return find_if(forkedCalls.begin(), forkedCalls.end(),
233                        bind(equal_to<pid_t>(),
234                             bind(&Forkedcall::pid, _1),
235                             pid));
236 }
237
238
239 // Kill the process prematurely and remove it from the list
240 // within tolerance secs
241 void ForkedcallsController::kill(pid_t pid, int tolerance)
242 {
243         ListType::iterator it = find_pid(pid);
244         if (it == forkedCalls.end())
245                 return;
246
247         (*it)->kill(tolerance);
248         forkedCalls.erase(it);
249 }
250
251
252 // Check the list of dead children and emit any associated signals.
253 void ForkedcallsController::handleCompletedProcesses()
254 {
255         if (current_child == -1)
256                 return;
257
258         // Block the SIGCHLD signal.
259         sigprocmask(SIG_BLOCK, &newMask, &oldMask);
260
261         for (int i = 0; i != 1 + current_child; ++i) {
262                 Data & store = reaped_children[i];
263
264                 if (store.pid == -1) {
265                         // Might happen perfectly innocently, eg as a result
266                         // of the system (3) call.
267                         if (errno)
268                                 lyxerr << "LyX: Error waiting for child: "
269                                        << strerror(errno) << endl;
270                         continue;
271                 }
272
273                 ListType::iterator it = find_pid(store.pid);
274                 if (it == forkedCalls.end())
275                         // Eg, child was run in blocking mode
276                         continue;
277
278                 ListType::value_type child = (*it);
279                 bool remove_it = false;
280
281                 if (WIFEXITED(store.status)) {
282                         // Ok, the return value goes into retval.
283                         child->setRetValue(WEXITSTATUS(store.status));
284                         remove_it = true;
285
286                 } else if (WIFSIGNALED(store.status)) {
287                         // Child died, so pretend it returned 1
288                         child->setRetValue(1);
289                         remove_it = true;
290
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;
296
297                 } else {
298                         lyxerr << "LyX: Something rotten happened while "
299                                << "waiting for child " << store.pid << endl;
300
301                         // Child died, so pretend it returned 1
302                         child->setRetValue(1);
303                         remove_it = true;
304                 }
305
306                 if (remove_it) {
307                         child->emitSignal();
308                         forkedCalls.erase(it);
309                 }
310         }
311
312         // Reset the counter
313         current_child = -1;
314
315         // Unblock the SIGCHLD signal and restore the old mask.
316         sigprocmask(SIG_SETMASK, &oldMask, 0);
317 }
318
319 } // namespace support
320 } // namespace lyx