Home | History | Annotate | Download | only in makeparallel
      1 // 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 <getopt.h>
     23 #include <poll.h>
     24 #include <signal.h>
     25 #include <stdio.h>
     26 #include <stdlib.h>
     27 #include <string.h>
     28 #include <unistd.h>
     29 #include <sys/time.h>
     30 #include <sys/types.h>
     31 #include <sys/wait.h>
     32 
     33 #include <string>
     34 #include <vector>
     35 
     36 #ifdef __linux__
     37 #include <error.h>
     38 #endif
     39 
     40 #ifdef __APPLE__
     41 #include <err.h>
     42 #define error(code, eval, fmt, ...) errc(eval, code, fmt, ##__VA_ARGS__)
     43 // Darwin does not interrupt syscalls by default.
     44 #define TEMP_FAILURE_RETRY(exp) (exp)
     45 #endif
     46 
     47 // Throw an error if fd is not valid.
     48 static void CheckFd(int fd) {
     49   int ret = fcntl(fd, F_GETFD);
     50   if (ret < 0) {
     51     if (errno == EBADF) {
     52       error(errno, 0, "no jobserver pipe, prefix recipe command with '+'");
     53     } else {
     54       error(errno, errno, "fnctl failed");
     55     }
     56   }
     57 }
     58 
     59 // Extract flags from MAKEFLAGS that need to be propagated to subproccess
     60 static std::vector<std::string> ReadMakeflags() {
     61   std::vector<std::string> args;
     62 
     63   const char* makeflags_env = getenv("MAKEFLAGS");
     64   if (makeflags_env == nullptr) {
     65     return args;
     66   }
     67 
     68   // The MAKEFLAGS format is pretty useless.  The first argument might be empty
     69   // (starts with a leading space), or it might be a set of one-character flags
     70   // merged together with no leading space, or it might be a variable
     71   // definition.
     72 
     73   std::string makeflags = makeflags_env;
     74 
     75   // Split makeflags into individual args on spaces.  Multiple spaces are
     76   // elided, but an initial space will result in a blank arg.
     77   size_t base = 0;
     78   size_t found;
     79   do {
     80     found = makeflags.find_first_of(" ", base);
     81     args.push_back(makeflags.substr(base, found - base));
     82     base = found + 1;
     83   } while (found != makeflags.npos);
     84 
     85   // Drop the first argument if it is empty
     86   while (args.size() > 0 && args[0].size() == 0) {
     87 	  args.erase(args.begin());
     88   }
     89 
     90   // Prepend a - to the first argument if it does not have one and is not a
     91   // variable definition
     92   if (args.size() > 0 && args[0][0] != '-') {
     93     if (args[0].find('=') == makeflags.npos) {
     94       args[0] = '-' + args[0];
     95     }
     96   }
     97 
     98   return args;
     99 }
    100 
    101 static bool ParseMakeflags(std::vector<std::string>& args,
    102     int* in_fd, int* out_fd, bool* parallel, bool* keep_going) {
    103 
    104   std::vector<char*> getopt_argv;
    105   // getopt starts reading at argv[1]
    106   getopt_argv.reserve(args.size() + 1);
    107   getopt_argv.push_back(strdup(""));
    108   for (std::string& v : args) {
    109     getopt_argv.push_back(strdup(v.c_str()));
    110   }
    111 
    112   opterr = 0;
    113   optind = 1;
    114   while (1) {
    115     const static option longopts[] = {
    116         {"jobserver-fds", required_argument, 0, 0},
    117         {0, 0, 0, 0},
    118     };
    119     int longopt_index = 0;
    120 
    121     int c = getopt_long(getopt_argv.size(), getopt_argv.data(), "kj",
    122         longopts, &longopt_index);
    123 
    124     if (c == -1) {
    125       break;
    126     }
    127 
    128     switch (c) {
    129     case 0:
    130       switch (longopt_index) {
    131       case 0:
    132       {
    133         // jobserver-fds
    134         if (sscanf(optarg, "%d,%d", in_fd, out_fd) != 2) {
    135           error(EXIT_FAILURE, 0, "incorrect format for --jobserver-fds: %s", optarg);
    136         }
    137         // TODO: propagate in_fd, out_fd
    138         break;
    139       }
    140       default:
    141         abort();
    142       }
    143       break;
    144     case 'j':
    145       *parallel = true;
    146       break;
    147     case 'k':
    148       *keep_going = true;
    149       break;
    150     case '?':
    151       // ignore unknown arguments
    152       break;
    153     default:
    154       abort();
    155     }
    156   }
    157 
    158   for (char *v : getopt_argv) {
    159     free(v);
    160   }
    161 
    162   return true;
    163 }
    164 
    165 // Read a single byte from fd, with timeout in milliseconds.  Returns true if
    166 // a byte was read, false on timeout.  Throws away the read value.
    167 // Non-reentrant, uses timer and signal handler global state, plus static
    168 // variable to communicate with signal handler.
    169 //
    170 // Uses a SIGALRM timer to fire a signal after timeout_ms that will interrupt
    171 // the read syscall if it hasn't yet completed.  If the timer fires before the
    172 // read the read could block forever, so read from a dup'd fd and close it from
    173 // the signal handler, which will cause the read to return EBADF if it occurs
    174 // after the signal.
    175 // The dup/read/close combo is very similar to the system described to avoid
    176 // a deadlock between SIGCHLD and read at
    177 // http://make.mad-scientist.net/papers/jobserver-implementation/
    178 static bool ReadByteTimeout(int fd, int timeout_ms) {
    179   // global variable to communicate with the signal handler
    180   static int dup_fd = -1;
    181 
    182   // dup the fd so the signal handler can close it without losing the real one
    183   dup_fd = dup(fd);
    184   if (dup_fd < 0) {
    185     error(errno, errno, "dup failed");
    186   }
    187 
    188   // set up a signal handler that closes dup_fd on SIGALRM
    189   struct sigaction action = {};
    190   action.sa_flags = SA_SIGINFO,
    191   action.sa_sigaction = [](int, siginfo_t*, void*) {
    192     close(dup_fd);
    193   };
    194   struct sigaction oldaction = {};
    195   int ret = sigaction(SIGALRM, &action, &oldaction);
    196   if (ret < 0) {
    197     error(errno, errno, "sigaction failed");
    198   }
    199 
    200   // queue a SIGALRM after timeout_ms
    201   const struct itimerval timeout = {{}, {0, timeout_ms * 1000}};
    202   ret = setitimer(ITIMER_REAL, &timeout, NULL);
    203   if (ret < 0) {
    204     error(errno, errno, "setitimer failed");
    205   }
    206 
    207   // start the blocking read
    208   char buf;
    209   int read_ret = read(dup_fd, &buf, 1);
    210   int read_errno = errno;
    211 
    212   // cancel the alarm in case it hasn't fired yet
    213   const struct itimerval cancel = {};
    214   ret = setitimer(ITIMER_REAL, &cancel, NULL);
    215   if (ret < 0) {
    216     error(errno, errno, "reset setitimer failed");
    217   }
    218 
    219   // remove the signal handler
    220   ret = sigaction(SIGALRM, &oldaction, NULL);
    221   if (ret < 0) {
    222     error(errno, errno, "reset sigaction failed");
    223   }
    224 
    225   // clean up the dup'd fd in case the signal never fired
    226   close(dup_fd);
    227   dup_fd = -1;
    228 
    229   if (read_ret == 0) {
    230     error(EXIT_FAILURE, 0, "EOF on jobserver pipe");
    231   } else if (read_ret > 0) {
    232     return true;
    233   } else if (read_errno == EINTR || read_errno == EBADF) {
    234     return false;
    235   } else {
    236     error(read_errno, read_errno, "read failed");
    237   }
    238   abort();
    239 }
    240 
    241 // Measure the size of the jobserver pool by reading from in_fd until it blocks
    242 static int GetJobserverTokens(int in_fd) {
    243   int tokens = 0;
    244   pollfd pollfds[] = {{in_fd, POLLIN, 0}};
    245   int ret;
    246   while ((ret = TEMP_FAILURE_RETRY(poll(pollfds, 1, 0))) != 0) {
    247     if (ret < 0) {
    248       error(errno, errno, "poll failed");
    249     } else if (pollfds[0].revents != POLLIN) {
    250       error(EXIT_FAILURE, 0, "unexpected event %d\n", pollfds[0].revents);
    251     }
    252 
    253     // There is probably a job token in the jobserver pipe.  There is a chance
    254     // another process reads it first, which would cause a blocking read to
    255     // block forever (or until another process put a token back in the pipe).
    256     // The file descriptor can't be set to O_NONBLOCK as that would affect
    257     // all users of the pipe, including the parent make process.
    258     // ReadByteTimeout emulates a non-blocking read on a !O_NONBLOCK socket
    259     // using a SIGALRM that fires after a short timeout.
    260     bool got_token = ReadByteTimeout(in_fd, 10);
    261     if (!got_token) {
    262       // No more tokens
    263       break;
    264     } else {
    265       tokens++;
    266     }
    267   }
    268 
    269   // This process implicitly gets a token, so pool size is measured size + 1
    270   return tokens;
    271 }
    272 
    273 // Return tokens to the jobserver pool.
    274 static void PutJobserverTokens(int out_fd, int tokens) {
    275   // Return all the tokens to the pipe
    276   char buf = '+';
    277   for (int i = 0; i < tokens; i++) {
    278     int ret = TEMP_FAILURE_RETRY(write(out_fd, &buf, 1));
    279     if (ret < 0) {
    280       error(errno, errno, "write failed");
    281     } else if (ret == 0) {
    282       error(EXIT_FAILURE, 0, "EOF on jobserver pipe");
    283     }
    284   }
    285 }
    286 
    287 int main(int argc, char* argv[]) {
    288   int in_fd = -1;
    289   int out_fd = -1;
    290   bool parallel = false;
    291   bool keep_going = false;
    292   bool ninja = false;
    293   int tokens = 0;
    294 
    295   if (argc > 1 && strcmp(argv[1], "--ninja") == 0) {
    296     ninja = true;
    297     argv++;
    298     argc--;
    299   }
    300 
    301   if (argc < 2) {
    302     error(EXIT_FAILURE, 0, "expected command to run");
    303   }
    304 
    305   const char* path = argv[1];
    306   std::vector<char*> args({argv[1]});
    307 
    308   std::vector<std::string> makeflags = ReadMakeflags();
    309   if (ParseMakeflags(makeflags, &in_fd, &out_fd, &parallel, &keep_going)) {
    310     if (in_fd >= 0 && out_fd >= 0) {
    311       CheckFd(in_fd);
    312       CheckFd(out_fd);
    313       fcntl(in_fd, F_SETFD, FD_CLOEXEC);
    314       fcntl(out_fd, F_SETFD, FD_CLOEXEC);
    315       tokens = GetJobserverTokens(in_fd);
    316     }
    317   }
    318 
    319   std::string jarg = "-j" + std::to_string(tokens + 1);
    320 
    321   if (ninja) {
    322     if (!parallel) {
    323       // ninja is parallel by default, pass -j1 to disable parallelism if make wasn't parallel
    324       args.push_back(strdup("-j1"));
    325     } else if (tokens > 0) {
    326       args.push_back(strdup(jarg.c_str()));
    327     }
    328     if (keep_going) {
    329       args.push_back(strdup("-k0"));
    330     }
    331   } else {
    332     args.push_back(strdup(jarg.c_str()));
    333   }
    334 
    335   args.insert(args.end(), &argv[2], &argv[argc]);
    336 
    337   args.push_back(nullptr);
    338 
    339   pid_t pid = fork();
    340   if (pid < 0) {
    341     error(errno, errno, "fork failed");
    342   } else if (pid == 0) {
    343     // child
    344     unsetenv("MAKEFLAGS");
    345     unsetenv("MAKELEVEL");
    346     int ret = execvp(path, args.data());
    347     if (ret < 0) {
    348       error(errno, errno, "exec %s failed", path);
    349     }
    350     abort();
    351   }
    352 
    353   // parent
    354   siginfo_t status = {};
    355   int exit_status = 0;
    356   int ret = waitid(P_PID, pid, &status, WEXITED);
    357   if (ret < 0) {
    358     error(errno, errno, "waitpid failed");
    359   } else if (status.si_code == CLD_EXITED) {
    360     exit_status = status.si_status;
    361   } else {
    362     exit_status = -(status.si_status);
    363   }
    364 
    365   if (tokens > 0) {
    366     PutJobserverTokens(out_fd, tokens);
    367   }
    368   exit(exit_status);
    369 }
    370