brintos

brintos / llvm-project-archived public Read only

0
0
Text · 6.6 KiB · 53bf7f2 Raw
196 lines · plain
1//===- llvm/Support/Unix/Jobserver.inc - Unix Jobserver Impl ----*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This file implements the UNIX-specific parts of the JobserverClient class.10//11//===----------------------------------------------------------------------===//12 13#include <atomic>14#include <cassert>15#include <cerrno>16#include <fcntl.h>17#include <string.h>18#include <sys/stat.h>19#include <unistd.h>20 21namespace {22/// Returns true if the given file descriptor is a FIFO (named pipe).23bool isFifo(int FD) {24  struct stat StatBuf;25  if (::fstat(FD, &StatBuf) != 0)26    return false;27  return S_ISFIFO(StatBuf.st_mode);28}29 30/// Returns true if the given file descriptors are valid.31bool areFdsValid(int ReadFD, int WriteFD) {32  if (ReadFD == -1 || WriteFD == -1)33    return false;34  // Check if the file descriptors are actually valid by checking their flags.35  return ::fcntl(ReadFD, F_GETFD) != -1 && ::fcntl(WriteFD, F_GETFD) != -1;36}37} // namespace38 39/// The constructor sets up the client based on the provided configuration.40/// For pipe-based jobservers, it duplicates the inherited file descriptors,41/// sets them to close-on-exec, and makes the read descriptor non-blocking.42/// For FIFO-based jobservers, it opens the named pipe. After setup, it drains43/// all available tokens from the jobserver to determine the total number of44/// available jobs (`NumJobs`), then immediately releases them back.45JobserverClientImpl::JobserverClientImpl(const JobserverConfig &Config) {46  switch (Config.TheMode) {47  case JobserverConfig::PosixPipe: {48    // Duplicate the read and write file descriptors.49    int NewReadFD = ::dup(Config.PipeFDs.Read);50    if (NewReadFD < 0)51      return;52    int NewWriteFD = ::dup(Config.PipeFDs.Write);53    if (NewWriteFD < 0) {54      ::close(NewReadFD);55      return;56    }57    // Set the new descriptors to be closed automatically on exec().58    if (::fcntl(NewReadFD, F_SETFD, FD_CLOEXEC) == -1 ||59        ::fcntl(NewWriteFD, F_SETFD, FD_CLOEXEC) == -1) {60      ::close(NewReadFD);61      ::close(NewWriteFD);62      return;63    }64    // Set the read descriptor to non-blocking.65    int flags = ::fcntl(NewReadFD, F_GETFL, 0);66    if (flags == -1 || ::fcntl(NewReadFD, F_SETFL, flags | O_NONBLOCK) == -1) {67      ::close(NewReadFD);68      ::close(NewWriteFD);69      return;70    }71    ReadFD = NewReadFD;72    WriteFD = NewWriteFD;73    break;74  }75  case JobserverConfig::PosixFifo:76    // Open the FIFO for reading. It must be non-blocking and close-on-exec.77    ReadFD = ::open(Config.Path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC);78    if (ReadFD < 0 || !isFifo(ReadFD)) {79      if (ReadFD >= 0)80        ::close(ReadFD);81      ReadFD = -1;82      return;83    }84    FifoPath = Config.Path;85    // The write FD is opened on-demand in release().86    WriteFD = -1;87    break;88  default:89    return;90  }91 92  IsInitialized = true;93  // Determine the total number of jobs by acquiring all available slots and94  // then immediately releasing them.95  SmallVector<JobSlot, 8> Slots;96  while (true) {97    auto S = tryAcquire();98    if (!S.isValid())99      break;100    Slots.push_back(std::move(S));101  }102  NumJobs = Slots.size();103  assert(NumJobs >= 1 && "Invalid number of jobs");104  for (auto &S : Slots)105    release(std::move(S));106}107 108/// The destructor closes any open file descriptors.109JobserverClientImpl::~JobserverClientImpl() {110  if (ReadFD >= 0)111    ::close(ReadFD);112  if (WriteFD >= 0)113    ::close(WriteFD);114}115 116/// Tries to acquire a job slot. The first call to this function will always117/// successfully acquire the single "implicit" slot that is granted to every118/// process started by `make`. Subsequent calls attempt to read a one-byte119/// token from the jobserver's read pipe. A successful read grants one120/// explicit job slot. The read is non-blocking; if no token is available,121/// it fails and returns an invalid JobSlot.122JobSlot JobserverClientImpl::tryAcquire() {123  if (!IsInitialized)124    return JobSlot();125 126  // The first acquisition is always for the implicit slot.127  if (HasImplicitSlot.exchange(false, std::memory_order_acquire)) {128    LLVM_DEBUG(dbgs() << "Acquired implicit job slot.\n");129    return JobSlot::createImplicit();130  }131 132  char Token;133  ssize_t Ret;134  LLVM_DEBUG(dbgs() << "Attempting to read token from FD " << ReadFD << ".\n");135  // Loop to retry on EINTR (interrupted system call).136  do {137    Ret = ::read(ReadFD, &Token, 1);138  } while (Ret < 0 && errno == EINTR);139 140  if (Ret == 1) {141    LLVM_DEBUG(dbgs() << "Acquired explicit token '" << Token << "'.\n");142    return JobSlot::createExplicit(static_cast<uint8_t>(Token));143  }144 145  LLVM_DEBUG(dbgs() << "Failed to acquire job slot, read returned " << Ret146                    << ".\n");147  return JobSlot();148}149 150/// Releases a job slot back to the pool. If the slot is implicit, it simply151/// resets a flag. If the slot is explicit, it writes the character token152/// associated with the slot back into the jobserver's write pipe. For FIFO153/// jobservers, this may require opening the FIFO for writing if it hasn't154/// been already.155void JobserverClientImpl::release(JobSlot Slot) {156  if (!Slot.isValid())157    return;158 159  // Releasing the implicit slot just makes it available for the next acquire.160  if (Slot.isImplicit()) {161    LLVM_DEBUG(dbgs() << "Released implicit job slot.\n");162    [[maybe_unused]] bool was_already_released =163        HasImplicitSlot.exchange(true, std::memory_order_release);164    assert(!was_already_released && "Implicit slot released twice");165    return;166  }167 168  uint8_t Token = Slot.getExplicitValue();169  LLVM_DEBUG(dbgs() << "Releasing explicit token '" << (char)Token << "' to FD "170                    << WriteFD << ".\n");171 172  // For FIFO-based jobservers, the write FD might not be open yet.173  // Open it on the first release.174  if (WriteFD < 0) {175    LLVM_DEBUG(dbgs() << "WriteFD is invalid, opening FIFO: " << FifoPath176                      << "\n");177    WriteFD = ::open(FifoPath.c_str(), O_WRONLY | O_CLOEXEC);178    if (WriteFD < 0) {179      LLVM_DEBUG(dbgs() << "Failed to open FIFO for writing.\n");180      return;181    }182    LLVM_DEBUG(dbgs() << "Opened FIFO as new WriteFD: " << WriteFD << "\n");183  }184 185  ssize_t Written;186  // Loop to retry on EINTR (interrupted system call).187  do {188    Written = ::write(WriteFD, &Token, 1);189  } while (Written < 0 && errno == EINTR);190 191  if (Written <= 0) {192    LLVM_DEBUG(dbgs() << "Failed to write token to pipe, write returned "193                      << Written << "\n");194  }195}196