blob: 0e9faa64edf41c5b95997615058fdd5ca13dc038 [file] [edit]
// Copyright 2025 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "jobserver_pool.h"
#include <assert.h>
#include "util.h"
#ifdef _WIN32
#include <windows.h>
class Win32JobserverPool : public JobserverPool {
public:
static std::unique_ptr<Win32JobserverPool> Create(size_t slot_count,
std::string* error) {
assert(slot_count > 1 && "slot_count must be 2 or higher");
auto pool = std::unique_ptr<Win32JobserverPool>(new Win32JobserverPool());
if (!pool->InitWithSemaphore(slot_count, error))
pool.reset();
return pool;
}
std::string GetEnvMakeFlagsValue() const override {
std::string result;
result.resize(sem_name_.size() + 32);
int ret =
snprintf(const_cast<char*>(result.data()), result.size(),
" -j%zd --jobserver-auth=%s", job_count_, sem_name_.c_str());
if (ret < 0 || ret > static_cast<int>(result.size()))
Fatal("Could not format Win32JobserverPool MAKEFLAGS!");
return result;
}
virtual ~Win32JobserverPool() {
if (IsValid())
::CloseHandle(handle_);
}
private:
Win32JobserverPool() = default;
// CreateSemaphore returns NULL on failure.
bool IsValid() const {
// CreateSemaphoreA() returns NULL on failure, not INVALID_HANDLE_VALUE.
return handle_ != NULL;
}
// Compute semaphore name for new instance.
static std::string GetSemaphoreName() {
// Use a per-process global counter to allow multiple instances of this
// class to run in the same process. Useful for unit-tests.
static int counter = 0;
counter += 1;
char name[64];
snprintf(name, sizeof(name), "ninja_jobserver_pool_%d_%d",
GetCurrentProcessId(), counter);
return std::string(name);
}
bool InitWithSemaphore(size_t slot_count, std::string* error) {
job_count_ = slot_count;
sem_name_ = GetSemaphoreName();
LONG count = static_cast<LONG>(slot_count - 1);
handle_ = ::CreateSemaphoreA(NULL, count, count, sem_name_.c_str());
if (!IsValid()) {
*error = "Could not create semaphore: " + GetLastErrorString();
return false;
}
return true;
}
// Semaphore handle.
HANDLE handle_ = NULL;
// Saved slot count.
size_t job_count_ = 0;
// Semaphore name.
std::string sem_name_;
};
#else // !_WIN32
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
class PosixJobserverPool : public JobserverPool {
public:
static std::unique_ptr<PosixJobserverPool> Create(size_t slot_count,
std::string* error) {
assert(slot_count > 1 && "slot_count must be 2 or higher");
auto pool = std::unique_ptr<PosixJobserverPool>(new PosixJobserverPool());
if (!pool->InitWithFifo(slot_count, error)) {
pool.reset();
}
return pool;
}
std::string GetEnvMakeFlagsValue() const override {
std::string result;
if (!fifo_.empty()) {
result.resize(fifo_.size() + 32);
int ret = snprintf(const_cast<char*>(result.data()), result.size(),
" -j%zd --jobserver-auth=fifo:%s", job_count_,
fifo_.c_str());
if (ret < 0 || ret > static_cast<int>(result.size()))
Fatal("Could not format PosixJobserverPool MAKEFLAGS!");
result.resize(static_cast<size_t>(ret));
}
return result;
}
virtual ~PosixJobserverPool() {
if (write_fd_ >= 0)
::close(write_fd_);
if (!fifo_.empty())
::unlink(fifo_.c_str());
}
private:
PosixJobserverPool() = default;
// Fill the pool to satisfy |slot_count| job slots. This
// writes |slot_count - 1| bytes to the pipe to satisfy the
// implicit job slot requirement.
bool FillSlots(size_t slot_count, std::string* error) {
job_count_ = slot_count;
while (slot_count > 1) {
// Write '+' into the pipe, just like GNU Make. Note that some
// implementations write '|' instead, but so far no client or pool
// implementation cares about the exact value, though the official spec
// says this might change in the future.
const char slot_char = '+';
ssize_t ret = ::write(write_fd_, &slot_char, 1);
if (ret == 1) {
slot_count--;
continue;
}
if (ret < 0 && errno == EINTR)
continue;
*error = std::string("Could not fill job slots pool: ") + strerror(errno);
return false;
}
return true;
}
bool InitWithFifo(size_t slot_count, std::string* error) {
const char* tmp_dir = getenv("TMPDIR");
if (!tmp_dir)
tmp_dir = "/tmp";
fifo_.resize(strlen(tmp_dir) + 32);
int len = snprintf(const_cast<char*>(fifo_.data()), fifo_.size(),
"%s/NinjaFIFO%d", tmp_dir, getpid());
if (len < 0) {
*error = "Cannot create fifo path!";
return false;
}
fifo_.resize(static_cast<size_t>(len));
int ret = mknod(fifo_.c_str(), S_IFIFO | 0666, 0);
if (ret < 0) {
*error = std::string("Cannot create fifo: ") + strerror(errno);
return false;
}
do {
write_fd_ = ::open(fifo_.c_str(), O_RDWR | O_CLOEXEC);
} while (write_fd_ < 0 && errno == EINTR);
if (write_fd_ < 0) {
*error = std::string("Could not open fifo: ") + strerror(errno);
// Let destructor remove the fifo.
return false;
}
return FillSlots(slot_count, error);
}
// Number of parallel job slots (including implicit one).
size_t job_count_ = 0;
// A non-inheritable file descriptor to keep the pool alive.
int write_fd_ = -1;
// Path to fifo, this will be empty when using an anonymous pipe.
std::string fifo_;
};
#endif // !_WIN32
// static
std::unique_ptr<JobserverPool> JobserverPool::Create(size_t num_job_slots,
std::string* error) {
if (num_job_slots < 2) {
*error = "At least 2 job slots needed";
return nullptr;
}
#ifdef _WIN32
return Win32JobserverPool::Create(num_job_slots, error);
#else // !_WIN32
return PosixJobserverPool::Create(num_job_slots, error);
#endif // !_WIN32
}