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