@@ -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)
159185int 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