Skip to content

Commit b399d15

Browse files
committed
Potential fix
1 parent 07d1462 commit b399d15

File tree

1 file changed

+51
-118
lines changed

1 file changed

+51
-118
lines changed

src/ray/util/process.cc

Lines changed: 51 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -213,182 +213,115 @@ class ProcessFD {
213213
new_env_ptrs.push_back(static_cast<char *>(NULL));
214214
char **envp = &new_env_ptrs[0];
215215

216-
// TODO(mehrdadn): Use clone() on Linux or posix_spawnp() on Mac to avoid duplicating
217-
// file descriptors into the child process, as that can be problematic.
218216
intptr_t fd = -1;
219-
int pipefds[2]; // Create pipe to get PID & track lifetime
220-
int parent_lifetime_pipe[2];
221-
222-
// Create pipes to health check parent <> child.
223-
// pipefds is used for parent to check child's health.
224-
if (pipe(pipefds) == -1) {
217+
// Pipe for getting startup status (PID and potential errno) from the child.
218+
int status_pipe[2];
219+
if (pipe(status_pipe) == -1) {
225220
ec = std::error_code(errno, std::system_category());
226221
return ProcessFD(-1, -1);
227222
}
228223

224+
// Pipe for parent lifetime tracking, connected to child's stdin.
225+
int parent_lifetime_pipe[2] = {-1, -1};
229226
if (pipe_to_stdin) {
230227
if (pipe(parent_lifetime_pipe) == -1) {
231-
parent_lifetime_pipe[0] = parent_lifetime_pipe[1] = -1;
228+
close(status_pipe[0]);
229+
close(status_pipe[1]);
230+
ec = std::error_code(errno, std::system_category());
231+
return ProcessFD(-1, -1);
232232
}
233233
}
234234

235-
// Set the write-end of the pipe to close on exec. This is crucial for the parent
236-
// to detect a successful execve, as the pipe will close automatically.
237-
SetFdCloseOnExec(pipefds[1]);
238-
239235
pid = fork();
240236

241237
if (pid == 0) {
242238
// Child process (or intermediate process if decoupled).
243-
close(pipefds[0]); // Child only writes.
239+
close(status_pipe[0]); // Child only writes to the status pipe.
240+
if (pipe_to_stdin) {
241+
close(parent_lifetime_pipe[1]); // Child only reads from the lifetime pipe.
242+
}
244243

245-
// Reset the SIGCHLD handler for the child.
246244
signal(SIGCHLD, SIG_DFL);
247245

248246
if (decouple) {
249-
// Double-fork to create an orphaned grandchild.
250247
if (fork() != 0) {
251-
// Intermediate parent exits, grandchild is orphaned and adopted by init.
248+
// Intermediate parent exits. Grandchild is orphaned.
252249
_exit(0);
253250
}
254-
// The grandchild continues from here.
255251
}
256252

257-
// This code is now executed by the direct child (if !decouple) or
258-
// the grandchild (if decouple).
259-
260-
// Redirect the read pipe to stdin so that child can track the
261-
// parent lifetime.
262-
if (pipe_to_stdin && parent_lifetime_pipe[0] != -1) {
263-
dup2(parent_lifetime_pipe[0], STDIN_FILENO);
253+
// Grandchild (if decoupled) or direct child (if not) continues here.
254+
if (pipe_to_stdin) {
255+
if (dup2(parent_lifetime_pipe[0], STDIN_FILENO) == -1) {
256+
// If dup2 fails, we can't health check, so exit.
257+
_exit(errno);
258+
}
259+
close(parent_lifetime_pipe[0]);
264260
}
265261

266-
// For decoupled processes, send the real PID back to the parent.
262+
// Set FD_CLOEXEC on the status pipe's write end. If execve succeeds,
263+
// this FD will be closed, and the parent will read EOF.
264+
SetFdCloseOnExec(status_pipe[1]);
265+
267266
if (decouple) {
268267
pid_t my_pid = getpid();
269-
if (write(pipefds[1], &my_pid, sizeof(my_pid)) != sizeof(my_pid)) {
270-
_exit(errno); // Exit if we can't even write the PID.
268+
if (write(status_pipe[1], &my_pid, sizeof(my_pid)) != sizeof(my_pid)) {
269+
_exit(errno);
271270
}
272271
}
273272

274273
execvpe(argv[0], const_cast<char *const *>(argv), const_cast<char *const *>(envp));
275274

276-
// If execvpe returns, an error occurred.
277-
if (!decouple) {
278-
// For the simple case, we can report the error via the pipe.
279-
int err = errno;
280-
(void)!write(pipefds[1], &err, sizeof(err));
281-
} else {
282-
// For the decoupled case, send the errno after the PID.
283-
int err = errno;
284-
(void)!write(pipefds[1], &err, sizeof(err));
285-
}
286-
// For both cases, exit with a non-zero status. For the decoupled case,
287-
// this will cause the lifetime-tracking pipe to close, signaling failure.
288-
_exit(errno);
275+
// If execvpe returns, an error occurred. Write errno to the pipe.
276+
int err = errno;
277+
(void)!write(status_pipe[1], &err, sizeof(err));
278+
_exit(err);
289279

290280
} else if (pid > 0) {
291281
// Parent process.
292-
close(pipefds[1]); // Parent only reads.
282+
close(status_pipe[1]); // Parent only reads from the status pipe.
283+
if (pipe_to_stdin) {
284+
close(parent_lifetime_pipe[0]); // Parent only writes to the lifetime pipe.
285+
}
293286

294287
if (!decouple) {
295-
// Simple case: read execvpe status from the direct child.
296288
int err_from_child;
297289
ssize_t bytes_read =
298-
ReadBytesFromFd(pipefds[0], &err_from_child, sizeof(err_from_child));
299-
290+
ReadBytesFromFd(status_pipe[0], &err_from_child, sizeof(err_from_child));
300291
if (bytes_read == 0) {
301-
// Success: child exec'd, pipe was closed by CLOEXEC.
292+
// Success: exec'd, pipe closed by CLOEXEC.
302293
ec = std::error_code();
303294
} else if (bytes_read == sizeof(err_from_child)) {
304-
// Failure: child sent a complete errno.
305295
ec = std::error_code(err_from_child, std::system_category());
306-
waitpid(pid, NULL, 0); // Reap the zombie child.
296+
waitpid(pid, NULL, 0);
307297
pid = -1;
308298
} else {
309-
// Pipe read error or unexpected data (e.g. partial read).
310-
// If read failed, use its errno; otherwise, it's a broken pipe.
311299
ec = std::error_code(bytes_read < 0 ? errno : EPIPE, std::system_category());
312300
waitpid(pid, NULL, 0);
313301
pid = -1;
314302
}
315-
close(pipefds[0]); // We're done with this short-lived pipe.
316-
fd = -1; // No persistent fd needed.
317-
318-
} else {
319-
// Decoupled case: Restore original logic to get grandchild PID.
320-
int s;
321-
waitpid(pid, &s, 0); // Wait for the intermediate process to exit.
322-
323-
// Read the grandchild's PID from the pipe.
324-
ssize_t bytes_read_pid = ReadBytesFromFd(pipefds[0], &pid, sizeof(pid));
325-
if (bytes_read_pid == sizeof(pid)) {
326-
// Successfully got PID. Now do a non-blocking read for a potential error.
327-
int flags = fcntl(pipefds[0], F_GETFL, 0);
328-
fcntl(pipefds[0], F_SETFL, flags | O_NONBLOCK);
329-
int exec_errno = 0;
330-
ssize_t bytes_read_errno = read(pipefds[0], &exec_errno, sizeof(exec_errno));
331-
// Restore original flags.
332-
fcntl(pipefds[0], F_SETFL, flags);
333-
334-
if (bytes_read_errno == sizeof(exec_errno)) {
335-
// We received an errno from the grandchild, meaning execvpe failed.
336-
ec = std::error_code(exec_errno, std::system_category());
337-
pid = -1;
338-
close(pipefds[0]);
339-
} else if (bytes_read_errno == -1 && errno == EAGAIN) {
340-
// No data available, which means execvpe succeeded.
341-
// 'fd' will be the pipe for lifetime tracking.
342-
fd = pipefds[0];
343-
} else {
344-
// Grandchild died after sending PID but before we could check for error.
345-
// This is a startup failure.
346-
ec = std::error_code(ECHILD, std::system_category());
347-
pid = -1;
348-
close(pipefds[0]);
349-
}
350-
} else if (bytes_read_pid == -1) {
351-
// read failed, use errno for specific error.
352-
ec = std::error_code(errno, std::system_category());
353-
pid = -1;
354-
close(pipefds[0]);
355-
} else { // bytes_read_pid == 0 or partial read
356-
// If bytes_read_pid is 0, it means EOF (child exited before writing PID).
357-
// If bytes_read_pid is positive but less than sizeof(pid), it's a partial
358-
// read. In both cases, it's a failure to get the PID.
303+
close(status_pipe[0]);
304+
} else { // Decoupled
305+
waitpid(pid, NULL, 0); // Reap the intermediate child.
306+
ssize_t bytes_read = ReadBytesFromFd(status_pipe[0], &pid, sizeof(pid));
307+
if (bytes_read != sizeof(pid)) {
308+
// Failed to get grandchild's PID.
359309
ec = std::error_code(ECHILD, std::system_category());
360310
pid = -1;
361-
close(pipefds[0]);
311+
} else {
312+
// Use the status pipe as the lifetime tracking fd.
313+
fd = status_pipe[0];
362314
}
363315
}
364-
365316
} else {
366317
// fork() failed.
367318
ec = std::error_code(errno, std::system_category());
368-
close(pipefds[0]);
369-
close(pipefds[1]);
370-
fd = -1;
371-
}
372-
373-
// Handle the separate pipe for tracking parent lifetime if used.
374-
if (pipe_to_stdin) {
375-
if (pid <= 0 && parent_lifetime_pipe[1] != -1) {
376-
// Child. Close sthe write end of the pipe from child.
377-
close(parent_lifetime_pipe[1]);
378-
parent_lifetime_pipe[1] = -1;
379-
SetFdCloseOnExec(parent_lifetime_pipe[0]);
380-
}
381-
if (pid != 0 && parent_lifetime_pipe[0] != -1) {
382-
// Parent. Close the read end of the pipe.
319+
close(status_pipe[0]);
320+
close(status_pipe[1]);
321+
if (pipe_to_stdin) {
383322
close(parent_lifetime_pipe[0]);
384-
parent_lifetime_pipe[0] = -1;
385-
// Make sure the write end of the pipe is closed on exec.
386-
SetFdCloseOnExec(parent_lifetime_pipe[1]);
323+
close(parent_lifetime_pipe[1]);
387324
}
388-
} else {
389-
// parent_lifetime_pipe pipes are not used.
390-
parent_lifetime_pipe[0] = -1;
391-
parent_lifetime_pipe[1] = -1;
392325
}
393326
#endif
394327
return ProcessFD(pid, fd);

0 commit comments

Comments
 (0)