@@ -36,6 +36,8 @@ extern char** environ;
36
36
37
37
using namespace std ;
38
38
39
+ static void CloseFileWhenPidExits (pid_t pid, int fd);
40
+
39
41
Subprocess::Subprocess (bool use_console) : fd_(-1 ), pid_(-1 ),
40
42
use_console_(use_console) {
41
43
}
@@ -103,12 +105,13 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) {
103
105
err = posix_spawn_file_actions_adddup2 (&action, output_pipe[1 ], 2 );
104
106
if (err != 0 )
105
107
Fatal (" posix_spawn_file_actions_adddup2: %s" , strerror (err));
106
- err = posix_spawn_file_actions_addclose (&action, output_pipe[1 ]);
107
- if (err != 0 )
108
- Fatal (" posix_spawn_file_actions_addclose: %s" , strerror (err));
109
108
// In the console case, output_pipe is still inherited by the child and
110
109
// closed when the subprocess finishes, which then notifies ninja.
111
110
}
111
+ err = posix_spawn_file_actions_addclose (&action, output_pipe[1 ]);
112
+ if (err != 0 )
113
+ Fatal (" posix_spawn_file_actions_addclose: %s" , strerror (err));
114
+
112
115
#ifdef POSIX_SPAWN_USEVFORK
113
116
flags |= POSIX_SPAWN_USEVFORK;
114
117
#endif
@@ -117,20 +120,42 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) {
117
120
if (err != 0 )
118
121
Fatal (" posix_spawnattr_setflags: %s" , strerror (err));
119
122
123
+ if (use_console_) {
124
+ // Block SIGCHLD to prevent a race where the new subprocess exits before we
125
+ // register its PID.
126
+ sigset_t signals;
127
+ sigemptyset (&signals);
128
+ sigaddset (&signals, SIGCHLD);
129
+ if (sigprocmask (SIG_BLOCK, &signals, NULL ) < 0 )
130
+ Fatal (" sigprocmask: %s" , strerror (errno));
131
+ }
132
+
120
133
const char * spawned_args[] = { " /bin/sh" , " -c" , command.c_str (), NULL };
121
134
err = posix_spawn (&pid_, " /bin/sh" , &action, &attr,
122
135
const_cast <char **>(spawned_args), environ);
123
136
if (err != 0 )
124
137
Fatal (" posix_spawn: %s" , strerror (err));
125
138
139
+ if (use_console_) {
140
+ // Shared console case: We keep the write-side of the pipe just so that we
141
+ // can close it on SIGCHLD reception.
142
+ CloseFileWhenPidExits (pid_, output_pipe[1 ]);
143
+ sigset_t signals;
144
+ sigemptyset (&signals);
145
+ sigaddset (&signals, SIGCHLD);
146
+ if (sigprocmask (SIG_UNBLOCK, &signals, NULL ) < 0 )
147
+ Fatal (" sigprocmask: %s" , strerror (errno));
148
+ } else {
149
+ // Not shared console: Only the subprocess needs the write-end of the pipe.
150
+ close (output_pipe[1 ]);
151
+ }
152
+
126
153
err = posix_spawnattr_destroy (&attr);
127
154
if (err != 0 )
128
155
Fatal (" posix_spawnattr_destroy: %s" , strerror (err));
129
156
err = posix_spawn_file_actions_destroy (&action);
130
157
if (err != 0 )
131
158
Fatal (" posix_spawn_file_actions_destroy: %s" , strerror (err));
132
-
133
- close (output_pipe[1 ]);
134
159
return true ;
135
160
}
136
161
@@ -150,8 +175,10 @@ void Subprocess::OnPipeReady() {
150
175
ExitStatus Subprocess::Finish () {
151
176
assert (pid_ != -1 );
152
177
int status;
153
- if (waitpid (pid_, &status, 0 ) < 0 )
154
- Fatal (" waitpid(%d): %s" , pid_, strerror (errno));
178
+ while (waitpid (pid_, &status, 0 ) < 0 ) {
179
+ if (errno != EINTR)
180
+ Fatal (" waitpid(%d): %s" , pid_, strerror (errno));
181
+ }
155
182
pid_ = -1 ;
156
183
157
184
#ifdef _AIX
@@ -205,6 +232,93 @@ void SubprocessSet::HandlePendingInterruption() {
205
232
interrupted_ = SIGHUP;
206
233
}
207
234
235
+ // SIGCHLD handling:
236
+
237
+ struct PidFdEntry ;
238
+ typedef unsigned int IxEntry;
239
+ // PidFdList is used to store the PID and file descriptor pairs of the
240
+ // subprocesses being run by ninja that share a terminal.
241
+ // On SIGCHLD the PID of the signal is matched with one of the entries and the
242
+ // associated file descriptor closed. Then the entry is freed for reuse.
243
+ // Normally we would use something like std::unordered_map<>, but that involves
244
+ // memory allocations which are not reentrant.
245
+ struct PidFdList {
246
+ PidFdEntry* entries; // Entry with index 0 is reserved as NULL and not used.
247
+ size_t capacity; // Number of PidFdEntry allocated for pid_fds.
248
+ IxEntry ix_head_used;
249
+ IxEntry ix_head_free;
250
+ };
251
+
252
+ // A PidFdEntry may be used or free.
253
+ struct PidFdEntry {
254
+ pid_t pid; // -1 if this is a free entry
255
+ int fd;
256
+ // ix_next of a used entry points to another used entry, ix_next of free entry
257
+ // points to another free entry.
258
+ IxEntry ix_next;
259
+ };
260
+
261
+ static PidFdList PID_FD_LIST = { nullptr , 0 , 0 , 1 };
262
+
263
+ static void initializePidFdEntries (PidFdEntry* entries, IxEntry ix_start, size_t capacity) {
264
+ for (unsigned int i = ix_start; i < capacity; i++) {
265
+ entries[i].pid = -1 ;
266
+ entries[i].fd = -1 ;
267
+ entries[i].ix_next = i + 1 ;
268
+ }
269
+ entries[capacity - 1 ].ix_next = 0 ; // end of the free list.
270
+ }
271
+
272
+ // Must only be called with SIGCHLD blocked.
273
+ static void CloseFileWhenPidExits (pid_t pid, int fd) {
274
+ PidFdList* list = &PID_FD_LIST;
275
+ if (!list->entries ) {
276
+ // First use, allocate the list.
277
+ list->capacity = 16 ;
278
+ list->entries = new PidFdEntry[list->capacity ];
279
+ initializePidFdEntries (list->entries , 1 , list->capacity );
280
+ } else if (!list->ix_head_free ) {
281
+ // Expand the list to get more free entries.
282
+ size_t old_capacity = list->capacity ;
283
+ list->capacity *= 2 ;
284
+ list->entries = static_cast <PidFdEntry*>(realloc (list->entries ,
285
+ sizeof (PidFdEntry) * list->capacity ));
286
+ initializePidFdEntries (list->entries , old_capacity, list->capacity );
287
+ list->ix_head_free = old_capacity;
288
+ }
289
+ // Replace the head free entry and make it the new used head.
290
+ IxEntry ix_entry = list->ix_head_free ;
291
+ PidFdEntry* entry = &list->entries [ix_entry];
292
+ IxEntry ix_next_free = entry->ix_next ;
293
+ entry->pid = pid;
294
+ entry->fd = fd;
295
+ entry->ix_next = list->ix_head_used ;
296
+ list->ix_head_used = ix_entry;
297
+ list->ix_head_free = ix_next_free;
298
+ }
299
+
300
+ static void SigChldHandler (int signo, siginfo_t * info, void * context) {
301
+ int pid = info->si_pid ;
302
+
303
+ PidFdList* list = &PID_FD_LIST;
304
+ if (!list->entries )
305
+ return ;
306
+ for (IxEntry* ix = &list->ix_head_used ; *ix; ix = &list->entries [*ix].ix_next ) {
307
+ PidFdEntry* entry = &list->entries [*ix];
308
+ if (entry->pid == pid) {
309
+ // Found a match. Close the pipe and remove the entry.
310
+ close (entry->fd );
311
+ IxEntry ix_next_used = entry->ix_next ;
312
+ entry->fd = -1 ;
313
+ entry->pid = -1 ;
314
+ entry->ix_next = list->ix_head_free ;
315
+ list->ix_head_free = *ix;
316
+ *ix = ix_next_used;
317
+ break ;
318
+ }
319
+ }
320
+ }
321
+
208
322
SubprocessSet::SubprocessSet () {
209
323
sigset_t set;
210
324
sigemptyset (&set);
@@ -223,6 +337,12 @@ SubprocessSet::SubprocessSet() {
223
337
Fatal (" sigaction: %s" , strerror (errno));
224
338
if (sigaction (SIGHUP, &act, &old_hup_act_) < 0 )
225
339
Fatal (" sigaction: %s" , strerror (errno));
340
+
341
+ memset (&act, 0 , sizeof (act));
342
+ act.sa_flags = SA_SIGINFO | SA_NOCLDSTOP;
343
+ act.sa_sigaction = SigChldHandler;
344
+ if (sigaction (SIGCHLD, &act, &old_chld_act_) < 0 )
345
+ Fatal (" sigaction: %s" , strerror (errno));
226
346
}
227
347
228
348
SubprocessSet::~SubprocessSet () {
@@ -234,6 +354,8 @@ SubprocessSet::~SubprocessSet() {
234
354
Fatal (" sigaction: %s" , strerror (errno));
235
355
if (sigaction (SIGHUP, &old_hup_act_, 0 ) < 0 )
236
356
Fatal (" sigaction: %s" , strerror (errno));
357
+ if (sigaction (SIGCHLD, &old_chld_act_, 0 ) < 0 )
358
+ Fatal (" sigaction: %s" , strerror (errno));
237
359
if (sigprocmask (SIG_SETMASK, &old_mask_, 0 ) < 0 )
238
360
Fatal (" sigprocmask: %s" , strerror (errno));
239
361
}
0 commit comments