VirtualBox

source: kBuild/trunk/src/kmk/posixos.c@ 3512

Last change on this file since 3512 was 3512, checked in by bird, 3 years ago

kmk/posixos.c: Applied b552b05251980f693c729e251f93f5225b400714 from upstream: [PATCH] [SV 51159] Use a non-blocking read with pselect to avoid hangs.

  • Property svn:eol-style set to native
File size: 11.7 KB
Line 
1/* POSIX-based operating system interface for GNU Make.
2Copyright (C) 2016 Free Software Foundation, Inc.
3This file is part of GNU Make.
4
5GNU Make is free software; you can redistribute it and/or modify it under the
6terms of the GNU General Public License as published by the Free Software
7Foundation; either version 3 of the License, or (at your option) any later
8version.
9
10GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
11WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13
14You should have received a copy of the GNU General Public License along with
15this program. If not, see <http://www.gnu.org/licenses/>. */
16
17#include "makeint.h"
18
19#include <stdio.h>
20
21#ifdef HAVE_FCNTL_H
22# include <fcntl.h>
23#endif
24#if defined(HAVE_PSELECT) && defined(HAVE_SYS_SELECT_H)
25# include <sys/select.h>
26#endif
27
28#include "debug.h"
29#include "job.h"
30#include "os.h"
31
32#ifdef MAKE_JOBSERVER
33
34/* This section provides OS-specific functions to support the jobserver. */
35
36/* These track the state of the jobserver pipe. Passed to child instances. */
37static int job_fds[2] = { -1, -1 };
38
39/* Used to signal read() that a SIGCHLD happened. Always CLOEXEC.
40 If we use pselect() this will never be created and always -1.
41 */
42static int job_rfd = -1;
43
44/* Token written to the pipe (could be any character...) */
45static char token = '+';
46
47static int
48make_job_rfd (void)
49{
50#ifdef HAVE_PSELECT
51 /* Pretend we succeeded. */
52 return 0;
53#else
54 EINTRLOOP (job_rfd, dup (job_fds[0]));
55 if (job_rfd >= 0)
56 CLOSE_ON_EXEC (job_rfd);
57
58 return job_rfd;
59#endif
60}
61
62static void
63set_blocking (int fd, int blocking)
64{
65 // If we're not using pselect() don't change the blocking
66#ifdef HAVE_PSELECT
67 int flags;
68 EINTRLOOP (flags, fcntl (fd, F_GETFL));
69 if (flags >= 0)
70 {
71 int r;
72 flags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);
73 EINTRLOOP (r, fcntl (fd, F_SETFL, flags));
74 if (r < 0)
75 pfatal_with_name ("fcntl(O_NONBLOCK)");
76 }
77#endif
78}
79
80unsigned int
81jobserver_setup (int slots)
82{
83 int r;
84
85 EINTRLOOP (r, pipe (job_fds));
86 if (r < 0)
87 pfatal_with_name (_("creating jobs pipe"));
88
89 if (make_job_rfd () < 0)
90 pfatal_with_name (_("duping jobs pipe"));
91
92 while (slots--)
93 {
94 EINTRLOOP (r, write (job_fds[1], &token, 1));
95 if (r != 1)
96 pfatal_with_name (_("init jobserver pipe"));
97 }
98
99 /* When using pselect() we want the read to be non-blocking. */
100 set_blocking (job_fds[0], 0);
101
102 return 1;
103}
104
105unsigned int
106jobserver_parse_auth (const char *auth)
107{
108 /* Given the command-line parameter, parse it. */
109 if (sscanf (auth, "%d,%d", &job_fds[0], &job_fds[1]) != 2)
110 OS (fatal, NILF,
111 _("internal error: invalid --jobserver-auth string '%s'"), auth);
112
113 DB (DB_JOBS,
114 (_("Jobserver client (fds %d,%d)\n"), job_fds[0], job_fds[1]));
115
116#ifdef HAVE_FCNTL_H
117# define FD_OK(_f) (fcntl ((_f), F_GETFD) != -1)
118#else
119# define FD_OK(_f) 1
120#endif
121
122 /* Make sure our pipeline is valid, and (possibly) create a duplicate pipe,
123 that will be closed in the SIGCHLD handler. If this fails with EBADF,
124 the parent has closed the pipe on us because it didn't think we were a
125 submake. If so, warn and default to -j1. */
126
127 if (!FD_OK (job_fds[0]) || !FD_OK (job_fds[1]) || make_job_rfd () < 0)
128 {
129 if (errno != EBADF)
130 pfatal_with_name (_("jobserver pipeline"));
131
132 job_fds[0] = job_fds[1] = -1;
133
134 return 0;
135 }
136
137 /* When using pselect() we want the read to be non-blocking. */
138 set_blocking (job_fds[0], 0);
139
140 return 1;
141}
142
143char *
144jobserver_get_auth (void)
145{
146 char *auth = xmalloc ((INTSTR_LENGTH * 2) + 2);
147 sprintf (auth, "%d,%d", job_fds[0], job_fds[1]);
148 return auth;
149}
150
151unsigned int
152jobserver_enabled (void)
153{
154 return job_fds[0] >= 0;
155}
156
157void
158jobserver_clear (void)
159{
160 if (job_fds[0] >= 0)
161 close (job_fds[0]);
162 if (job_fds[1] >= 0)
163 close (job_fds[1]);
164 if (job_rfd >= 0)
165 close (job_rfd);
166
167 job_fds[0] = job_fds[1] = job_rfd = -1;
168}
169
170void
171jobserver_release (int is_fatal)
172{
173 int r;
174 EINTRLOOP (r, write (job_fds[1], &token, 1));
175 if (r != 1)
176 {
177 if (is_fatal)
178 pfatal_with_name (_("write jobserver"));
179 perror_with_name ("write", "");
180 }
181}
182
183unsigned int
184jobserver_acquire_all (void)
185{
186 unsigned int tokens = 0;
187
188 /* Use blocking reads to wait for all outstanding jobs. */
189 set_blocking (job_fds[0], 1);
190
191 /* Close the write side, so the read() won't hang forever. */
192 close (job_fds[1]);
193 job_fds[1] = -1;
194
195 while (1)
196 {
197 char intake;
198 int r;
199 EINTRLOOP (r, read (job_fds[0], &intake, 1));
200 if (r != 1)
201 return tokens;
202 ++tokens;
203 }
204}
205
206/* Prepare the jobserver to start a child process. */
207void
208jobserver_pre_child (int recursive)
209{
210 /* If it's not a recursive make, avoid polutting the jobserver pipes. */
211 if (!recursive && job_fds[0] >= 0)
212 {
213 CLOSE_ON_EXEC (job_fds[0]);
214 CLOSE_ON_EXEC (job_fds[1]);
215 }
216}
217
218void
219jobserver_post_child (int recursive)
220{
221#if defined(F_GETFD) && defined(F_SETFD)
222 if (!recursive && job_fds[0] >= 0)
223 {
224 unsigned int i;
225 for (i = 0; i < 2; ++i)
226 {
227 int flags;
228 EINTRLOOP (flags, fcntl (job_fds[i], F_GETFD));
229 if (flags >= 0)
230 {
231 int r;
232 EINTRLOOP (r, fcntl (job_fds[i], F_SETFD, flags & ~FD_CLOEXEC));
233 }
234 }
235 }
236#endif
237}
238
239void
240jobserver_signal (void)
241{
242 if (job_rfd >= 0)
243 {
244 close (job_rfd);
245 job_rfd = -1;
246 }
247}
248
249void
250jobserver_pre_acquire (void)
251{
252 /* Make sure we have a dup'd FD. */
253 if (job_rfd < 0 && job_fds[0] >= 0 && make_job_rfd () < 0)
254 pfatal_with_name (_("duping jobs pipe"));
255}
256
257#ifdef HAVE_PSELECT
258
259/* Use pselect() to atomically wait for both a signal and a file descriptor.
260 It also provides a timeout facility so we don't need to use SIGALRM.
261
262 This method relies on the fact that SIGCHLD will be blocked everywhere,
263 and only unblocked (atomically) within the pselect() call, so we can
264 never miss a SIGCHLD.
265 */
266unsigned int
267jobserver_acquire (int timeout)
268{
269 struct timespec spec;
270 struct timespec *specp = NULL;
271 sigset_t empty;
272
273 sigemptyset (&empty);
274
275 if (timeout)
276 {
277 /* Alarm after one second (is this too granular?) */
278 spec.tv_sec = 1;
279 spec.tv_nsec = 0;
280 specp = &spec;
281 }
282
283 while (1)
284 {
285 fd_set readfds;
286 int r;
287 char intake;
288
289 FD_ZERO (&readfds);
290 FD_SET (job_fds[0], &readfds);
291
292 r = pselect (job_fds[0]+1, &readfds, NULL, NULL, specp, &empty);
293 if (r < 0)
294 switch (errno)
295 {
296 case EINTR:
297 /* SIGCHLD will show up as an EINTR. */
298 return 0;
299
300 case EBADF:
301 /* Someone closed the jobs pipe.
302 That shouldn't happen but if it does we're done. */
303 O (fatal, NILF, _("job server shut down"));
304
305 default:
306 pfatal_with_name (_("pselect jobs pipe"));
307 }
308
309 if (r == 0)
310 /* Timeout. */
311 return 0;
312
313 /* The read FD is ready: read it! This is non-blocking. */
314 EINTRLOOP (r, read (job_fds[0], &intake, 1));
315
316 if (r < 0)
317 {
318 /* Someone sniped our token! Try again. */
319 if (errno == EAGAIN)
320 continue;
321
322 pfatal_with_name (_("read jobs pipe"));
323 }
324
325 /* read() should never return 0: only the master make can reap all the
326 tokens and close the write side...?? */
327 return r > 0;
328 }
329}
330
331#else
332
333/* This method uses a "traditional" UNIX model for waiting on both a signal
334 and a file descriptor. However, it's complex and since we have a SIGCHLD
335 handler installed we need to check ALL system calls for EINTR: painful!
336
337 Read a token. As long as there's no token available we'll block. We
338 enable interruptible system calls before the read(2) so that if we get a
339 SIGCHLD while we're waiting, we'll return with EINTR and we can process the
340 death(s) and return tokens to the free pool.
341
342 Once we return from the read, we immediately reinstate restartable system
343 calls. This allows us to not worry about checking for EINTR on all the
344 other system calls in the program.
345
346 There is one other twist: there is a span between the time reap_children()
347 does its last check for dead children and the time the read(2) call is
348 entered, below, where if a child dies we won't notice. This is extremely
349 serious as it could cause us to deadlock, given the right set of events.
350
351 To avoid this, we do the following: before we reap_children(), we dup(2)
352 the read FD on the jobserver pipe. The read(2) call below uses that new
353 FD. In the signal handler, we close that FD. That way, if a child dies
354 during the section mentioned above, the read(2) will be invoked with an
355 invalid FD and will return immediately with EBADF. */
356
357static RETSIGTYPE
358job_noop (int sig UNUSED)
359{
360}
361
362/* Set the child handler action flags to FLAGS. */
363static void
364set_child_handler_action_flags (int set_handler, int set_alarm)
365{
366 struct sigaction sa;
367
368#ifdef __EMX__
369 /* The child handler must be turned off here. */
370 signal (SIGCHLD, SIG_DFL);
371#endif
372
373 memset (&sa, '\0', sizeof sa);
374 sa.sa_handler = child_handler;
375 sa.sa_flags = set_handler ? 0 : SA_RESTART;
376
377#if defined SIGCHLD
378 if (sigaction (SIGCHLD, &sa, NULL) < 0)
379 pfatal_with_name ("sigaction: SIGCHLD");
380#endif
381
382#if defined SIGCLD && SIGCLD != SIGCHLD
383 if (sigaction (SIGCLD, &sa, NULL) < 0)
384 pfatal_with_name ("sigaction: SIGCLD");
385#endif
386
387#if defined SIGALRM
388 if (set_alarm)
389 {
390 /* If we're about to enter the read(), set an alarm to wake up in a
391 second so we can check if the load has dropped and we can start more
392 work. On the way out, turn off the alarm and set SIG_DFL. */
393 if (set_handler)
394 {
395 sa.sa_handler = job_noop;
396 sa.sa_flags = 0;
397 if (sigaction (SIGALRM, &sa, NULL) < 0)
398 pfatal_with_name ("sigaction: SIGALRM");
399 alarm (1);
400 }
401 else
402 {
403 alarm (0);
404 sa.sa_handler = SIG_DFL;
405 sa.sa_flags = 0;
406 if (sigaction (SIGALRM, &sa, NULL) < 0)
407 pfatal_with_name ("sigaction: SIGALRM");
408 }
409 }
410#endif
411}
412
413unsigned int
414jobserver_acquire (int timeout)
415{
416 char intake;
417 int got_token;
418 int saved_errno;
419
420 /* Set interruptible system calls, and read() for a job token. */
421 set_child_handler_action_flags (1, timeout);
422
423 EINTRLOOP (got_token, read (job_rfd, &intake, 1));
424 saved_errno = errno;
425
426 set_child_handler_action_flags (0, timeout);
427
428 if (got_token == 1)
429 return 1;
430
431 /* If the error _wasn't_ expected (EINTR or EBADF), fatal. Otherwise,
432 go back and reap_children(), and try again. */
433 errno = saved_errno;
434
435 if (errno != EINTR && errno != EBADF)
436 pfatal_with_name (_("read jobs pipe"));
437
438 if (errno == EBADF)
439 DB (DB_JOBS, ("Read returned EBADF.\n"));
440
441 return 0;
442}
443
444#endif
445
446#endif /* MAKE_JOBSERVER */
447
448/* Create a "bad" file descriptor for stdin when parallel jobs are run. */
449int
450get_bad_stdin (void)
451{
452 static int bad_stdin = -1;
453
454 /* Set up a bad standard input that reads from a broken pipe. */
455
456 if (bad_stdin == -1)
457 {
458 /* Make a file descriptor that is the read end of a broken pipe.
459 This will be used for some children's standard inputs. */
460 int pd[2];
461 if (pipe (pd) == 0)
462 {
463 /* Close the write side. */
464 (void) close (pd[1]);
465 /* Save the read side. */
466 bad_stdin = pd[0];
467
468 /* Set the descriptor to close on exec, so it does not litter any
469 child's descriptor table. When it is dup2'd onto descriptor 0,
470 that descriptor will not close on exec. */
471 CLOSE_ON_EXEC (bad_stdin);
472 }
473 }
474
475 return bad_stdin;
476}
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette