Skip to content

Commit ec04fde

Browse files
Sjorsryanofsky
andcommitted
Precompute argv before fork in SpawnProcess
Evaluate fd_to_args and build argv in the parent before fork(). In the child between fork() and execvp(), avoid throwing/allocating and use _exit(1) on error. This likely fixes the occasional deadlock in Bitcoin Core functional test, noticed in bitcoin/bitcoin#34187. Co-authored-by: Russell Yanofsky <russ@yanofsky.org>
1 parent 30a8681 commit ec04fde

2 files changed

Lines changed: 37 additions & 10 deletions

File tree

include/mp/util.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,11 @@ using FdToArgsFn = std::function<std::vector<std::string>(int fd)>;
221221

222222
//! Spawn a new process that communicates with the current process over a socket
223223
//! pair. Returns pid through an output argument, and file descriptor for the
224-
//! local side of the socket. Invokes fd_to_args callback with the remote file
225-
//! descriptor number which returns the command line arguments that should be
226-
//! used to execute the process, and which should have the remote file
227-
//! descriptor embedded in whatever format the child process expects.
224+
//! local side of the socket.
225+
//! The fd_to_args callback is invoked in the parent process before fork().
226+
//! It must not rely on child pid/state, and must return the command line
227+
//! arguments that should be used to execute the process. Embed the remote file
228+
//! descriptor number in whatever format the child process expects.
228229
int SpawnProcess(int& pid, FdToArgsFn&& fd_to_args);
229230

230231
//! Call execvp with vector args.

src/mp/util.cpp

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,24 +122,50 @@ int SpawnProcess(int& pid, FdToArgsFn&& fd_to_args)
122122
throw std::system_error(errno, std::system_category(), "socketpair");
123123
}
124124

125+
// Evaluate the callback and build the argv array before forking.
126+
//
127+
// The parent process may be multi-threaded and holding internal library
128+
// locks at fork time. In that case, running code that allocates memory or
129+
// takes locks in the child between fork() and exec() can deadlock
130+
// indefinitely. Precomputing arguments in the parent avoids this.
131+
const std::vector<std::string> args{fd_to_args(fds[0])};
132+
const std::vector<char*> argv{MakeArgv(args)};
133+
125134
pid = fork();
126135
if (pid == -1) {
127136
throw std::system_error(errno, std::system_category(), "fork");
128137
}
129-
// Parent process closes the descriptor for socket 0, child closes the descriptor for socket 1.
138+
// Parent process closes the descriptor for socket 0, child closes the
139+
// descriptor for socket 1. On failure, the parent throws, but the child
140+
// must _exit(126) (post-fork child must not throw).
130141
if (close(fds[pid ? 0 : 1]) != 0) {
131-
if (pid) (void)close(fds[1]);
132-
throw std::system_error(errno, std::system_category(), "close");
142+
if (pid) {
143+
(void)close(fds[1]);
144+
throw std::system_error(errno, std::system_category(), "close");
145+
}
146+
static constexpr char msg[] = "SpawnProcess(child): close(fds[1]) failed\n";
147+
const ssize_t writeResult = ::write(STDERR_FILENO, msg, sizeof(msg) - 1);
148+
(void)writeResult;
149+
_exit(126);
133150
}
151+
134152
if (!pid) {
135-
// Child process must close all potentially open descriptors, except socket 0.
153+
// Child process must close all potentially open descriptors, except
154+
// socket 0. Do not throw, allocate, or do non-fork-safe work here.
136155
const int maxFd = MaxFd();
137156
for (int fd = 3; fd < maxFd; ++fd) {
138157
if (fd != fds[0]) {
139158
close(fd);
140159
}
141160
}
142-
ExecProcess(fd_to_args(fds[0]));
161+
162+
execvp(argv[0], argv.data());
163+
// NOTE: perror() is not async-signal-safe; calling it here in a
164+
// post-fork child may deadlock in multithreaded parents.
165+
// TODO: Report errors to the parent via a pipe (e.g. write errno)
166+
// so callers can get diagnostics without relying on perror().
167+
perror("execvp failed");
168+
_exit(127);
143169
}
144170
return fds[1];
145171
}
@@ -159,7 +185,7 @@ void ExecProcess(const std::vector<std::string>& args)
159185
int WaitProcess(int pid)
160186
{
161187
int status;
162-
if (::waitpid(pid, &status, 0 /* options */) != pid) {
188+
if (::waitpid(pid, &status, /*options=*/0) != pid) {
163189
throw std::system_error(errno, std::system_category(), "waitpid");
164190
}
165191
return status;

0 commit comments

Comments
 (0)