]> git.lyx.org Git - lyx.git/blob - src/support/forkedcontr.C
tostr -> convert and some bformat 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 #include <unistd.h>
27 #include <sys/wait.h>
28
29 using boost::bind;
30
31 using std::endl;
32 using std::equal_to;
33 using std::find_if;
34
35 using std::string;
36 using std::vector;
37
38 #ifndef CXX_GLOBAL_CSTD
39 using std::signal;
40 using std::strerror;
41 #endif
42
43
44 namespace lyx {
45 namespace support {
46
47 /* The forkedcall controller code handles finished child processes in a
48    two-stage process.
49
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.
55
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.
60
61    The signal handler is guaranteed to be safe even though it may not be
62    atomic:
63
64    int completed_child_status;
65    sig_atomic_t completed_child_pid;
66
67    extern "C"
68    void child_handler(int)
69    {
70      // Clean up the child process.
71      completed_child_pid = wait(&completed_child_status);
72    }
73
74    (See the signals tutorial at http://tinyurl.com/3h82w.)
75
76    It's safe because:
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.
81
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
86    not be safe.
87
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.
91
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.)
96
97    struct child_data {
98      pid_t pid;
99      int status;
100    };
101
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;
107
108    extern "C"
109    void child_handler(int)
110    {
111      child_data & store = reaped_children[++current_child];
112      // Clean up the child process.
113      store.pid = wait(&store.status);
114    }
115
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':
120
121    if (current_child != -1)
122      handleCompletedProcesses();
123
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:
127
128    // Used to block SIGCHLD signals.
129    sigset_t newMask, oldMask;
130
131    ForkedcallsController::ForkedcallsController()
132    {
133      reaped_children.resize(50);
134      signal(SIGCHLD, child_handler);
135
136      sigemptyset(&oldMask);
137      sigemptyset(&newMask);
138      sigaddset(&newMask, SIGCHLD);
139    }
140
141    void ForkedcallsController::handleCompletedProcesses()
142    {
143      if (current_child == -1)
144        return;
145
146      // Block the SIGCHLD signal.
147      sigprocmask(SIG_BLOCK, &newMask, &oldMask);
148
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
152        ...
153      }
154
155      // Unblock the SIGCHLD signal and restore the old mask.
156      sigprocmask(SIG_SETMASK, &oldMask, 0);
157    }
158
159    VoilĂ ! An efficient, elegant and *safe* mechanism to handle child processes.
160 */
161
162 namespace {
163
164 extern "C"
165 void child_handler(int)
166 {
167         ForkedcallsController & fcc = ForkedcallsController::get();
168
169         // Be safe
170         typedef vector<ForkedcallsController::Data>::size_type size_type;
171         if (size_type(fcc.current_child + 1) >= fcc.reaped_children.size())
172                 return;
173
174         ForkedcallsController::Data & store =
175                 fcc.reaped_children[++fcc.current_child];
176         // Clean up the child process.
177         store.pid = wait(&store.status);
178 }
179
180 } // namespace anon
181
182
183 // Ensure, that only one controller exists inside process
184 ForkedcallsController & ForkedcallsController::get()
185 {
186         static ForkedcallsController singleton;
187         return singleton;
188 }
189
190
191 ForkedcallsController::ForkedcallsController()
192         : reaped_children(50), current_child(-1)
193 {
194         signal(SIGCHLD, child_handler);
195
196         sigemptyset(&oldMask);
197         sigemptyset(&newMask);
198         sigaddset(&newMask, SIGCHLD);
199 }
200
201
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()
206 {
207         signal(SIGCHLD, SIG_DFL);
208 }
209
210
211 void ForkedcallsController::addCall(ForkedProcess const & newcall)
212 {
213         forkedCalls.push_back(newcall.clone());
214
215         if (forkedCalls.size() > reaped_children.size()) {
216                 // Block the SIGCHLD signal.
217                 sigprocmask(SIG_BLOCK, &newMask, &oldMask);
218
219                 reaped_children.resize(2*reaped_children.size());
220
221                 // Unblock the SIGCHLD signal and restore the old mask.
222                 sigprocmask(SIG_SETMASK, &oldMask, 0);
223         }
224 }
225
226
227 ForkedcallsController::iterator
228  ForkedcallsController::find_pid(pid_t pid)
229 {
230         return find_if(forkedCalls.begin(), forkedCalls.end(),
231                        bind(equal_to<pid_t>(),
232                             bind(&Forkedcall::pid, _1),
233                             pid));
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                         // Might happen perfectly innocently, eg as a result
264                         // of the system (3) call.
265                         if (errno)
266                                 lyxerr << "LyX: Error waiting for child: "
267                                        << strerror(errno) << endl;
268                         continue;
269                 }
270
271                 ListType::iterator it = find_pid(store.pid);
272                 if (it == forkedCalls.end())
273                         // Eg, child was run in blocking mode
274                         continue;
275
276                 ListType::value_type child = (*it);
277                 bool remove_it = false;
278
279                 if (WIFEXITED(store.status)) {
280                         // Ok, the return value goes into retval.
281                         child->setRetValue(WEXITSTATUS(store.status));
282                         remove_it = true;
283
284                 } else if (WIFSIGNALED(store.status)) {
285                         // Child died, so pretend it returned 1
286                         child->setRetValue(1);
287                         remove_it = true;
288
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;
294
295                 } else {
296                         lyxerr << "LyX: Something rotten happened while "
297                                << "waiting for child " << store.pid << endl;
298
299                         // Child died, so pretend it returned 1
300                         child->setRetValue(1);
301                         remove_it = true;
302                 }
303
304                 if (remove_it) {
305                         child->emitSignal();
306                         forkedCalls.erase(it);
307                 }
308         }
309
310         // Reset the counter
311         current_child = -1;
312
313         // Unblock the SIGCHLD signal and restore the old mask.
314         sigprocmask(SIG_SETMASK, &oldMask, 0);
315 }
316
317 } // namespace support
318 } // namespace lyx