blob: 5eb1dd669b27161d55d148b840760990204dd94a [file] [log] [blame]
Colin Crossa5554482015-09-05 21:16:19 -07001// Copyright (C) 2015 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// makeparallel communicates with the GNU make jobserver
16// (http://make.mad-scientist.net/papers/jobserver-implementation/)
17// in order claim all available jobs, and then passes the number of jobs
18// claimed to a subprocess with -j<jobs>.
19
20#include <errno.h>
21#include <fcntl.h>
22#include <poll.h>
23#include <signal.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <unistd.h>
28#include <sys/time.h>
29#include <sys/types.h>
30#include <sys/wait.h>
31
32#include <string>
33#include <vector>
34
35#ifdef __linux__
36#include <error.h>
37#endif
38
39#ifdef __APPLE__
40#include <err.h>
41#define error(code, eval, fmt, ...) errc(eval, code, fmt, ##__VA_ARGS__)
42// Darwin does not interrupt syscalls by default.
43#define TEMP_FAILURE_RETRY(exp) (exp)
44#endif
45
46// Throw an error if fd is not valid.
47static void CheckFd(int fd) {
48 int ret = fcntl(fd, F_GETFD);
49 if (ret < 0) {
50 if (errno == EBADF) {
51 error(errno, 0, "no jobserver pipe, prefix recipe command with '+'");
52 } else {
53 error(errno, errno, "fnctl failed");
54 }
55 }
56}
57
58// Extract --jobserver-fds= argument from MAKEFLAGS environment variable.
59static int GetJobserver(int* in_fd, int* out_fd) {
60 const char* makeflags_env = getenv("MAKEFLAGS");
61 if (makeflags_env == nullptr) {
62 return false;
63 }
64
65 std::string makeflags = makeflags_env;
66
67 const std::string jobserver_fds_arg = "--jobserver-fds=";
68 size_t start = makeflags.find(jobserver_fds_arg);
69
70 if (start == std::string::npos) {
71 return false;
72 }
73
74 start += jobserver_fds_arg.size();
75
76 std::string::size_type end = makeflags.find(' ', start);
77
78 std::string::size_type len;
79 if (end == std::string::npos) {
80 len = std::string::npos;
81 } else {
82 len = end - start;
83 }
84
85 std::string jobserver_fds = makeflags.substr(start, len);
86
87 if (sscanf(jobserver_fds.c_str(), "%d,%d", in_fd, out_fd) != 2) {
88 return false;
89 }
90
91 CheckFd(*in_fd);
92 CheckFd(*out_fd);
93
94 return true;
95}
96
97// Read a single byte from fd, with timeout in milliseconds. Returns true if
98// a byte was read, false on timeout. Throws away the read value.
99// Non-reentrant, uses timer and signal handler global state, plus static
100// variable to communicate with signal handler.
101//
102// Uses a SIGALRM timer to fire a signal after timeout_ms that will interrupt
103// the read syscall if it hasn't yet completed. If the timer fires before the
104// read the read could block forever, so read from a dup'd fd and close it from
105// the signal handler, which will cause the read to return EBADF if it occurs
106// after the signal.
107// The dup/read/close combo is very similar to the system described to avoid
108// a deadlock between SIGCHLD and read at
109// http://make.mad-scientist.net/papers/jobserver-implementation/
110static bool ReadByteTimeout(int fd, int timeout_ms) {
111 // global variable to communicate with the signal handler
112 static int dup_fd = -1;
113
114 // dup the fd so the signal handler can close it without losing the real one
115 dup_fd = dup(fd);
116 if (dup_fd < 0) {
117 error(errno, errno, "dup failed");
118 }
119
120 // set up a signal handler that closes dup_fd on SIGALRM
121 struct sigaction action = {};
122 action.sa_flags = SA_SIGINFO,
123 action.sa_sigaction = [](int, siginfo_t*, void*) {
124 close(dup_fd);
125 };
126 struct sigaction oldaction = {};
127 int ret = sigaction(SIGALRM, &action, &oldaction);
128 if (ret < 0) {
129 error(errno, errno, "sigaction failed");
130 }
131
132 // queue a SIGALRM after timeout_ms
133 const struct itimerval timeout = {{}, {0, timeout_ms * 1000}};
134 ret = setitimer(ITIMER_REAL, &timeout, NULL);
135 if (ret < 0) {
136 error(errno, errno, "setitimer failed");
137 }
138
139 // start the blocking read
140 char buf;
141 int read_ret = read(dup_fd, &buf, 1);
142 int read_errno = errno;
143
144 // cancel the alarm in case it hasn't fired yet
145 const struct itimerval cancel = {};
146 ret = setitimer(ITIMER_REAL, &cancel, NULL);
147 if (ret < 0) {
148 error(errno, errno, "reset setitimer failed");
149 }
150
151 // remove the signal handler
152 ret = sigaction(SIGALRM, &oldaction, NULL);
153 if (ret < 0) {
154 error(errno, errno, "reset sigaction failed");
155 }
156
157 // clean up the dup'd fd in case the signal never fired
158 close(dup_fd);
159 dup_fd = -1;
160
161 if (read_ret == 0) {
162 error(EXIT_FAILURE, 0, "EOF on jobserver pipe");
163 } else if (read_ret > 0) {
164 return true;
165 } else if (read_errno == EINTR || read_errno == EBADF) {
166 return false;
167 } else {
168 error(read_errno, read_errno, "read failed");
169 }
170 abort();
171}
172
173// Measure the size of the jobserver pool by reading from in_fd until it blocks
174static int GetJobserverTokens(int in_fd) {
175 int tokens = 0;
176 pollfd pollfds[] = {{in_fd, POLLIN, 0}};
177 int ret;
178 while ((ret = TEMP_FAILURE_RETRY(poll(pollfds, 1, 0))) != 0) {
179 if (ret < 0) {
180 error(errno, errno, "poll failed");
181 } else if (pollfds[0].revents != POLLIN) {
182 error(EXIT_FAILURE, 0, "unexpected event %d\n", pollfds[0].revents);
183 }
184
185 // There is probably a job token in the jobserver pipe. There is a chance
186 // another process reads it first, which would cause a blocking read to
187 // block forever (or until another process put a token back in the pipe).
188 // The file descriptor can't be set to O_NONBLOCK as that would affect
189 // all users of the pipe, including the parent make process.
190 // ReadByteTimeout emulates a non-blocking read on a !O_NONBLOCK socket
191 // using a SIGALRM that fires after a short timeout.
192 bool got_token = ReadByteTimeout(in_fd, 10);
193 if (!got_token) {
194 // No more tokens
195 break;
196 } else {
197 tokens++;
198 }
199 }
200
201 // This process implicitly gets a token, so pool size is measured size + 1
202 return tokens;
203}
204
205// Return tokens to the jobserver pool.
206static void PutJobserverTokens(int out_fd, int tokens) {
207 // Return all the tokens to the pipe
208 char buf = '+';
209 for (int i = 0; i < tokens; i++) {
210 int ret = TEMP_FAILURE_RETRY(write(out_fd, &buf, 1));
211 if (ret < 0) {
212 error(errno, errno, "write failed");
213 } else if (ret == 0) {
214 error(EXIT_FAILURE, 0, "EOF on jobserver pipe");
215 }
216 }
217}
218
219int main(int argc, char* argv[]) {
220 int in_fd = -1;
221 int out_fd = -1;
222 int tokens = 0;
223
224 const char* path = argv[1];
225 std::vector<char*> args(&argv[1], &argv[argc]);
226
227 if (GetJobserver(&in_fd, &out_fd)) {
228 fcntl(in_fd, F_SETFD, FD_CLOEXEC);
229 fcntl(out_fd, F_SETFD, FD_CLOEXEC);
230
231 tokens = GetJobserverTokens(in_fd);
232 }
233
234 std::string jarg = "-j" + std::to_string(tokens + 1);
235 args.push_back(strdup(jarg.c_str()));
236 args.push_back(nullptr);
237
238 pid_t pid = fork();
239 if (pid < 0) {
240 error(errno, errno, "fork failed");
241 } else if (pid == 0) {
242 // child
243 int ret = execvp(path, args.data());
244 if (ret < 0) {
245 error(errno, errno, "exec failed");
246 }
247 abort();
248 }
249
250 // parent
251 siginfo_t status = {};
252 int exit_status = 0;
253 int ret = waitid(P_PID, pid, &status, WEXITED);
254 if (ret < 0) {
255 error(errno, errno, "waitpid failed");
256 } else if (status.si_code == CLD_EXITED) {
257 exit_status = status.si_status;
258 } else {
259 exit_status = -(status.si_status);
260 }
261
262 if (tokens > 0) {
263 PutJobserverTokens(out_fd, tokens);
264 }
265 exit(exit_status);
266}