]> git.lyx.org Git - lyx.git/blob - src/support/forkedcontr.C
Clean-up the forked process code a little.
[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 #include "lyxfunctional.h"
20
21 #include "debug.h"
22
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 std::endl;
31 using std::find_if;
32
33 using std::string;
34 using std::vector;
35
36 #ifndef CXX_GLOBAL_CSTD
37 using std::signal;
38 using std::strerror;
39 #endif
40
41
42 namespace lyx {
43 namespace support {
44
45 /* The forkedcall controller code handles finished child processes in a
46    two-stage process.
47
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.
53
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.
58
59    The signal handler is guaranteed to be safe even though it may not be
60    atomic:
61
62    int completed_child_status;
63    sig_atomic_t completed_child_pid;
64
65    extern "C"
66    void child_handler(int)
67    {
68      // Clean up the child process.
69      completed_child_pid = wait(&completed_child_status);
70    }
71
72    (See the signals tutorial at http://tinyurl.com/3h82w.)
73
74    It's safe because:
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.
79
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
84    not be safe.
85
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.
89
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.)
94
95    struct child_data {
96      pid_t pid;
97      int status;
98    };
99
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;
105
106    extern "C"
107    void child_handler(int)
108    {
109      child_data & store = reaped_children[++current_child];
110      // Clean up the child process.
111      store.pid = wait(&store.status);
112    }
113
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':
118
119    if (current_child != -1)
120      handleCompletedProcesses();
121
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:
125
126    // Used to block SIGCHLD signals.
127    sigset_t newMask, oldMask;
128
129    ForkedcallsController::ForkedcallsController()
130    {
131      reaped_children.resize(50);
132      signal(SIGCHLD, child_handler);
133
134      sigemptyset(&oldMask);
135      sigemptyset(&newMask);
136      sigaddset(&newMask, SIGCHLD);
137    }
138
139    void ForkedcallsController::handleCompletedProcesses()
140    {
141      if (current_child == -1)
142        return;
143
144      // Block the SIGCHLD signal.
145      sigprocmask(SIG_BLOCK, &newMask, &oldMask);
146
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
150        ...
151      }
152
153      // Unblock the SIGCHLD signal and restore the old mask.
154      sigprocmask(SIG_SETMASK, &oldMask, 0);
155    }
156
157    VoilĂ ! An efficient, elegant and *safe* mechanism to handle child processes.
158 */
159
160 namespace {
161
162 extern "C"
163 void child_handler(int)
164 {
165         ForkedcallsController & fcc = ForkedcallsController::get();
166
167         // Be safe
168         if (fcc.current_child+1 >= fcc.reaped_children.size())
169                 return;
170
171         ForkedcallsController::Data & store =
172                 fcc.reaped_children[++fcc.current_child];
173         // Clean up the child process.
174         store.pid = wait(&store.status);
175 }
176
177 } // namespace anon
178
179
180 // Ensure, that only one controller exists inside process
181 ForkedcallsController & ForkedcallsController::get()
182 {
183         static ForkedcallsController singleton;
184         return singleton;
185 }
186
187
188 ForkedcallsController::ForkedcallsController()
189         : reaped_children(50), current_child(-1)
190 {
191         signal(SIGCHLD, child_handler);
192
193         sigemptyset(&oldMask);
194         sigemptyset(&newMask);
195         sigaddset(&newMask, SIGCHLD);
196 }
197
198
199 // open question: should we stop childs here?
200 // Asger says no: I like to have my xdvi open after closing LyX. Maybe
201 // I want to print or something.
202 ForkedcallsController::~ForkedcallsController()
203 {
204         signal(SIGCHLD, SIG_DFL);
205 }
206
207
208 void ForkedcallsController::addCall(ForkedProcess const & newcall)
209 {
210         forkedCalls.push_back(newcall.clone());
211
212         if (forkedCalls.size() > reaped_children.size()) {
213                 // Block the SIGCHLD signal.
214                 sigprocmask(SIG_BLOCK, &newMask, &oldMask);
215
216                 reaped_children.resize(2*reaped_children.size());
217
218                 // Unblock the SIGCHLD signal and restore the old mask.
219                 sigprocmask(SIG_SETMASK, &oldMask, 0);
220         }
221 }
222
223
224 ForkedcallsController::iterator ForkedcallsController::find_pid(pid_t pid)
225 {
226         typedef boost::indirect_iterator<ListType::iterator> iterator;
227
228         iterator begin = boost::make_indirect_iterator(forkedCalls.begin());
229         iterator end   = boost::make_indirect_iterator(forkedCalls.end());
230         iterator it = find_if(begin, end,
231                               lyx::compare_memfun(&Forkedcall::pid, pid));
232
233         return it.base();
234 }
235
236
237 // Kill the process prematurely and remove it from the list
238 // within tolerance secs
239 void ForkedcallsController::kill(pid_t pid, int tolerance)
240 {
241         ListType::iterator it = find_pid(pid);
242         if (it == forkedCalls.end())
243                 return;
244
245         (*it)->kill(tolerance);
246         forkedCalls.erase(it);
247 }
248
249
250 // Check the list of dead children and emit any associated signals.
251 void ForkedcallsController::handleCompletedProcesses()
252 {
253         if (current_child == -1)
254                 return;
255
256         // Block the SIGCHLD signal.
257         sigprocmask(SIG_BLOCK, &newMask, &oldMask);
258
259         for (int i = 0; i != 1+current_child; ++i) {
260                 Data & store = reaped_children[i];
261
262                 if (store.pid == -1) {
263                         lyxerr << "LyX: Error waiting for child: "
264                                << strerror(errno) << endl;
265                         continue;
266                 }
267
268                 ListType::iterator it = find_pid(store.pid);
269                 if (it == forkedCalls.end())
270                         // Eg, child was run in blocking mode
271                         continue;
272
273                 ForkedProcess & child = *it->get();
274                 bool remove_it = false;
275
276                 if (WIFEXITED(store.status)) {
277                         // Ok, the return value goes into retval.
278                         child.setRetValue(WEXITSTATUS(store.status));
279                         remove_it = true;
280
281                 } else if (WIFSIGNALED(store.status)) {
282                         // Child died, so pretend it returned 1
283                         child.setRetValue(1);
284                         remove_it = true;
285
286                 } else if (WIFSTOPPED(store.status)) {
287                         lyxerr << "LyX: Child (pid: " << store.pid
288                                << ") stopped on signal "
289                                << WSTOPSIG(store.status)
290                                << ". Waiting for child to finish." << endl;
291
292                 } else {
293                         lyxerr << "LyX: Something rotten happened while "
294                                 "waiting for child " << store.pid << endl;
295
296                         // Child died, so pretend it returned 1
297                         child.setRetValue(1);
298                         remove_it = true;
299                 }
300
301                 if (remove_it) {
302                         child.emitSignal();
303                         forkedCalls.erase(it);
304                 }
305         }
306
307         // Reset the counter
308         current_child = -1;
309
310         // Unblock the SIGCHLD signal and restore the old mask.
311         sigprocmask(SIG_SETMASK, &oldMask, 0);
312 }
313
314 } // namespace support
315 } // namespace lyx