github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/make/tools/makeparallel/makeparallel.cpp (about)

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