LLVM 22.0.0git
Jobserver.inc
Go to the documentation of this file.
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-exception
6//
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} // namespace
38
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 drains
43/// all available tokens from the jobserver to determine the total number of
44/// 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 and
94 // 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 always
117/// successfully acquire the single "implicit" slot that is granted to every
118/// process started by `make`. Subsequent calls attempt to read a one-byte
119/// token from the jobserver's read pipe. A successful read grants one
120/// 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 " << Ret
146 << ".\n");
147 return JobSlot();
148}
149
150/// Releases a job slot back to the pool. If the slot is implicit, it simply
151/// resets a flag. If the slot is explicit, it writes the character token
152/// associated with the slot back into the jobserver's write pipe. For FIFO
153/// jobservers, this may require opening the FIFO for writing if it hasn't
154/// 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: " << FifoPath
176 << "\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}
assert(UImm &&(UImm !=~static_cast< T >(0)) &&"Invalid immediate!")
static void write(bool isBE, void *P, T V)
#define LLVM_DEBUG(...)
Definition Debug.h:114
LLVM_ABI raw_ostream & dbgs()
dbgs() - This returns a reference to a raw_ostream for debugging messages.
Definition Debug.cpp:207