@@ -37,6 +37,10 @@ extern char** environ;
37
37
38
38
using namespace std ;
39
39
40
+ namespace {
41
+ ExitStatus ParseExitStatus (int status);
42
+ }
43
+
40
44
Subprocess::Subprocess (bool use_console) : fd_(-1 ), pid_(-1 ),
41
45
use_console_(use_console) {
42
46
}
@@ -50,26 +54,34 @@ Subprocess::~Subprocess() {
50
54
}
51
55
52
56
bool Subprocess::Start (SubprocessSet* set, const string& command) {
53
- int output_pipe[2 ];
54
- if (pipe (output_pipe) < 0 )
55
- Fatal (" pipe: %s" , strerror (errno));
56
- fd_ = output_pipe[0 ];
57
+ int subproc_stdout_fd = -1 ;
58
+ if (use_console_) {
59
+ fd_ = -1 ;
60
+ } else {
61
+ int output_pipe[2 ];
62
+ if (pipe (output_pipe) < 0 )
63
+ Fatal (" pipe: %s" , strerror (errno));
64
+ fd_ = output_pipe[0 ];
65
+ subproc_stdout_fd = output_pipe[1 ];
57
66
#if !defined(USE_PPOLL)
58
- // If available, we use ppoll in DoWork(); otherwise we use pselect
59
- // and so must avoid overly-large FDs.
60
- if (fd_ >= static_cast <int >(FD_SETSIZE))
61
- Fatal (" pipe: %s" , strerror (EMFILE));
67
+ // If available, we use ppoll in DoWork(); otherwise we use pselect
68
+ // and so must avoid overly-large FDs.
69
+ if (fd_ >= static_cast <int >(FD_SETSIZE))
70
+ Fatal (" pipe: %s" , strerror (EMFILE));
62
71
#endif // !USE_PPOLL
63
- SetCloseOnExec (fd_);
72
+ SetCloseOnExec (fd_);
73
+ }
64
74
65
75
posix_spawn_file_actions_t action;
66
76
int err = posix_spawn_file_actions_init (&action);
67
77
if (err != 0 )
68
78
Fatal (" posix_spawn_file_actions_init: %s" , strerror (err));
69
79
70
- err = posix_spawn_file_actions_addclose (&action, output_pipe[0 ]);
71
- if (err != 0 )
72
- Fatal (" posix_spawn_file_actions_addclose: %s" , strerror (err));
80
+ if (!use_console_) {
81
+ err = posix_spawn_file_actions_addclose (&action, fd_);
82
+ if (err != 0 )
83
+ Fatal (" posix_spawn_file_actions_addclose: %s" , strerror (err));
84
+ }
73
85
74
86
posix_spawnattr_t attr;
75
87
err = posix_spawnattr_init (&attr);
@@ -98,18 +110,17 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) {
98
110
Fatal (" posix_spawn_file_actions_addopen: %s" , strerror (err));
99
111
}
100
112
101
- err = posix_spawn_file_actions_adddup2 (&action, output_pipe[ 1 ] , 1 );
113
+ err = posix_spawn_file_actions_adddup2 (&action, subproc_stdout_fd , 1 );
102
114
if (err != 0 )
103
115
Fatal (" posix_spawn_file_actions_adddup2: %s" , strerror (err));
104
- err = posix_spawn_file_actions_adddup2 (&action, output_pipe[ 1 ] , 2 );
116
+ err = posix_spawn_file_actions_adddup2 (&action, subproc_stdout_fd , 2 );
105
117
if (err != 0 )
106
118
Fatal (" posix_spawn_file_actions_adddup2: %s" , strerror (err));
107
- err = posix_spawn_file_actions_addclose (&action, output_pipe[ 1 ] );
119
+ err = posix_spawn_file_actions_addclose (&action, subproc_stdout_fd );
108
120
if (err != 0 )
109
121
Fatal (" posix_spawn_file_actions_addclose: %s" , strerror (err));
110
- // In the console case, output_pipe is still inherited by the child and
111
- // closed when the subprocess finishes, which then notifies ninja.
112
122
}
123
+
113
124
#ifdef POSIX_SPAWN_USEVFORK
114
125
flags |= POSIX_SPAWN_USEVFORK;
115
126
#endif
@@ -131,7 +142,8 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) {
131
142
if (err != 0 )
132
143
Fatal (" posix_spawn_file_actions_destroy: %s" , strerror (err));
133
144
134
- close (output_pipe[1 ]);
145
+ if (!use_console_)
146
+ close (subproc_stdout_fd);
135
147
return true ;
136
148
}
137
149
@@ -148,13 +160,32 @@ void Subprocess::OnPipeReady() {
148
160
}
149
161
}
150
162
151
- ExitStatus Subprocess::Finish () {
163
+
164
+ bool Subprocess::TryFinish (int waitpid_options) {
152
165
assert (pid_ != -1 );
153
- int status;
154
- if (waitpid (pid_, &status, 0 ) < 0 )
155
- Fatal (" waitpid(%d): %s" , pid_, strerror (errno));
166
+ int status, ret;
167
+ while ((ret = waitpid (pid_, &status, waitpid_options)) < 0 ) {
168
+ if (errno != EINTR)
169
+ Fatal (" waitpid(%d): %s" , pid_, strerror (errno));
170
+ }
171
+ if (ret == 0 )
172
+ return false ; // Subprocess is alive (WNOHANG-only).
156
173
pid_ = -1 ;
174
+ exit_status_ = ParseExitStatus (status);
175
+ return true ; // Subprocess has terminated.
176
+ }
177
+
178
+ ExitStatus Subprocess::Finish () {
179
+ if (pid_ != -1 ) {
180
+ TryFinish (0 );
181
+ assert (pid_ == -1 );
182
+ }
183
+ return exit_status_;
184
+ }
157
185
186
+ namespace {
187
+
188
+ ExitStatus ParseExitStatus (int status) {
158
189
#ifdef _AIX
159
190
if (WIFEXITED (status) && WEXITSTATUS (status) & 0x80 ) {
160
191
// Map the shell's exit code used for signal failure (128 + signal) to the
@@ -178,20 +209,31 @@ ExitStatus Subprocess::Finish() {
178
209
return static_cast <ExitStatus>(status + 128 );
179
210
}
180
211
212
+ } // anonymous namespace
213
+
181
214
bool Subprocess::Done () const {
182
- return fd_ == -1 ;
215
+ // Console subprocesses share console with ninja, and we consider them done
216
+ // when they exit.
217
+ // For other processes, we consider them done when we have consumed all their
218
+ // output and closed their associated pipe.
219
+ return (use_console_ && pid_ == -1 ) || (!use_console_ && fd_ == -1 );
183
220
}
184
221
185
222
const string& Subprocess::GetOutput () const {
186
223
return buf_;
187
224
}
188
225
189
- int SubprocessSet::interrupted_;
226
+ volatile sig_atomic_t SubprocessSet::interrupted_;
227
+ volatile sig_atomic_t SubprocessSet::s_sigchld_received;
190
228
191
229
void SubprocessSet::SetInterruptedFlag (int signum) {
192
230
interrupted_ = signum;
193
231
}
194
232
233
+ void SubprocessSet::SigChldHandler (int signo, siginfo_t * info, void * context) {
234
+ s_sigchld_received = 1 ;
235
+ }
236
+
195
237
void SubprocessSet::HandlePendingInterruption () {
196
238
sigset_t pending;
197
239
sigemptyset (&pending);
@@ -208,11 +250,14 @@ void SubprocessSet::HandlePendingInterruption() {
208
250
}
209
251
210
252
SubprocessSet::SubprocessSet () {
253
+ // Block all these signals.
254
+ // Their handlers will only be enabled during ppoll/pselect().
211
255
sigset_t set;
212
256
sigemptyset (&set);
213
257
sigaddset (&set, SIGINT);
214
258
sigaddset (&set, SIGTERM);
215
259
sigaddset (&set, SIGHUP);
260
+ sigaddset (&set, SIGCHLD);
216
261
if (sigprocmask (SIG_BLOCK, &set, &old_mask_) < 0 )
217
262
Fatal (" sigprocmask: %s" , strerror (errno));
218
263
@@ -225,6 +270,27 @@ SubprocessSet::SubprocessSet() {
225
270
Fatal (" sigaction: %s" , strerror (errno));
226
271
if (sigaction (SIGHUP, &act, &old_hup_act_) < 0 )
227
272
Fatal (" sigaction: %s" , strerror (errno));
273
+
274
+ memset (&act, 0 , sizeof (act));
275
+ act.sa_flags = SA_SIGINFO | SA_NOCLDSTOP;
276
+ act.sa_sigaction = SigChldHandler;
277
+ if (sigaction (SIGCHLD, &act, &old_chld_act_) < 0 )
278
+ Fatal (" sigaction: %s" , strerror (errno));
279
+ }
280
+
281
+ // Reaps console processes that have exited and moves them from the running set
282
+ // to the finished set.
283
+ void SubprocessSet::CheckConsoleProcessTerminated () {
284
+ if (!s_sigchld_received)
285
+ return ;
286
+ for (auto i = running_.begin (); i != running_.end (); ) {
287
+ if ((*i)->use_console_ && (*i)->TryFinish (WNOHANG)) {
288
+ finished_.push (*i);
289
+ i = running_.erase (i);
290
+ } else {
291
+ ++i;
292
+ }
293
+ }
228
294
}
229
295
230
296
SubprocessSet::~SubprocessSet () {
@@ -236,6 +302,8 @@ SubprocessSet::~SubprocessSet() {
236
302
Fatal (" sigaction: %s" , strerror (errno));
237
303
if (sigaction (SIGHUP, &old_hup_act_, 0 ) < 0 )
238
304
Fatal (" sigaction: %s" , strerror (errno));
305
+ if (sigaction (SIGCHLD, &old_chld_act_, 0 ) < 0 )
306
+ Fatal (" sigaction: %s" , strerror (errno));
239
307
if (sigprocmask (SIG_SETMASK, &old_mask_, 0 ) < 0 )
240
308
Fatal (" sigprocmask: %s" , strerror (errno));
241
309
}
@@ -264,9 +332,21 @@ bool SubprocessSet::DoWork() {
264
332
fds.push_back (pfd);
265
333
++nfds;
266
334
}
335
+ if (nfds == 0 ) {
336
+ // Add a dummy entry to prevent using an empty pollfd vector.
337
+ // ppoll() allows to do this by setting fd < 0.
338
+ pollfd pfd = { -1 , 0 , 0 };
339
+ fds.push_back (pfd);
340
+ ++nfds;
341
+ }
267
342
268
343
interrupted_ = 0 ;
344
+ s_sigchld_received = 0 ;
269
345
int ret = ppoll (&fds.front (), nfds, NULL , &old_mask_);
346
+ // Note: This can remove console processes from the running set, but that is
347
+ // not a problem for the pollfd set, as console processes are not part of the
348
+ // pollfd set (they don't have a fd).
349
+ CheckConsoleProcessTerminated ();
270
350
if (ret == -1 ) {
271
351
if (errno != EINTR) {
272
352
perror (" ninja: ppoll" );
@@ -275,16 +355,23 @@ bool SubprocessSet::DoWork() {
275
355
return IsInterrupted ();
276
356
}
277
357
358
+ // ppoll/pselect prioritizes file descriptor events over a signal delivery.
359
+ // However, if the user is trying to quit ninja, we should react as fast as
360
+ // possible.
278
361
HandlePendingInterruption ();
279
362
if (IsInterrupted ())
280
363
return true ;
281
364
365
+ // Iterate through both the pollfd set and the running set.
366
+ // All valid fds in the running set are in the pollfd, in the same order.
282
367
nfds_t cur_nfd = 0 ;
283
368
for (vector<Subprocess*>::iterator i = running_.begin ();
284
369
i != running_.end (); ) {
285
370
int fd = (*i)->fd_ ;
286
- if (fd < 0 )
371
+ if (fd < 0 ) {
372
+ ++i;
287
373
continue ;
374
+ }
288
375
assert (fd == fds[cur_nfd].fd );
289
376
if (fds[cur_nfd++].revents ) {
290
377
(*i)->OnPipeReady ();
@@ -317,7 +404,9 @@ bool SubprocessSet::DoWork() {
317
404
}
318
405
319
406
interrupted_ = 0 ;
320
- int ret = pselect (nfds, &set, 0 , 0 , 0 , &old_mask_);
407
+ s_sigchld_received = 0 ;
408
+ int ret = pselect (nfds, (nfds > 0 ? &set : nullptr ), 0 , 0 , 0 , &old_mask_);
409
+ CheckConsoleProcessTerminated ();
321
410
if (ret == -1 ) {
322
411
if (errno != EINTR) {
323
412
perror (" ninja: pselect" );
@@ -326,6 +415,9 @@ bool SubprocessSet::DoWork() {
326
415
return IsInterrupted ();
327
416
}
328
417
418
+ // ppoll/pselect prioritizes file descriptor events over a signal delivery.
419
+ // However, if the user is trying to quit ninja, we should react as fast as
420
+ // possible.
329
421
HandlePendingInterruption ();
330
422
if (IsInterrupted ())
331
423
return true ;
0 commit comments