openwrt/tools/ninja/patches/100-make_jobserver_support.patch

4291 lines
130 KiB
Diff
Raw Normal View History

From 17d13fd7881fd3ce9f9b9d44ce435d6caf4b8f28 Mon Sep 17 00:00:00 2001
From: Stefan Becker <stefanb@gpartner-nvidia.com>
Date: Tue, 22 Mar 2016 13:48:07 +0200
Subject: [PATCH 01/11] Add GNU make jobserver client support
- add new TokenPool interface
- GNU make implementation for TokenPool parses and verifies the magic
information from the MAKEFLAGS environment variable
- RealCommandRunner tries to acquire TokenPool
* if no token pool is available then there is no change in behaviour
- When a token pool is available then RealCommandRunner behaviour
changes as follows
* CanRunMore() only returns true if TokenPool::Acquire() returns true
* StartCommand() calls TokenPool::Reserve()
* WaitForCommand() calls TokenPool::Release()
Documentation for GNU make jobserver
http://make.mad-scientist.net/papers/jobserver-implementation/
Fixes https://github.com/ninja-build/ninja/issues/1139
---
configure.py | 2 +
src/build.cc | 63 ++++++++----
src/build.h | 3 +
src/tokenpool-gnu-make.cc | 211 ++++++++++++++++++++++++++++++++++++++
src/tokenpool-none.cc | 27 +++++
src/tokenpool.h | 26 +++++
6 files changed, 310 insertions(+), 22 deletions(-)
create mode 100644 src/tokenpool-gnu-make.cc
create mode 100644 src/tokenpool-none.cc
create mode 100644 src/tokenpool.h
diff --git a/configure.py b/configure.py
index 43904349a8..db3492c93c 100755
--- a/configure.py
+++ b/configure.py
@@ -522,6 +522,7 @@ def has_re2c():
objs += cxx(name, variables=cxxvariables)
if platform.is_windows():
for name in ['subprocess-win32',
+ 'tokenpool-none',
'includes_normalize-win32',
'msvc_helper-win32',
'msvc_helper_main-win32']:
@@ -531,6 +532,7 @@ def has_re2c():
objs += cc('getopt')
else:
objs += cxx('subprocess-posix')
+ objs += cxx('tokenpool-gnu-make')
if platform.is_aix():
objs += cc('getopt')
if platform.is_msvc():
diff --git a/src/build.cc b/src/build.cc
index 6f11ed7a3c..fa096eac33 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -35,6 +35,7 @@
#include "state.h"
#include "status.h"
#include "subprocess.h"
+#include "tokenpool.h"
#include "util.h"
using namespace std;
@@ -149,7 +150,7 @@ void Plan::EdgeWanted(const Edge* edge) {
}
Edge* Plan::FindWork() {
- if (ready_.empty())
+ if (!more_ready())
return NULL;
EdgeSet::iterator e = ready_.begin();
Edge* edge = *e;
@@ -448,8 +449,8 @@ void Plan::Dump() const {
}
struct RealCommandRunner : public CommandRunner {
- explicit RealCommandRunner(const BuildConfig& config) : config_(config) {}
- virtual ~RealCommandRunner() {}
+ explicit RealCommandRunner(const BuildConfig& config);
+ virtual ~RealCommandRunner();
virtual bool CanRunMore() const;
virtual bool StartCommand(Edge* edge);
virtual bool WaitForCommand(Result* result);
@@ -458,9 +459,18 @@ struct RealCommandRunner : public CommandRunner {
const BuildConfig& config_;
SubprocessSet subprocs_;
+ TokenPool *tokens_;
map<const Subprocess*, Edge*> subproc_to_edge_;
};
+RealCommandRunner::RealCommandRunner(const BuildConfig& config) : config_(config) {
+ tokens_ = TokenPool::Get();
+}
+
+RealCommandRunner::~RealCommandRunner() {
+ delete tokens_;
+}
+
vector<Edge*> RealCommandRunner::GetActiveEdges() {
vector<Edge*> edges;
for (map<const Subprocess*, Edge*>::iterator e = subproc_to_edge_.begin();
@@ -471,14 +481,18 @@ vector<Edge*> RealCommandRunner::GetActiveEdges() {
void RealCommandRunner::Abort() {
subprocs_.Clear();
+ if (tokens_)
+ tokens_->Clear();
}
bool RealCommandRunner::CanRunMore() const {
size_t subproc_number =
subprocs_.running_.size() + subprocs_.finished_.size();
return (int)subproc_number < config_.parallelism
- && ((subprocs_.running_.empty() || config_.max_load_average <= 0.0f)
- || GetLoadAverage() < config_.max_load_average);
+ && (subprocs_.running_.empty() ||
+ ((config_.max_load_average <= 0.0f ||
+ GetLoadAverage() < config_.max_load_average)
+ && (!tokens_ || tokens_->Acquire())));
}
bool RealCommandRunner::StartCommand(Edge* edge) {
@@ -486,6 +500,8 @@ bool RealCommandRunner::StartCommand(Edge* edge) {
Subprocess* subproc = subprocs_.Add(command, edge->use_console());
if (!subproc)
return false;
+ if (tokens_)
+ tokens_->Reserve();
subproc_to_edge_.insert(make_pair(subproc, edge));
return true;
@@ -499,6 +515,9 @@ bool RealCommandRunner::WaitForCommand(Result* result) {
return false;
}
+ if (tokens_)
+ tokens_->Release();
+
result->status = subproc->Finish();
result->output = subproc->GetOutput();
@@ -621,31 +640,31 @@ bool Builder::Build(string* err) {
// Second, we attempt to wait for / reap the next finished command.
while (plan_.more_to_do()) {
// See if we can start any more commands.
- if (failures_allowed && command_runner_->CanRunMore()) {
- if (Edge* edge = plan_.FindWork()) {
- if (edge->GetBindingBool("generator")) {
+ if (failures_allowed && plan_.more_ready() &&
+ command_runner_->CanRunMore()) {
+ Edge* edge = plan_.FindWork();
+ if (edge->GetBindingBool("generator")) {
scan_.build_log()->Close();
}
- if (!StartEdge(edge, err)) {
+ if (!StartEdge(edge, err)) {
+ Cleanup();
+ status_->BuildFinished();
+ return false;
+ }
+
+ if (edge->is_phony()) {
+ if (!plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, err)) {
Cleanup();
status_->BuildFinished();
return false;
}
-
- if (edge->is_phony()) {
- if (!plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, err)) {
- Cleanup();
- status_->BuildFinished();
- return false;
- }
- } else {
- ++pending_commands;
- }
-
- // We made some progress; go back to the main loop.
- continue;
+ } else {
+ ++pending_commands;
}
+
+ // We made some progress; go back to the main loop.
+ continue;
}
// See if we can reap any finished commands.
diff --git a/src/build.h b/src/build.h
index d697dfb89e..7dcd111e61 100644
--- a/src/build.h
+++ b/src/build.h
@@ -52,6 +52,9 @@ struct Plan {
/// Returns true if there's more work to be done.
bool more_to_do() const { return wanted_edges_ > 0 && command_edges_ > 0; }
+ /// Returns true if there's more edges ready to start
+ bool more_ready() const { return !ready_.empty(); }
+
/// Dumps the current state of the plan.
void Dump() const;
diff --git a/src/tokenpool-gnu-make.cc b/src/tokenpool-gnu-make.cc
new file mode 100644
index 0000000000..a8f9b7139d
--- /dev/null
+++ b/src/tokenpool-gnu-make.cc
@@ -0,0 +1,211 @@
+// Copyright 2016 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 "tokenpool.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+// TokenPool implementation for GNU make jobserver
+// (http://make.mad-scientist.net/papers/jobserver-implementation/)
+struct GNUmakeTokenPool : public TokenPool {
+ GNUmakeTokenPool();
+ virtual ~GNUmakeTokenPool();
+
+ virtual bool Acquire();
+ virtual void Reserve();
+ virtual void Release();
+ virtual void Clear();
+
+ bool Setup();
+
+ private:
+ int available_;
+ int used_;
+
+#ifdef _WIN32
+ // @TODO
+#else
+ int rfd_;
+ int wfd_;
+
+ struct sigaction old_act_;
+ bool restore_;
+
+ static int dup_rfd_;
+ static void CloseDupRfd(int signum);
+
+ bool CheckFd(int fd);
+ bool SetAlarmHandler();
+#endif
+
+ void Return();
+};
+
+// every instance owns an implicit token -> available_ == 1
+GNUmakeTokenPool::GNUmakeTokenPool() : available_(1), used_(0),
+ rfd_(-1), wfd_(-1), restore_(false) {
+}
+
+GNUmakeTokenPool::~GNUmakeTokenPool() {
+ Clear();
+ if (restore_)
+ sigaction(SIGALRM, &old_act_, NULL);
+}
+
+bool GNUmakeTokenPool::CheckFd(int fd) {
+ if (fd < 0)
+ return false;
+ int ret = fcntl(fd, F_GETFD);
+ if (ret < 0)
+ return false;
+ return true;
+}
+
+int GNUmakeTokenPool::dup_rfd_ = -1;
+
+void GNUmakeTokenPool::CloseDupRfd(int signum) {
+ close(dup_rfd_);
+ dup_rfd_ = -1;
+}
+
+bool GNUmakeTokenPool::SetAlarmHandler() {
+ struct sigaction act;
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = CloseDupRfd;
+ if (sigaction(SIGALRM, &act, &old_act_) < 0) {
+ perror("sigaction:");
+ return(false);
+ } else {
+ restore_ = true;
+ return(true);
+ }
+}
+
+bool GNUmakeTokenPool::Setup() {
+ const char *value = getenv("MAKEFLAGS");
+ if (value) {
+ // GNU make <= 4.1
+ const char *jobserver = strstr(value, "--jobserver-fds=");
+ // GNU make => 4.2
+ if (!jobserver)
+ jobserver = strstr(value, "--jobserver-auth=");
+ if (jobserver) {
+ int rfd = -1;
+ int wfd = -1;
+ if ((sscanf(jobserver, "%*[^=]=%d,%d", &rfd, &wfd) == 2) &&
+ CheckFd(rfd) &&
+ CheckFd(wfd) &&
+ SetAlarmHandler()) {
+ printf("ninja: using GNU make jobserver.\n");
+ rfd_ = rfd;
+ wfd_ = wfd;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool GNUmakeTokenPool::Acquire() {
+ if (available_ > 0)
+ return true;
+
+#ifdef USE_PPOLL
+ pollfd pollfds[] = {{rfd_, POLLIN, 0}};
+ int ret = poll(pollfds, 1, 0);
+#else
+ fd_set set;
+ struct timeval timeout = { 0, 0 };
+ FD_ZERO(&set);
+ FD_SET(rfd_, &set);
+ int ret = select(rfd_ + 1, &set, NULL, NULL, &timeout);
+#endif
+ if (ret > 0) {
+ dup_rfd_ = dup(rfd_);
+
+ if (dup_rfd_ != -1) {
+ struct sigaction act, old_act;
+ int ret = 0;
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = CloseDupRfd;
+ if (sigaction(SIGCHLD, &act, &old_act) == 0) {
+ char buf;
+
+ // block until token read, child exits or timeout
+ alarm(1);
+ ret = read(dup_rfd_, &buf, 1);
+ alarm(0);
+
+ sigaction(SIGCHLD, &old_act, NULL);
+ }
+
+ CloseDupRfd(0);
+
+ if (ret > 0) {
+ available_++;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void GNUmakeTokenPool::Reserve() {
+ available_--;
+ used_++;
+}
+
+void GNUmakeTokenPool::Return() {
+ const char buf = '+';
+ while (1) {
+ int ret = write(wfd_, &buf, 1);
+ if (ret > 0)
+ available_--;
+ if ((ret != -1) || (errno != EINTR))
+ return;
+ // write got interrupted - retry
+ }
+}
+
+void GNUmakeTokenPool::Release() {
+ available_++;
+ used_--;
+ if (available_ > 1)
+ Return();
+}
+
+void GNUmakeTokenPool::Clear() {
+ while (used_ > 0)
+ Release();
+ while (available_ > 1)
+ Return();
+}
+
+struct TokenPool *TokenPool::Get(void) {
+ GNUmakeTokenPool *tokenpool = new GNUmakeTokenPool;
+ if (tokenpool->Setup())
+ return tokenpool;
+ else
+ delete tokenpool;
+ return NULL;
+}
diff --git a/src/tokenpool-none.cc b/src/tokenpool-none.cc
new file mode 100644
index 0000000000..602b3316f5
--- /dev/null
+++ b/src/tokenpool-none.cc
@@ -0,0 +1,27 @@
+// Copyright 2016 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 "tokenpool.h"
+
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+// No-op TokenPool implementation
+struct TokenPool *TokenPool::Get(void) {
+ return NULL;
+}
diff --git a/src/tokenpool.h b/src/tokenpool.h
new file mode 100644
index 0000000000..f560b1083b
--- /dev/null
+++ b/src/tokenpool.h
@@ -0,0 +1,26 @@
+// Copyright 2016 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.
+
+// interface to token pool
+struct TokenPool {
+ virtual ~TokenPool() {}
+
+ virtual bool Acquire() = 0;
+ virtual void Reserve() = 0;
+ virtual void Release() = 0;
+ virtual void Clear() = 0;
+
+ // returns NULL if token pool is not available
+ static struct TokenPool *Get(void);
+};
From ccaccc610cd456f6068758f82e72006364c7380b Mon Sep 17 00:00:00 2001
From: Stefan Becker <stefanb@gpartner-nvidia.com>
Date: Fri, 27 May 2016 16:47:10 +0300
Subject: [PATCH 02/11] Add TokenPool monitoring to SubprocessSet::DoWork()
Improve on the original jobserver client implementation. This makes
ninja a more aggressive GNU make jobserver client.
- add monitor interface to TokenPool
- TokenPool is passed down when main loop indicates that more work is
ready and would be allowed to start if a token becomes available
- posix: update DoWork() to monitor TokenPool read file descriptor
- WaitForCommand() exits when DoWork() sets token flag
- Main loop starts over when WaitForCommand() sets token exit status
---
src/build.cc | 53 +++++++++++++++++++++++++++++----------
src/build.h | 3 ++-
src/build_test.cc | 9 +++++--
src/exit_status.h | 3 ++-
src/subprocess-posix.cc | 33 ++++++++++++++++++++++--
src/subprocess-win32.cc | 2 +-
src/subprocess.h | 8 +++++-
src/subprocess_test.cc | 47 +++++++++++++++++++++++-----------
src/tokenpool-gnu-make.cc | 5 ++++
src/tokenpool.h | 6 +++++
10 files changed, 134 insertions(+), 35 deletions(-)
diff --git a/src/build.cc b/src/build.cc
index fa096eac33..a25c349050 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -48,8 +48,9 @@ struct DryRunCommandRunner : public CommandRunner {
// Overridden from CommandRunner:
virtual bool CanRunMore() const;
+ virtual bool AcquireToken();
virtual bool StartCommand(Edge* edge);
- virtual bool WaitForCommand(Result* result);
+ virtual bool WaitForCommand(Result* result, bool more_ready);
private:
queue<Edge*> finished_;
@@ -59,12 +60,16 @@ bool DryRunCommandRunner::CanRunMore() const {
return true;
}
+bool DryRunCommandRunner::AcquireToken() {
+ return true;
+}
+
bool DryRunCommandRunner::StartCommand(Edge* edge) {
finished_.push(edge);
return true;
}
-bool DryRunCommandRunner::WaitForCommand(Result* result) {
+bool DryRunCommandRunner::WaitForCommand(Result* result, bool more_ready) {
if (finished_.empty())
return false;
@@ -452,8 +457,9 @@ struct RealCommandRunner : public CommandRunner {
explicit RealCommandRunner(const BuildConfig& config);
virtual ~RealCommandRunner();
virtual bool CanRunMore() const;
+ virtual bool AcquireToken();
virtual bool StartCommand(Edge* edge);
- virtual bool WaitForCommand(Result* result);
+ virtual bool WaitForCommand(Result* result, bool more_ready);
virtual vector<Edge*> GetActiveEdges();
virtual void Abort();
@@ -490,9 +496,12 @@ bool RealCommandRunner::CanRunMore() const {
subprocs_.running_.size() + subprocs_.finished_.size();
return (int)subproc_number < config_.parallelism
&& (subprocs_.running_.empty() ||
- ((config_.max_load_average <= 0.0f ||
- GetLoadAverage() < config_.max_load_average)
- && (!tokens_ || tokens_->Acquire())));
+ (config_.max_load_average <= 0.0f ||
+ GetLoadAverage() < config_.max_load_average));
+}
+
+bool RealCommandRunner::AcquireToken() {
+ return (!tokens_ || tokens_->Acquire());
}
bool RealCommandRunner::StartCommand(Edge* edge) {
@@ -507,14 +516,23 @@ bool RealCommandRunner::StartCommand(Edge* edge) {
return true;
}
-bool RealCommandRunner::WaitForCommand(Result* result) {
+bool RealCommandRunner::WaitForCommand(Result* result, bool more_ready) {
Subprocess* subproc;
- while ((subproc = subprocs_.NextFinished()) == NULL) {
- bool interrupted = subprocs_.DoWork();
+ subprocs_.ResetTokenAvailable();
+ while (((subproc = subprocs_.NextFinished()) == NULL) &&
+ !subprocs_.IsTokenAvailable()) {
+ bool interrupted = subprocs_.DoWork(more_ready ? tokens_ : NULL);
if (interrupted)
return false;
}
+ // token became available
+ if (subproc == NULL) {
+ result->status = ExitTokenAvailable;
+ return true;
+ }
+
+ // command completed
if (tokens_)
tokens_->Release();
@@ -639,9 +657,14 @@ bool Builder::Build(string* err) {
// command runner.
// Second, we attempt to wait for / reap the next finished command.
while (plan_.more_to_do()) {
- // See if we can start any more commands.
- if (failures_allowed && plan_.more_ready() &&
- command_runner_->CanRunMore()) {
+ // See if we can start any more commands...
+ bool can_run_more =
+ failures_allowed &&
+ plan_.more_ready() &&
+ command_runner_->CanRunMore();
+
+ // ... but we also need a token to do that.
+ if (can_run_more && command_runner_->AcquireToken()) {
Edge* edge = plan_.FindWork();
if (edge->GetBindingBool("generator")) {
scan_.build_log()->Close();
@@ -670,7 +693,7 @@ bool Builder::Build(string* err) {
// See if we can reap any finished commands.
if (pending_commands) {
CommandRunner::Result result;
- if (!command_runner_->WaitForCommand(&result) ||
+ if (!command_runner_->WaitForCommand(&result, can_run_more) ||
result.status == ExitInterrupted) {
Cleanup();
status_->BuildFinished();
@@ -678,6 +701,10 @@ bool Builder::Build(string* err) {
return false;
}
+ // We might be able to start another command; start the main loop over.
+ if (result.status == ExitTokenAvailable)
+ continue;
+
--pending_commands;
if (!FinishCommand(&result, err)) {
Cleanup();
diff --git a/src/build.h b/src/build.h
index 7dcd111e61..35c7b97d12 100644
--- a/src/build.h
+++ b/src/build.h
@@ -139,6 +139,7 @@ struct Plan {
struct CommandRunner {
virtual ~CommandRunner() {}
virtual bool CanRunMore() const = 0;
+ virtual bool AcquireToken() = 0;
virtual bool StartCommand(Edge* edge) = 0;
/// The result of waiting for a command.
@@ -150,7 +151,7 @@ struct CommandRunner {
bool success() const { return status == ExitSuccess; }
};
/// Wait for a command to complete, or return false if interrupted.
- virtual bool WaitForCommand(Result* result) = 0;
+ virtual bool WaitForCommand(Result* result, bool more_ready) = 0;
virtual std::vector<Edge*> GetActiveEdges() { return std::vector<Edge*>(); }
virtual void Abort() {}
diff --git a/src/build_test.cc b/src/build_test.cc
index 4ef62b2113..7a5ff4015a 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -474,8 +474,9 @@ struct FakeCommandRunner : public CommandRunner {
// CommandRunner impl
virtual bool CanRunMore() const;
+ virtual bool AcquireToken();
virtual bool StartCommand(Edge* edge);
- virtual bool WaitForCommand(Result* result);
+ virtual bool WaitForCommand(Result* result, bool more_ready);
virtual vector<Edge*> GetActiveEdges();
virtual void Abort();
@@ -578,6 +579,10 @@ bool FakeCommandRunner::CanRunMore() const {
return active_edges_.size() < max_active_edges_;
}
+bool FakeCommandRunner::AcquireToken() {
+ return true;
+}
+
bool FakeCommandRunner::StartCommand(Edge* edge) {
assert(active_edges_.size() < max_active_edges_);
assert(find(active_edges_.begin(), active_edges_.end(), edge)
@@ -649,7 +654,7 @@ bool FakeCommandRunner::StartCommand(Edge* edge) {
return true;
}
-bool FakeCommandRunner::WaitForCommand(Result* result) {
+bool FakeCommandRunner::WaitForCommand(Result* result, bool more_ready) {
if (active_edges_.empty())
return false;
diff --git a/src/exit_status.h b/src/exit_status.h
index a714ece791..75ebf6a7a0 100644
--- a/src/exit_status.h
+++ b/src/exit_status.h
@@ -18,7 +18,8 @@
enum ExitStatus {
ExitSuccess,
ExitFailure,
- ExitInterrupted
+ ExitTokenAvailable,
+ ExitInterrupted,
};
#endif // NINJA_EXIT_STATUS_H_
diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc
index 8e785406c9..74451b0be2 100644
--- a/src/subprocess-posix.cc
+++ b/src/subprocess-posix.cc
@@ -13,6 +13,7 @@
// limitations under the License.
#include "subprocess.h"
+#include "tokenpool.h"
#include <sys/select.h>
#include <assert.h>
@@ -249,7 +250,7 @@ Subprocess *SubprocessSet::Add(const string& command, bool use_console) {
}
#ifdef USE_PPOLL
-bool SubprocessSet::DoWork() {
+bool SubprocessSet::DoWork(struct TokenPool* tokens) {
vector<pollfd> fds;
nfds_t nfds = 0;
@@ -263,6 +264,12 @@ bool SubprocessSet::DoWork() {
++nfds;
}
+ if (tokens) {
+ pollfd pfd = { tokens->GetMonitorFd(), POLLIN | POLLPRI, 0 };
+ fds.push_back(pfd);
+ ++nfds;
+ }
+
interrupted_ = 0;
int ret = ppoll(&fds.front(), nfds, NULL, &old_mask_);
if (ret == -1) {
@@ -295,11 +302,20 @@ bool SubprocessSet::DoWork() {
++i;
}
+ if (tokens) {
+ pollfd *pfd = &fds[nfds - 1];
+ if (pfd->fd >= 0) {
+ assert(pfd->fd == tokens->GetMonitorFd());
+ if (pfd->revents != 0)
+ token_available_ = true;
+ }
+ }
+
return IsInterrupted();
}
#else // !defined(USE_PPOLL)
-bool SubprocessSet::DoWork() {
+bool SubprocessSet::DoWork(struct TokenPool* tokens) {
fd_set set;
int nfds = 0;
FD_ZERO(&set);
@@ -314,6 +330,13 @@ bool SubprocessSet::DoWork() {
}
}
+ if (tokens) {
+ int fd = tokens->GetMonitorFd();
+ FD_SET(fd, &set);
+ if (nfds < fd+1)
+ nfds = fd+1;
+ }
+
interrupted_ = 0;
int ret = pselect(nfds, &set, 0, 0, 0, &old_mask_);
if (ret == -1) {
@@ -342,6 +365,12 @@ bool SubprocessSet::DoWork() {
++i;
}
+ if (tokens) {
+ int fd = tokens->GetMonitorFd();
+ if ((fd >= 0) && FD_ISSET(fd, &set))
+ token_available_ = true;
+ }
+
return IsInterrupted();
}
#endif // !defined(USE_PPOLL)
diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc
index ff3baaca7f..66d2c2c430 100644
--- a/src/subprocess-win32.cc
+++ b/src/subprocess-win32.cc
@@ -251,7 +251,7 @@ Subprocess *SubprocessSet::Add(const string& command, bool use_console) {
return subprocess;
}
-bool SubprocessSet::DoWork() {
+bool SubprocessSet::DoWork(struct TokenPool* tokens) {
DWORD bytes_read;
Subprocess* subproc;
OVERLAPPED* overlapped;
diff --git a/src/subprocess.h b/src/subprocess.h
index 9e3d2ee98f..9ea67ea477 100644
--- a/src/subprocess.h
+++ b/src/subprocess.h
@@ -76,6 +76,8 @@ struct Subprocess {
friend struct SubprocessSet;
};
+struct TokenPool;
+
/// SubprocessSet runs a ppoll/pselect() loop around a set of Subprocesses.
/// DoWork() waits for any state change in subprocesses; finished_
/// is a queue of subprocesses as they finish.
@@ -84,13 +86,17 @@ struct SubprocessSet {
~SubprocessSet();
Subprocess* Add(const std::string& command, bool use_console = false);
- bool DoWork();
+ bool DoWork(struct TokenPool* tokens);
Subprocess* NextFinished();
void Clear();
std::vector<Subprocess*> running_;
std::queue<Subprocess*> finished_;
+ bool token_available_;
+ bool IsTokenAvailable() { return token_available_; }
+ void ResetTokenAvailable() { token_available_ = false; }
+
#ifdef _WIN32
static BOOL WINAPI NotifyInterrupted(DWORD dwCtrlType);
static HANDLE ioport_;
diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc
index 073fe86931..4bc8083e26 100644
--- a/src/subprocess_test.cc
+++ b/src/subprocess_test.cc
@@ -45,10 +45,12 @@ TEST_F(SubprocessTest, BadCommandStderr) {
Subprocess* subproc = subprocs_.Add("cmd /c ninja_no_such_command");
ASSERT_NE((Subprocess *) 0, subproc);
+ subprocs_.ResetTokenAvailable();
while (!subproc->Done()) {
// Pretend we discovered that stderr was ready for writing.
- subprocs_.DoWork();
+ subprocs_.DoWork(NULL);
}
+ ASSERT_EQ(false, subprocs_.IsTokenAvailable());
EXPECT_EQ(ExitFailure, subproc->Finish());
EXPECT_NE("", subproc->GetOutput());
@@ -59,10 +61,12 @@ TEST_F(SubprocessTest, NoSuchCommand) {
Subprocess* subproc = subprocs_.Add("ninja_no_such_command");
ASSERT_NE((Subprocess *) 0, subproc);
+ subprocs_.ResetTokenAvailable();
while (!subproc->Done()) {
// Pretend we discovered that stderr was ready for writing.
- subprocs_.DoWork();
+ subprocs_.DoWork(NULL);
}
+ ASSERT_EQ(false, subprocs_.IsTokenAvailable());
EXPECT_EQ(ExitFailure, subproc->Finish());
EXPECT_NE("", subproc->GetOutput());
@@ -78,9 +82,11 @@ TEST_F(SubprocessTest, InterruptChild) {
Subprocess* subproc = subprocs_.Add("kill -INT $$");
ASSERT_NE((Subprocess *) 0, subproc);
+ subprocs_.ResetTokenAvailable();
while (!subproc->Done()) {
- subprocs_.DoWork();
+ subprocs_.DoWork(NULL);
}
+ ASSERT_EQ(false, subprocs_.IsTokenAvailable());
EXPECT_EQ(ExitInterrupted, subproc->Finish());
}
@@ -90,7 +96,7 @@ TEST_F(SubprocessTest, InterruptParent) {
ASSERT_NE((Subprocess *) 0, subproc);
while (!subproc->Done()) {
- bool interrupted = subprocs_.DoWork();
+ bool interrupted = subprocs_.DoWork(NULL);
if (interrupted)
return;
}
@@ -102,9 +108,11 @@ TEST_F(SubprocessTest, InterruptChildWithSigTerm) {
Subprocess* subproc = subprocs_.Add("kill -TERM $$");
ASSERT_NE((Subprocess *) 0, subproc);
+ subprocs_.ResetTokenAvailable();
while (!subproc->Done()) {
- subprocs_.DoWork();
+ subprocs_.DoWork(NULL);
}
+ ASSERT_EQ(false, subprocs_.IsTokenAvailable());
EXPECT_EQ(ExitInterrupted, subproc->Finish());
}
@@ -114,7 +122,7 @@ TEST_F(SubprocessTest, InterruptParentWithSigTerm) {
ASSERT_NE((Subprocess *) 0, subproc);
while (!subproc->Done()) {
- bool interrupted = subprocs_.DoWork();
+ bool interrupted = subprocs_.DoWork(NULL);
if (interrupted)
return;
}
@@ -126,9 +134,11 @@ TEST_F(SubprocessTest, InterruptChildWithSigHup) {
Subprocess* subproc = subprocs_.Add("kill -HUP $$");
ASSERT_NE((Subprocess *) 0, subproc);
+ subprocs_.ResetTokenAvailable();
while (!subproc->Done()) {
- subprocs_.DoWork();
+ subprocs_.DoWork(NULL);
}
+ ASSERT_EQ(false, subprocs_.IsTokenAvailable());
EXPECT_EQ(ExitInterrupted, subproc->Finish());
}
@@ -138,7 +148,7 @@ TEST_F(SubprocessTest, InterruptParentWithSigHup) {
ASSERT_NE((Subprocess *) 0, subproc);
while (!subproc->Done()) {
- bool interrupted = subprocs_.DoWork();
+ bool interrupted = subprocs_.DoWork(NULL);
if (interrupted)
return;
}
@@ -153,9 +163,11 @@ TEST_F(SubprocessTest, Console) {
subprocs_.Add("test -t 0 -a -t 1 -a -t 2", /*use_console=*/true);
ASSERT_NE((Subprocess*)0, subproc);
+ subprocs_.ResetTokenAvailable();
while (!subproc->Done()) {
- subprocs_.DoWork();
+ subprocs_.DoWork(NULL);
}
+ ASSERT_EQ(false, subprocs_.IsTokenAvailable());
EXPECT_EQ(ExitSuccess, subproc->Finish());
}
@@ -167,9 +179,11 @@ TEST_F(SubprocessTest, SetWithSingle) {
Subprocess* subproc = subprocs_.Add(kSimpleCommand);
ASSERT_NE((Subprocess *) 0, subproc);
+ subprocs_.ResetTokenAvailable();
while (!subproc->Done()) {
- subprocs_.DoWork();
+ subprocs_.DoWork(NULL);
}
+ ASSERT_EQ(false, subprocs_.IsTokenAvailable());
ASSERT_EQ(ExitSuccess, subproc->Finish());
ASSERT_NE("", subproc->GetOutput());
@@ -200,12 +214,13 @@ TEST_F(SubprocessTest, SetWithMulti) {
ASSERT_EQ("", processes[i]->GetOutput());
}
+ subprocs_.ResetTokenAvailable();
while (!processes[0]->Done() || !processes[1]->Done() ||
!processes[2]->Done()) {
ASSERT_GT(subprocs_.running_.size(), 0u);
- subprocs_.DoWork();
+ subprocs_.DoWork(NULL);
}
-
+ ASSERT_EQ(false, subprocs_.IsTokenAvailable());
ASSERT_EQ(0u, subprocs_.running_.size());
ASSERT_EQ(3u, subprocs_.finished_.size());
@@ -237,8 +252,10 @@ TEST_F(SubprocessTest, SetWithLots) {
ASSERT_NE((Subprocess *) 0, subproc);
procs.push_back(subproc);
}
+ subprocs_.ResetTokenAvailable();
while (!subprocs_.running_.empty())
- subprocs_.DoWork();
+ subprocs_.DoWork(NULL);
+ ASSERT_EQ(false, subprocs_.IsTokenAvailable());
for (size_t i = 0; i < procs.size(); ++i) {
ASSERT_EQ(ExitSuccess, procs[i]->Finish());
ASSERT_NE("", procs[i]->GetOutput());
@@ -254,9 +271,11 @@ TEST_F(SubprocessTest, SetWithLots) {
// that stdin is closed.
TEST_F(SubprocessTest, ReadStdin) {
Subprocess* subproc = subprocs_.Add("cat -");
+ subprocs_.ResetTokenAvailable();
while (!subproc->Done()) {
- subprocs_.DoWork();
+ subprocs_.DoWork(NULL);
}
+ ASSERT_EQ(false, subprocs_.IsTokenAvailable());
ASSERT_EQ(ExitSuccess, subproc->Finish());
ASSERT_EQ(1u, subprocs_.finished_.size());
}
diff --git a/src/tokenpool-gnu-make.cc b/src/tokenpool-gnu-make.cc
index a8f9b7139d..396bb7d874 100644
--- a/src/tokenpool-gnu-make.cc
+++ b/src/tokenpool-gnu-make.cc
@@ -33,6 +33,7 @@ struct GNUmakeTokenPool : public TokenPool {
virtual void Reserve();
virtual void Release();
virtual void Clear();
+ virtual int GetMonitorFd();
bool Setup();
@@ -201,6 +202,10 @@ void GNUmakeTokenPool::Clear() {
Return();
}
+int GNUmakeTokenPool::GetMonitorFd() {
+ return(rfd_);
+}
+
struct TokenPool *TokenPool::Get(void) {
GNUmakeTokenPool *tokenpool = new GNUmakeTokenPool;
if (tokenpool->Setup())
diff --git a/src/tokenpool.h b/src/tokenpool.h
index f560b1083b..301e1998ee 100644
--- a/src/tokenpool.h
+++ b/src/tokenpool.h
@@ -21,6 +21,12 @@ struct TokenPool {
virtual void Release() = 0;
virtual void Clear() = 0;
+#ifdef _WIN32
+ // @TODO
+#else
+ virtual int GetMonitorFd() = 0;
+#endif
+
// returns NULL if token pool is not available
static struct TokenPool *Get(void);
};
From d09f3d77821b3b1fdf09fc0ef8e814907675eafb Mon Sep 17 00:00:00 2001
From: Stefan Becker <chemobejk@gmail.com>
Date: Sun, 12 Nov 2017 16:58:55 +0200
Subject: [PATCH 03/11] Ignore jobserver when -jN is forced on command line
This emulates the behaviour of GNU make.
- add parallelism_from_cmdline flag to build configuration
- set the flag when -jN is given on command line
- pass the flag to TokenPool::Get()
- GNUmakeTokenPool::Setup()
* prints a warning when the flag is true and jobserver was detected
* returns false, i.e. jobserver will be ignored
- ignore config.parallelism in CanRunMore() when we have a valid
TokenPool, because it gets always initialized to a default when not
given on the command line
---
src/build.cc | 10 ++++++----
src/build.h | 4 +++-
src/ninja.cc | 1 +
src/tokenpool-gnu-make.cc | 34 +++++++++++++++++++---------------
src/tokenpool-none.cc | 4 ++--
src/tokenpool.h | 4 ++--
6 files changed, 33 insertions(+), 24 deletions(-)
diff --git a/src/build.cc b/src/build.cc
index a25c349050..406a84ec39 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -470,7 +470,7 @@ struct RealCommandRunner : public CommandRunner {
};
RealCommandRunner::RealCommandRunner(const BuildConfig& config) : config_(config) {
- tokens_ = TokenPool::Get();
+ tokens_ = TokenPool::Get(config_.parallelism_from_cmdline);
}
RealCommandRunner::~RealCommandRunner() {
@@ -492,9 +492,11 @@ void RealCommandRunner::Abort() {
}
bool RealCommandRunner::CanRunMore() const {
- size_t subproc_number =
- subprocs_.running_.size() + subprocs_.finished_.size();
- return (int)subproc_number < config_.parallelism
+ bool parallelism_limit_not_reached =
+ tokens_ || // ignore config_.parallelism
+ ((int) (subprocs_.running_.size() +
+ subprocs_.finished_.size()) < config_.parallelism);
+ return parallelism_limit_not_reached
&& (subprocs_.running_.empty() ||
(config_.max_load_average <= 0.0f ||
GetLoadAverage() < config_.max_load_average));
diff --git a/src/build.h b/src/build.h
index 35c7b97d12..dfde576573 100644
--- a/src/build.h
+++ b/src/build.h
@@ -159,7 +159,8 @@ struct CommandRunner {
/// Options (e.g. verbosity, parallelism) passed to a build.
struct BuildConfig {
- BuildConfig() : verbosity(NORMAL), dry_run(false), parallelism(1),
+ BuildConfig() : verbosity(NORMAL), dry_run(false),
+ parallelism(1), parallelism_from_cmdline(false),
failures_allowed(1), max_load_average(-0.0f) {}
enum Verbosity {
@@ -171,6 +172,7 @@ struct BuildConfig {
Verbosity verbosity;
bool dry_run;
int parallelism;
+ bool parallelism_from_cmdline;
int failures_allowed;
/// The maximum load average we must not exceed. A negative value
/// means that we do not have any limit.
diff --git a/src/ninja.cc b/src/ninja.cc
index df39ba92d1..d904c56c4e 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -1447,6 +1447,7 @@ int ReadFlags(int* argc, char*** argv,
// We want to run N jobs in parallel. For N = 0, INT_MAX
// is close enough to infinite for most sane builds.
config->parallelism = value > 0 ? value : INT_MAX;
+ config->parallelism_from_cmdline = true;
deferGuessParallelism.needGuess = false;
break;
}
diff --git a/src/tokenpool-gnu-make.cc b/src/tokenpool-gnu-make.cc
index 396bb7d874..af4be05a31 100644
--- a/src/tokenpool-gnu-make.cc
+++ b/src/tokenpool-gnu-make.cc
@@ -1,4 +1,4 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
+// Copyright 2016-2017 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.
@@ -35,7 +35,7 @@ struct GNUmakeTokenPool : public TokenPool {
virtual void Clear();
virtual int GetMonitorFd();
- bool Setup();
+ bool Setup(bool ignore);
private:
int available_;
@@ -100,7 +100,7 @@ bool GNUmakeTokenPool::SetAlarmHandler() {
}
}
-bool GNUmakeTokenPool::Setup() {
+bool GNUmakeTokenPool::Setup(bool ignore) {
const char *value = getenv("MAKEFLAGS");
if (value) {
// GNU make <= 4.1
@@ -109,16 +109,20 @@ bool GNUmakeTokenPool::Setup() {
if (!jobserver)
jobserver = strstr(value, "--jobserver-auth=");
if (jobserver) {
- int rfd = -1;
- int wfd = -1;
- if ((sscanf(jobserver, "%*[^=]=%d,%d", &rfd, &wfd) == 2) &&
- CheckFd(rfd) &&
- CheckFd(wfd) &&
- SetAlarmHandler()) {
- printf("ninja: using GNU make jobserver.\n");
- rfd_ = rfd;
- wfd_ = wfd;
- return true;
+ if (ignore) {
+ printf("ninja: warning: -jN forced on command line; ignoring GNU make jobserver.\n");
+ } else {
+ int rfd = -1;
+ int wfd = -1;
+ if ((sscanf(jobserver, "%*[^=]=%d,%d", &rfd, &wfd) == 2) &&
+ CheckFd(rfd) &&
+ CheckFd(wfd) &&
+ SetAlarmHandler()) {
+ printf("ninja: using GNU make jobserver.\n");
+ rfd_ = rfd;
+ wfd_ = wfd;
+ return true;
+ }
}
}
}
@@ -206,9 +210,9 @@ int GNUmakeTokenPool::GetMonitorFd() {
return(rfd_);
}
-struct TokenPool *TokenPool::Get(void) {
+struct TokenPool *TokenPool::Get(bool ignore) {
GNUmakeTokenPool *tokenpool = new GNUmakeTokenPool;
- if (tokenpool->Setup())
+ if (tokenpool->Setup(ignore))
return tokenpool;
else
delete tokenpool;
diff --git a/src/tokenpool-none.cc b/src/tokenpool-none.cc
index 602b3316f5..199b22264b 100644
--- a/src/tokenpool-none.cc
+++ b/src/tokenpool-none.cc
@@ -1,4 +1,4 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
+// Copyright 2016-2017 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.
@@ -22,6 +22,6 @@
#include <stdlib.h>
// No-op TokenPool implementation
-struct TokenPool *TokenPool::Get(void) {
+struct TokenPool *TokenPool::Get(bool ignore) {
return NULL;
}
diff --git a/src/tokenpool.h b/src/tokenpool.h
index 301e1998ee..878a0933c2 100644
--- a/src/tokenpool.h
+++ b/src/tokenpool.h
@@ -1,4 +1,4 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
+// Copyright 2016-2017 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.
@@ -28,5 +28,5 @@ struct TokenPool {
#endif
// returns NULL if token pool is not available
- static struct TokenPool *Get(void);
+ static struct TokenPool *Get(bool ignore);
};
From dfe4ca753caee65bf9041e2b4e883dfa172a5c6a Mon Sep 17 00:00:00 2001
From: Stefan Becker <chemobejk@gmail.com>
Date: Sun, 12 Nov 2017 18:04:12 +0200
Subject: [PATCH 04/11] Honor -lN from MAKEFLAGS
This emulates the behaviour of GNU make.
- build: make a copy of max_load_average and pass it to TokenPool.
- GNUmakeTokenPool: if we detect a jobserver and a valid -lN argument in
MAKEFLAGS then set max_load_average to N.
---
src/build.cc | 10 +++++++---
src/tokenpool-gnu-make.cc | 19 +++++++++++++++----
src/tokenpool-none.cc | 2 +-
src/tokenpool.h | 2 +-
4 files changed, 24 insertions(+), 9 deletions(-)
diff --git a/src/build.cc b/src/build.cc
index 406a84ec39..9e6272d035 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -464,13 +464,17 @@ struct RealCommandRunner : public CommandRunner {
virtual void Abort();
const BuildConfig& config_;
+ // copy of config_.max_load_average; can be modified by TokenPool setup
+ double max_load_average_;
SubprocessSet subprocs_;
TokenPool *tokens_;
map<const Subprocess*, Edge*> subproc_to_edge_;
};
RealCommandRunner::RealCommandRunner(const BuildConfig& config) : config_(config) {
- tokens_ = TokenPool::Get(config_.parallelism_from_cmdline);
+ max_load_average_ = config.max_load_average;
+ tokens_ = TokenPool::Get(config_.parallelism_from_cmdline,
+ max_load_average_);
}
RealCommandRunner::~RealCommandRunner() {
@@ -498,8 +502,8 @@ bool RealCommandRunner::CanRunMore() const {
subprocs_.finished_.size()) < config_.parallelism);
return parallelism_limit_not_reached
&& (subprocs_.running_.empty() ||
- (config_.max_load_average <= 0.0f ||
- GetLoadAverage() < config_.max_load_average));
+ (max_load_average_ <= 0.0f ||
+ GetLoadAverage() < max_load_average_));
}
bool RealCommandRunner::AcquireToken() {
diff --git a/src/tokenpool-gnu-make.cc b/src/tokenpool-gnu-make.cc
index af4be05a31..fb654c4d88 100644
--- a/src/tokenpool-gnu-make.cc
+++ b/src/tokenpool-gnu-make.cc
@@ -35,7 +35,7 @@ struct GNUmakeTokenPool : public TokenPool {
virtual void Clear();
virtual int GetMonitorFd();
- bool Setup(bool ignore);
+ bool Setup(bool ignore, double& max_load_average);
private:
int available_;
@@ -100,7 +100,7 @@ bool GNUmakeTokenPool::SetAlarmHandler() {
}
}
-bool GNUmakeTokenPool::Setup(bool ignore) {
+bool GNUmakeTokenPool::Setup(bool ignore, double& max_load_average) {
const char *value = getenv("MAKEFLAGS");
if (value) {
// GNU make <= 4.1
@@ -118,9 +118,20 @@ bool GNUmakeTokenPool::Setup(bool ignore) {
CheckFd(rfd) &&
CheckFd(wfd) &&
SetAlarmHandler()) {
+ const char *l_arg = strstr(value, " -l");
+ int load_limit = -1;
+
printf("ninja: using GNU make jobserver.\n");
rfd_ = rfd;
wfd_ = wfd;
+
+ // translate GNU make -lN to ninja -lN
+ if (l_arg &&
+ (sscanf(l_arg + 3, "%d ", &load_limit) == 1) &&
+ (load_limit > 0)) {
+ max_load_average = load_limit;
+ }
+
return true;
}
}
@@ -210,9 +221,9 @@ int GNUmakeTokenPool::GetMonitorFd() {
return(rfd_);
}
-struct TokenPool *TokenPool::Get(bool ignore) {
+struct TokenPool *TokenPool::Get(bool ignore, double& max_load_average) {
GNUmakeTokenPool *tokenpool = new GNUmakeTokenPool;
- if (tokenpool->Setup(ignore))
+ if (tokenpool->Setup(ignore, max_load_average))
return tokenpool;
else
delete tokenpool;
diff --git a/src/tokenpool-none.cc b/src/tokenpool-none.cc
index 199b22264b..e8e25426c3 100644
--- a/src/tokenpool-none.cc
+++ b/src/tokenpool-none.cc
@@ -22,6 +22,6 @@
#include <stdlib.h>
// No-op TokenPool implementation
-struct TokenPool *TokenPool::Get(bool ignore) {
+struct TokenPool *TokenPool::Get(bool ignore, double& max_load_average) {
return NULL;
}
diff --git a/src/tokenpool.h b/src/tokenpool.h
index 878a0933c2..f9e8cc2ee0 100644
--- a/src/tokenpool.h
+++ b/src/tokenpool.h
@@ -28,5 +28,5 @@ struct TokenPool {
#endif
// returns NULL if token pool is not available
- static struct TokenPool *Get(bool ignore);
+ static struct TokenPool *Get(bool ignore, double& max_load_average);
};
From 1c10047fc6a3269ba42839da19361e09cbc06ff0 Mon Sep 17 00:00:00 2001
From: Stefan Becker <chemobejk@gmail.com>
Date: Wed, 6 Dec 2017 22:14:21 +0200
Subject: [PATCH 05/11] Use LinePrinter for TokenPool messages
- replace printf() with calls to LinePrinter
- print GNU make jobserver message only when verbose build is requested
---
src/build.cc | 1 +
src/tokenpool-gnu-make.cc | 22 ++++++++++++++++------
src/tokenpool-none.cc | 4 +++-
src/tokenpool.h | 4 +++-
4 files changed, 23 insertions(+), 8 deletions(-)
diff --git a/src/build.cc b/src/build.cc
index 9e6272d035..662e4bd7be 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -474,6 +474,7 @@ struct RealCommandRunner : public CommandRunner {
RealCommandRunner::RealCommandRunner(const BuildConfig& config) : config_(config) {
max_load_average_ = config.max_load_average;
tokens_ = TokenPool::Get(config_.parallelism_from_cmdline,
+ config_.verbosity == BuildConfig::VERBOSE,
max_load_average_);
}
diff --git a/src/tokenpool-gnu-make.cc b/src/tokenpool-gnu-make.cc
index fb654c4d88..b0d3e6ebc4 100644
--- a/src/tokenpool-gnu-make.cc
+++ b/src/tokenpool-gnu-make.cc
@@ -23,6 +23,8 @@
#include <string.h>
#include <stdlib.h>
+#include "line_printer.h"
+
// TokenPool implementation for GNU make jobserver
// (http://make.mad-scientist.net/papers/jobserver-implementation/)
struct GNUmakeTokenPool : public TokenPool {
@@ -35,7 +37,7 @@ struct GNUmakeTokenPool : public TokenPool {
virtual void Clear();
virtual int GetMonitorFd();
- bool Setup(bool ignore, double& max_load_average);
+ bool Setup(bool ignore, bool verbose, double& max_load_average);
private:
int available_;
@@ -100,7 +102,9 @@ bool GNUmakeTokenPool::SetAlarmHandler() {
}
}
-bool GNUmakeTokenPool::Setup(bool ignore, double& max_load_average) {
+bool GNUmakeTokenPool::Setup(bool ignore,
+ bool verbose,
+ double& max_load_average) {
const char *value = getenv("MAKEFLAGS");
if (value) {
// GNU make <= 4.1
@@ -109,8 +113,10 @@ bool GNUmakeTokenPool::Setup(bool ignore, double& max_load_average) {
if (!jobserver)
jobserver = strstr(value, "--jobserver-auth=");
if (jobserver) {
+ LinePrinter printer;
+
if (ignore) {
- printf("ninja: warning: -jN forced on command line; ignoring GNU make jobserver.\n");
+ printer.PrintOnNewLine("ninja: warning: -jN forced on command line; ignoring GNU make jobserver.\n");
} else {
int rfd = -1;
int wfd = -1;
@@ -121,7 +127,9 @@ bool GNUmakeTokenPool::Setup(bool ignore, double& max_load_average) {
const char *l_arg = strstr(value, " -l");
int load_limit = -1;
- printf("ninja: using GNU make jobserver.\n");
+ if (verbose) {
+ printer.PrintOnNewLine("ninja: using GNU make jobserver.\n");
+ }
rfd_ = rfd;
wfd_ = wfd;
@@ -221,9 +229,11 @@ int GNUmakeTokenPool::GetMonitorFd() {
return(rfd_);
}
-struct TokenPool *TokenPool::Get(bool ignore, double& max_load_average) {
+struct TokenPool *TokenPool::Get(bool ignore,
+ bool verbose,
+ double& max_load_average) {
GNUmakeTokenPool *tokenpool = new GNUmakeTokenPool;
- if (tokenpool->Setup(ignore, max_load_average))
+ if (tokenpool->Setup(ignore, verbose, max_load_average))
return tokenpool;
else
delete tokenpool;
diff --git a/src/tokenpool-none.cc b/src/tokenpool-none.cc
index e8e25426c3..1c1c499c8d 100644
--- a/src/tokenpool-none.cc
+++ b/src/tokenpool-none.cc
@@ -22,6 +22,8 @@
#include <stdlib.h>
// No-op TokenPool implementation
-struct TokenPool *TokenPool::Get(bool ignore, double& max_load_average) {
+struct TokenPool *TokenPool::Get(bool ignore,
+ bool verbose,
+ double& max_load_average) {
return NULL;
}
diff --git a/src/tokenpool.h b/src/tokenpool.h
index f9e8cc2ee0..4bf477f20c 100644
--- a/src/tokenpool.h
+++ b/src/tokenpool.h
@@ -28,5 +28,7 @@ struct TokenPool {
#endif
// returns NULL if token pool is not available
- static struct TokenPool *Get(bool ignore, double& max_load_average);
+ static struct TokenPool *Get(bool ignore,
+ bool verbose,
+ double& max_load_average);
};
From fdbf68416e3574add3bffd0b637d0694fbaba320 Mon Sep 17 00:00:00 2001
From: Stefan Becker <chemobejk@gmail.com>
Date: Sat, 7 Apr 2018 17:11:21 +0300
Subject: [PATCH 06/11] Prepare PR for merging
- fix Windows build error in no-op TokenPool implementation
- improve Acquire() to block for a maximum of 100ms
- address review comments
---
src/build.h | 2 ++
src/tokenpool-gnu-make.cc | 53 +++++++++++++++++++++++++++++++++------
src/tokenpool-none.cc | 7 +-----
3 files changed, 49 insertions(+), 13 deletions(-)
diff --git a/src/build.h b/src/build.h
index dfde576573..66ddefb888 100644
--- a/src/build.h
+++ b/src/build.h
@@ -151,6 +151,8 @@ struct CommandRunner {
bool success() const { return status == ExitSuccess; }
};
/// Wait for a command to complete, or return false if interrupted.
+ /// If more_ready is true then the optional TokenPool is monitored too
+ /// and we return when a token becomes available.
virtual bool WaitForCommand(Result* result, bool more_ready) = 0;
virtual std::vector<Edge*> GetActiveEdges() { return std::vector<Edge*>(); }
diff --git a/src/tokenpool-gnu-make.cc b/src/tokenpool-gnu-make.cc
index b0d3e6ebc4..4132bb06d9 100644
--- a/src/tokenpool-gnu-make.cc
+++ b/src/tokenpool-gnu-make.cc
@@ -1,4 +1,4 @@
-// Copyright 2016-2017 Google Inc. All Rights Reserved.
+// Copyright 2016-2018 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.
@@ -19,6 +19,7 @@
#include <poll.h>
#include <unistd.h>
#include <signal.h>
+#include <sys/time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
@@ -153,6 +154,15 @@ bool GNUmakeTokenPool::Acquire() {
if (available_ > 0)
return true;
+ // Please read
+ //
+ // http://make.mad-scientist.net/papers/jobserver-implementation/
+ //
+ // for the reasoning behind the following code.
+ //
+ // Try to read one character from the pipe. Returns true on success.
+ //
+ // First check if read() would succeed without blocking.
#ifdef USE_PPOLL
pollfd pollfds[] = {{rfd_, POLLIN, 0}};
int ret = poll(pollfds, 1, 0);
@@ -164,33 +174,62 @@ bool GNUmakeTokenPool::Acquire() {
int ret = select(rfd_ + 1, &set, NULL, NULL, &timeout);
#endif
if (ret > 0) {
+ // Handle potential race condition:
+ // - the above check succeeded, i.e. read() should not block
+ // - the character disappears before we call read()
+ //
+ // Create a duplicate of rfd_. The duplicate file descriptor dup_rfd_
+ // can safely be closed by signal handlers without affecting rfd_.
dup_rfd_ = dup(rfd_);
if (dup_rfd_ != -1) {
struct sigaction act, old_act;
int ret = 0;
+ // Temporarily replace SIGCHLD handler with our own
memset(&act, 0, sizeof(act));
act.sa_handler = CloseDupRfd;
if (sigaction(SIGCHLD, &act, &old_act) == 0) {
- char buf;
-
- // block until token read, child exits or timeout
- alarm(1);
- ret = read(dup_rfd_, &buf, 1);
- alarm(0);
+ struct itimerval timeout;
+
+ // install a 100ms timeout that generates SIGALARM on expiration
+ memset(&timeout, 0, sizeof(timeout));
+ timeout.it_value.tv_usec = 100 * 1000; // [ms] -> [usec]
+ if (setitimer(ITIMER_REAL, &timeout, NULL) == 0) {
+ char buf;
+
+ // Now try to read() from dup_rfd_. Return values from read():
+ //
+ // 1. token read -> 1
+ // 2. pipe closed -> 0
+ // 3. alarm expires -> -1 (EINTR)
+ // 4. child exits -> -1 (EINTR)
+ // 5. alarm expired before entering read() -> -1 (EBADF)
+ // 6. child exited before entering read() -> -1 (EBADF)
+ // 7. child exited before handler is installed -> go to 1 - 3
+ ret = read(dup_rfd_, &buf, 1);
+
+ // disarm timer
+ memset(&timeout, 0, sizeof(timeout));
+ setitimer(ITIMER_REAL, &timeout, NULL);
+ }
sigaction(SIGCHLD, &old_act, NULL);
}
CloseDupRfd(0);
+ // Case 1 from above list
if (ret > 0) {
available_++;
return true;
}
}
}
+
+ // read() would block, i.e. no token available,
+ // cases 2-6 from above list or
+ // select() / poll() / dup() / sigaction() / setitimer() failed
return false;
}
diff --git a/src/tokenpool-none.cc b/src/tokenpool-none.cc
index 1c1c499c8d..4c592875b4 100644
--- a/src/tokenpool-none.cc
+++ b/src/tokenpool-none.cc
@@ -1,4 +1,4 @@
-// Copyright 2016-2017 Google Inc. All Rights Reserved.
+// Copyright 2016-2018 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.
@@ -14,11 +14,6 @@
#include "tokenpool.h"
-#include <fcntl.h>
-#include <poll.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <string.h>
#include <stdlib.h>
// No-op TokenPool implementation
From ec6220a0baf7d3a6eaf1a2b75bf8960ddfe24c2f Mon Sep 17 00:00:00 2001
From: Stefan Becker <chemobejk@gmail.com>
Date: Fri, 25 May 2018 00:17:07 +0300
Subject: [PATCH 07/11] Add tests for TokenPool
- TokenPool setup
- GetMonitorFd() API
- implicit token and tokens in jobserver pipe
- Acquire() / Reserve() / Release() protocol
- Clear() API
---
configure.py | 1 +
src/tokenpool_test.cc | 198 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 199 insertions(+)
create mode 100644 src/tokenpool_test.cc
diff --git a/configure.py b/configure.py
index db3492c93c..dc8a0066b7 100755
--- a/configure.py
+++ b/configure.py
@@ -590,6 +590,7 @@ def has_re2c():
'string_piece_util_test',
'subprocess_test',
'test',
+ 'tokenpool_test',
'util_test']:
objs += cxx(name, variables=cxxvariables)
if platform.is_windows():
diff --git a/src/tokenpool_test.cc b/src/tokenpool_test.cc
new file mode 100644
index 0000000000..6c89064ca4
--- /dev/null
+++ b/src/tokenpool_test.cc
@@ -0,0 +1,198 @@
+// Copyright 2018 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 "tokenpool.h"
+
+#include "test.h"
+
+#ifndef _WIN32
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define ENVIRONMENT_CLEAR() unsetenv("MAKEFLAGS")
+#define ENVIRONMENT_INIT(v) setenv("MAKEFLAGS", v, true);
+#endif
+
+namespace {
+
+const double kLoadAverageDefault = -1.23456789;
+
+struct TokenPoolTest : public testing::Test {
+ double load_avg_;
+ TokenPool *tokens_;
+#ifndef _WIN32
+ char buf_[1024];
+ int fds_[2];
+#endif
+
+ virtual void SetUp() {
+ load_avg_ = kLoadAverageDefault;
+ tokens_ = NULL;
+#ifndef _WIN32
+ ENVIRONMENT_CLEAR();
+ if (pipe(fds_) < 0)
+ ASSERT_TRUE(false);
+#endif
+ }
+
+ void CreatePool(const char *format, bool ignore_jobserver) {
+#ifndef _WIN32
+ if (format) {
+ sprintf(buf_, format, fds_[0], fds_[1]);
+ ENVIRONMENT_INIT(buf_);
+ }
+#endif
+ tokens_ = TokenPool::Get(ignore_jobserver, false, load_avg_);
+ }
+
+ void CreateDefaultPool() {
+ CreatePool("foo --jobserver-auth=%d,%d bar", false);
+ }
+
+ virtual void TearDown() {
+ if (tokens_)
+ delete tokens_;
+#ifndef _WIN32
+ close(fds_[0]);
+ close(fds_[1]);
+ ENVIRONMENT_CLEAR();
+#endif
+ }
+};
+
+} // anonymous namespace
+
+// verifies none implementation
+TEST_F(TokenPoolTest, NoTokenPool) {
+ CreatePool(NULL, false);
+
+ EXPECT_EQ(NULL, tokens_);
+ EXPECT_EQ(kLoadAverageDefault, load_avg_);
+}
+
+#ifndef _WIN32
+TEST_F(TokenPoolTest, SuccessfulOldSetup) {
+ // GNUmake <= 4.1
+ CreatePool("foo --jobserver-fds=%d,%d bar", false);
+
+ EXPECT_NE(NULL, tokens_);
+ EXPECT_EQ(kLoadAverageDefault, load_avg_);
+}
+
+TEST_F(TokenPoolTest, SuccessfulNewSetup) {
+ // GNUmake => 4.2
+ CreateDefaultPool();
+
+ EXPECT_NE(NULL, tokens_);
+ EXPECT_EQ(kLoadAverageDefault, load_avg_);
+}
+
+TEST_F(TokenPoolTest, IgnoreWithJN) {
+ CreatePool("foo --jobserver-auth=%d,%d bar", true);
+
+ EXPECT_EQ(NULL, tokens_);
+ EXPECT_EQ(kLoadAverageDefault, load_avg_);
+}
+
+TEST_F(TokenPoolTest, HonorLN) {
+ CreatePool("foo -l9 --jobserver-auth=%d,%d bar", false);
+
+ EXPECT_NE(NULL, tokens_);
+ EXPECT_EQ(9.0, load_avg_);
+}
+
+TEST_F(TokenPoolTest, MonitorFD) {
+ CreateDefaultPool();
+
+ ASSERT_NE(NULL, tokens_);
+ EXPECT_EQ(kLoadAverageDefault, load_avg_);
+
+ EXPECT_EQ(fds_[0], tokens_->GetMonitorFd());
+}
+
+TEST_F(TokenPoolTest, ImplicitToken) {
+ CreateDefaultPool();
+
+ ASSERT_NE(NULL, tokens_);
+ EXPECT_EQ(kLoadAverageDefault, load_avg_);
+
+ EXPECT_TRUE(tokens_->Acquire());
+ tokens_->Reserve();
+ EXPECT_FALSE(tokens_->Acquire());
+ tokens_->Release();
+ EXPECT_TRUE(tokens_->Acquire());
+}
+
+TEST_F(TokenPoolTest, TwoTokens) {
+ CreateDefaultPool();
+
+ ASSERT_NE(NULL, tokens_);
+ EXPECT_EQ(kLoadAverageDefault, load_avg_);
+
+ // implicit token
+ EXPECT_TRUE(tokens_->Acquire());
+ tokens_->Reserve();
+ EXPECT_FALSE(tokens_->Acquire());
+
+ // jobserver offers 2nd token
+ ASSERT_EQ(1u, write(fds_[1], "T", 1));
+ EXPECT_TRUE(tokens_->Acquire());
+ tokens_->Reserve();
+ EXPECT_FALSE(tokens_->Acquire());
+
+ // release 2nd token
+ tokens_->Release();
+ EXPECT_TRUE(tokens_->Acquire());
+
+ // release implict token - must return 2nd token back to jobserver
+ tokens_->Release();
+ EXPECT_TRUE(tokens_->Acquire());
+
+ // there must be one token in the pipe
+ EXPECT_EQ(1u, read(fds_[0], buf_, sizeof(buf_)));
+
+ // implicit token
+ EXPECT_TRUE(tokens_->Acquire());
+}
+
+TEST_F(TokenPoolTest, Clear) {
+ CreateDefaultPool();
+
+ ASSERT_NE(NULL, tokens_);
+ EXPECT_EQ(kLoadAverageDefault, load_avg_);
+
+ // implicit token
+ EXPECT_TRUE(tokens_->Acquire());
+ tokens_->Reserve();
+ EXPECT_FALSE(tokens_->Acquire());
+
+ // jobserver offers 2nd & 3rd token
+ ASSERT_EQ(2u, write(fds_[1], "TT", 2));
+ EXPECT_TRUE(tokens_->Acquire());
+ tokens_->Reserve();
+ EXPECT_TRUE(tokens_->Acquire());
+ tokens_->Reserve();
+ EXPECT_FALSE(tokens_->Acquire());
+
+ tokens_->Clear();
+ EXPECT_TRUE(tokens_->Acquire());
+
+ // there must be two tokens in the pipe
+ EXPECT_EQ(2u, read(fds_[0], buf_, sizeof(buf_)));
+
+ // implicit token
+ EXPECT_TRUE(tokens_->Acquire());
+}
+#endif
From e59d8858327126d1593fd0b8e607975a79072e92 Mon Sep 17 00:00:00 2001
From: Stefan Becker <chemobejk@gmail.com>
Date: Thu, 24 May 2018 18:52:45 +0300
Subject: [PATCH 08/11] Add tests for subprocess module
- add TokenPoolTest stub to provide TokenPool::GetMonitorFd()
- add two tests
* both tests set up a dummy GNUmake jobserver pipe
* both tests call DoWork() with TokenPoolTest
* test 1: verify that DoWork() detects when a token is available
* test 2: verify that DoWork() works as before without a token
- the tests are not compiled in under Windows
---
src/subprocess_test.cc | 76 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 76 insertions(+)
diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc
index 4bc8083e26..6264c8bf11 100644
--- a/src/subprocess_test.cc
+++ b/src/subprocess_test.cc
@@ -13,6 +13,7 @@
// limitations under the License.
#include "subprocess.h"
+#include "tokenpool.h"
#include "test.h"
@@ -34,8 +35,23 @@ const char* kSimpleCommand = "cmd /c dir \\";
const char* kSimpleCommand = "ls /";
#endif
+struct TokenPoolTest : public TokenPool {
+ bool Acquire() { return false; }
+ void Reserve() {}
+ void Release() {}
+ void Clear() {}
+
+#ifdef _WIN32
+ // @TODO
+#else
+ int _fd;
+ int GetMonitorFd() { return _fd; }
+#endif
+};
+
struct SubprocessTest : public testing::Test {
SubprocessSet subprocs_;
+ TokenPoolTest tokens_;
};
} // anonymous namespace
@@ -280,3 +296,63 @@ TEST_F(SubprocessTest, ReadStdin) {
ASSERT_EQ(1u, subprocs_.finished_.size());
}
#endif // _WIN32
+
+// @TODO: remove once TokenPool implementation for Windows is available
+#ifndef _WIN32
+TEST_F(SubprocessTest, TokenAvailable) {
+ Subprocess* subproc = subprocs_.Add(kSimpleCommand);
+ ASSERT_NE((Subprocess *) 0, subproc);
+
+ // simulate GNUmake jobserver pipe with 1 token
+ int fds[2];
+ ASSERT_EQ(0u, pipe(fds));
+ tokens_._fd = fds[0];
+ ASSERT_EQ(1u, write(fds[1], "T", 1));
+
+ subprocs_.ResetTokenAvailable();
+ subprocs_.DoWork(&tokens_);
+
+ EXPECT_TRUE(subprocs_.IsTokenAvailable());
+ EXPECT_EQ(0u, subprocs_.finished_.size());
+
+ // remove token to let DoWork() wait for command again
+ char token;
+ ASSERT_EQ(1u, read(fds[0], &token, 1));
+
+ while (!subproc->Done()) {
+ subprocs_.DoWork(&tokens_);
+ }
+
+ close(fds[1]);
+ close(fds[0]);
+
+ EXPECT_EQ(ExitSuccess, subproc->Finish());
+ EXPECT_NE("", subproc->GetOutput());
+
+ EXPECT_EQ(1u, subprocs_.finished_.size());
+}
+
+TEST_F(SubprocessTest, TokenNotAvailable) {
+ Subprocess* subproc = subprocs_.Add(kSimpleCommand);
+ ASSERT_NE((Subprocess *) 0, subproc);
+
+ // simulate GNUmake jobserver pipe with 0 tokens
+ int fds[2];
+ ASSERT_EQ(0u, pipe(fds));
+ tokens_._fd = fds[0];
+
+ subprocs_.ResetTokenAvailable();
+ while (!subproc->Done()) {
+ subprocs_.DoWork(&tokens_);
+ }
+
+ close(fds[1]);
+ close(fds[0]);
+
+ EXPECT_FALSE(subprocs_.IsTokenAvailable());
+ EXPECT_EQ(ExitSuccess, subproc->Finish());
+ EXPECT_NE("", subproc->GetOutput());
+
+ EXPECT_EQ(1u, subprocs_.finished_.size());
+}
+#endif // _WIN32
From 0145e2d4db64ea6c21aeb371928e4071f65164eb Mon Sep 17 00:00:00 2001
From: Stefan Becker <chemobejk@gmail.com>
Date: Sat, 26 May 2018 23:17:51 +0300
Subject: [PATCH 09/11] Add tests for build module
Add tests that verify the token functionality of the builder main loop.
We replace the default fake command runner with a special version where
the tests can control each call to AcquireToken(), CanRunMore() and
WaitForCommand().
---
src/build_test.cc | 364 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 364 insertions(+)
diff --git a/src/build_test.cc b/src/build_test.cc
index 7a5ff4015a..dd41dfbe1d 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -15,6 +15,7 @@
#include "build.h"
#include <assert.h>
+#include <stdarg.h>
#include "build_log.h"
#include "deps_log.h"
@@ -3990,3 +3991,366 @@ TEST_F(BuildTest, ValidationWithCircularDependency) {
EXPECT_FALSE(builder_.AddTarget("out", &err));
EXPECT_EQ("dependency cycle: validate -> validate_in -> validate", err);
}
+
+/// The token tests are concerned with the main loop functionality when
+// the CommandRunner has an active TokenPool. It is therefore intentional
+// that the plan doesn't complete and that builder_.Build() returns false!
+
+/// Fake implementation of CommandRunner that simulates a TokenPool
+struct FakeTokenCommandRunner : public CommandRunner {
+ explicit FakeTokenCommandRunner() {}
+
+ // CommandRunner impl
+ virtual bool CanRunMore() const;
+ virtual bool AcquireToken();
+ virtual bool StartCommand(Edge* edge);
+ virtual bool WaitForCommand(Result* result, bool more_ready);
+ virtual vector<Edge*> GetActiveEdges();
+ virtual void Abort();
+
+ vector<string> commands_ran_;
+ vector<Edge *> edges_;
+
+ vector<bool> acquire_token_;
+ vector<bool> can_run_more_;
+ vector<bool> wait_for_command_;
+};
+
+bool FakeTokenCommandRunner::CanRunMore() const {
+ if (can_run_more_.size() == 0) {
+ EXPECT_FALSE("unexpected call to CommandRunner::CanRunMore()");
+ return false;
+ }
+
+ bool result = can_run_more_[0];
+
+ // Unfortunately CanRunMore() isn't "const" for tests
+ const_cast<FakeTokenCommandRunner*>(this)->can_run_more_.erase(
+ const_cast<FakeTokenCommandRunner*>(this)->can_run_more_.begin()
+ );
+
+ return result;
+}
+
+bool FakeTokenCommandRunner::AcquireToken() {
+ if (acquire_token_.size() == 0) {
+ EXPECT_FALSE("unexpected call to CommandRunner::AcquireToken()");
+ return false;
+ }
+
+ bool result = acquire_token_[0];
+ acquire_token_.erase(acquire_token_.begin());
+ return result;
+}
+
+bool FakeTokenCommandRunner::StartCommand(Edge* edge) {
+ commands_ran_.push_back(edge->EvaluateCommand());
+ edges_.push_back(edge);
+ return true;
+}
+
+bool FakeTokenCommandRunner::WaitForCommand(Result* result, bool more_ready) {
+ if (wait_for_command_.size() == 0) {
+ EXPECT_FALSE("unexpected call to CommandRunner::WaitForCommand()");
+ return false;
+ }
+
+ bool expected = wait_for_command_[0];
+ if (expected != more_ready) {
+ EXPECT_EQ(expected, more_ready);
+ return false;
+ }
+ wait_for_command_.erase(wait_for_command_.begin());
+
+ if (edges_.size() == 0)
+ return false;
+
+ Edge* edge = edges_[0];
+ result->edge = edge;
+
+ if (more_ready &&
+ (edge->rule().name() == "token-available")) {
+ result->status = ExitTokenAvailable;
+ } else {
+ edges_.erase(edges_.begin());
+ result->status = ExitSuccess;
+ }
+
+ return true;
+}
+
+vector<Edge*> FakeTokenCommandRunner::GetActiveEdges() {
+ return edges_;
+}
+
+void FakeTokenCommandRunner::Abort() {
+ edges_.clear();
+}
+
+struct BuildTokenTest : public BuildTest {
+ virtual void SetUp();
+ virtual void TearDown();
+
+ FakeTokenCommandRunner token_command_runner_;
+
+ void ExpectAcquireToken(int count, ...);
+ void ExpectCanRunMore(int count, ...);
+ void ExpectWaitForCommand(int count, ...);
+
+private:
+ void EnqueueBooleans(vector<bool>& booleans, int count, va_list ao);
+};
+
+void BuildTokenTest::SetUp() {
+ BuildTest::SetUp();
+
+ // replace FakeCommandRunner with FakeTokenCommandRunner
+ builder_.command_runner_.release();
+ builder_.command_runner_.reset(&token_command_runner_);
+}
+void BuildTokenTest::TearDown() {
+ EXPECT_EQ(0u, token_command_runner_.acquire_token_.size());
+ EXPECT_EQ(0u, token_command_runner_.can_run_more_.size());
+ EXPECT_EQ(0u, token_command_runner_.wait_for_command_.size());
+
+ BuildTest::TearDown();
+}
+
+void BuildTokenTest::ExpectAcquireToken(int count, ...) {
+ va_list ap;
+ va_start(ap, count);
+ EnqueueBooleans(token_command_runner_.acquire_token_, count, ap);
+ va_end(ap);
+}
+
+void BuildTokenTest::ExpectCanRunMore(int count, ...) {
+ va_list ap;
+ va_start(ap, count);
+ EnqueueBooleans(token_command_runner_.can_run_more_, count, ap);
+ va_end(ap);
+}
+
+void BuildTokenTest::ExpectWaitForCommand(int count, ...) {
+ va_list ap;
+ va_start(ap, count);
+ EnqueueBooleans(token_command_runner_.wait_for_command_, count, ap);
+ va_end(ap);
+}
+
+void BuildTokenTest::EnqueueBooleans(vector<bool>& booleans, int count, va_list ap) {
+ while (count--) {
+ int value = va_arg(ap, int);
+ booleans.push_back(!!value); // force bool
+ }
+}
+
+TEST_F(BuildTokenTest, CompleteNoWork) {
+ // plan should not execute anything
+ string err;
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ EXPECT_EQ(0u, token_command_runner_.commands_ran_.size());
+}
+
+TEST_F(BuildTokenTest, DoNotAquireToken) {
+ // plan should execute one command
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("cat1", &err));
+ ASSERT_EQ("", err);
+
+ // pretend we can't run anything
+ ExpectCanRunMore(1, false);
+
+ EXPECT_FALSE(builder_.Build(&err));
+ EXPECT_EQ("stuck [this is a bug]", err);
+
+ EXPECT_EQ(0u, token_command_runner_.commands_ran_.size());
+}
+
+TEST_F(BuildTokenTest, DoNotStartWithoutToken) {
+ // plan should execute one command
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("cat1", &err));
+ ASSERT_EQ("", err);
+
+ // we could run a command but do not have a token for it
+ ExpectCanRunMore(1, true);
+ ExpectAcquireToken(1, false);
+
+ EXPECT_FALSE(builder_.Build(&err));
+ EXPECT_EQ("stuck [this is a bug]", err);
+
+ EXPECT_EQ(0u, token_command_runner_.commands_ran_.size());
+}
+
+TEST_F(BuildTokenTest, CompleteOneStep) {
+ // plan should execute one command
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("cat1", &err));
+ ASSERT_EQ("", err);
+
+ // allow running of one command
+ ExpectCanRunMore(1, true);
+ ExpectAcquireToken(1, true);
+ // block and wait for command to finalize
+ ExpectWaitForCommand(1, false);
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ EXPECT_EQ(1u, token_command_runner_.commands_ran_.size());
+ EXPECT_TRUE(token_command_runner_.commands_ran_[0] == "cat in1 > cat1");
+}
+
+TEST_F(BuildTokenTest, AcquireOneToken) {
+ // plan should execute more than one command
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("cat12", &err));
+ ASSERT_EQ("", err);
+
+ // allow running of one command
+ ExpectCanRunMore(3, true, false, false);
+ ExpectAcquireToken(1, true);
+ // block and wait for command to finalize
+ ExpectWaitForCommand(1, false);
+
+ EXPECT_FALSE(builder_.Build(&err));
+ EXPECT_EQ("stuck [this is a bug]", err);
+
+ EXPECT_EQ(1u, token_command_runner_.commands_ran_.size());
+ // any of the two dependencies could have been executed
+ EXPECT_TRUE(token_command_runner_.commands_ran_[0] == "cat in1 > cat1" ||
+ token_command_runner_.commands_ran_[0] == "cat in1 in2 > cat2");
+}
+
+TEST_F(BuildTokenTest, WantTwoTokens) {
+ // plan should execute more than one command
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("cat12", &err));
+ ASSERT_EQ("", err);
+
+ // allow running of one command
+ ExpectCanRunMore(3, true, true, false);
+ ExpectAcquireToken(2, true, false);
+ // wait for command to finalize or token to become available
+ ExpectWaitForCommand(1, true);
+
+ EXPECT_FALSE(builder_.Build(&err));
+ EXPECT_EQ("stuck [this is a bug]", err);
+
+ EXPECT_EQ(1u, token_command_runner_.commands_ran_.size());
+ // any of the two dependencies could have been executed
+ EXPECT_TRUE(token_command_runner_.commands_ran_[0] == "cat in1 > cat1" ||
+ token_command_runner_.commands_ran_[0] == "cat in1 in2 > cat2");
+}
+
+TEST_F(BuildTokenTest, CompleteTwoSteps) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build out1: cat in1\n"
+"build out2: cat out1\n"));
+
+ // plan should execute more than one command
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("out2", &err));
+ ASSERT_EQ("", err);
+
+ // allow running of two commands
+ ExpectCanRunMore(2, true, true);
+ ExpectAcquireToken(2, true, true);
+ // wait for commands to finalize
+ ExpectWaitForCommand(2, false, false);
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ EXPECT_EQ(2u, token_command_runner_.commands_ran_.size());
+ EXPECT_TRUE(token_command_runner_.commands_ran_[0] == "cat in1 > out1");
+ EXPECT_TRUE(token_command_runner_.commands_ran_[1] == "cat out1 > out2");
+}
+
+TEST_F(BuildTokenTest, TwoCommandsInParallel) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule token-available\n"
+" command = cat $in > $out\n"
+"build out1: token-available in1\n"
+"build out2: token-available in2\n"
+"build out12: cat out1 out2\n"));
+
+ // plan should execute more than one command
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("out12", &err));
+ ASSERT_EQ("", err);
+
+ // 1st command: token available -> allow running
+ // 2nd command: no token available but becomes available later
+ ExpectCanRunMore(4, true, true, true, false);
+ ExpectAcquireToken(3, true, false, true);
+ // 1st call waits for command to finalize or token to become available
+ // 2nd call waits for command to finalize
+ // 3rd call waits for command to finalize
+ ExpectWaitForCommand(3, true, false, false);
+
+ EXPECT_FALSE(builder_.Build(&err));
+ EXPECT_EQ("stuck [this is a bug]", err);
+
+ EXPECT_EQ(2u, token_command_runner_.commands_ran_.size());
+ EXPECT_TRUE((token_command_runner_.commands_ran_[0] == "cat in1 > out1" &&
+ token_command_runner_.commands_ran_[1] == "cat in2 > out2") ||
+ (token_command_runner_.commands_ran_[0] == "cat in2 > out2" &&
+ token_command_runner_.commands_ran_[1] == "cat in1 > out1"));
+}
+
+TEST_F(BuildTokenTest, CompleteThreeStepsSerial) {
+ // plan should execute more than one command
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("cat12", &err));
+ ASSERT_EQ("", err);
+
+ // allow running of all commands
+ ExpectCanRunMore(4, true, true, true, true);
+ ExpectAcquireToken(4, true, false, true, true);
+ // wait for commands to finalize
+ ExpectWaitForCommand(3, true, false, false);
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ EXPECT_EQ(3u, token_command_runner_.commands_ran_.size());
+ EXPECT_TRUE((token_command_runner_.commands_ran_[0] == "cat in1 > cat1" &&
+ token_command_runner_.commands_ran_[1] == "cat in1 in2 > cat2") ||
+ (token_command_runner_.commands_ran_[0] == "cat in1 in2 > cat2" &&
+ token_command_runner_.commands_ran_[1] == "cat in1 > cat1" ));
+ EXPECT_TRUE(token_command_runner_.commands_ran_[2] == "cat cat1 cat2 > cat12");
+}
+
+TEST_F(BuildTokenTest, CompleteThreeStepsParallel) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule token-available\n"
+" command = cat $in > $out\n"
+"build out1: token-available in1\n"
+"build out2: token-available in2\n"
+"build out12: cat out1 out2\n"));
+
+ // plan should execute more than one command
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("out12", &err));
+ ASSERT_EQ("", err);
+
+ // allow running of all commands
+ ExpectCanRunMore(4, true, true, true, true);
+ ExpectAcquireToken(4, true, false, true, true);
+ // wait for commands to finalize
+ ExpectWaitForCommand(4, true, false, false, false);
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ EXPECT_EQ(3u, token_command_runner_.commands_ran_.size());
+ EXPECT_TRUE((token_command_runner_.commands_ran_[0] == "cat in1 > out1" &&
+ token_command_runner_.commands_ran_[1] == "cat in2 > out2") ||
+ (token_command_runner_.commands_ran_[0] == "cat in2 > out2" &&
+ token_command_runner_.commands_ran_[1] == "cat in1 > out1"));
+ EXPECT_TRUE(token_command_runner_.commands_ran_[2] == "cat out1 out2 > out12");
+}
From f016e5430c9123d34a73ea7ad28693b20ee59d6d Mon Sep 17 00:00:00 2001
From: Stefan Becker <chemobejk@gmail.com>
Date: Mon, 8 Oct 2018 17:47:50 +0300
Subject: [PATCH 10/11] Add Win32 implementation for GNUmakeTokenPool
GNU make uses a semaphore as jobserver protocol on Win32. See also
https://www.gnu.org/software/make/manual/html_node/Windows-Jobserver.html
Usage is pretty simple and straightforward, i.e. WaitForSingleObject()
to obtain a token and ReleaseSemaphore() to return it.
Unfortunately subprocess-win32.cc uses an I/O completion port (IOCP).
IOCPs aren't waitable objects, i.e. we can't use WaitForMultipleObjects()
to wait on the IOCP and the token semaphore at the same time.
Therefore GNUmakeTokenPoolWin32 creates a child thread that waits on the
token semaphore and posts a dummy I/O completion status on the IOCP when
it was able to obtain a token. That unblocks SubprocessSet::DoWork() and
it can then check if a token became available or not.
- split existing GNUmakeTokenPool into common and platform bits
- add GNUmakeTokenPool interface
- move the Posix bits to GNUmakeTokenPoolPosix
- add the Win32 bits as GNUmakeTokenPoolWin32
- move Setup() method up to TokenPool interface
- update Subprocess & TokenPool tests accordingly
---
configure.py | 8 +-
src/build.cc | 11 +-
src/subprocess-win32.cc | 9 ++
src/subprocess_test.cc | 34 ++++-
src/tokenpool-gnu-make-posix.cc | 203 +++++++++++++++++++++++++++
src/tokenpool-gnu-make-win32.cc | 237 ++++++++++++++++++++++++++++++++
src/tokenpool-gnu-make.cc | 203 ++-------------------------
src/tokenpool-gnu-make.h | 40 ++++++
src/tokenpool-none.cc | 4 +-
src/tokenpool.h | 18 ++-
src/tokenpool_test.cc | 113 ++++++++++++---
11 files changed, 653 insertions(+), 227 deletions(-)
create mode 100644 src/tokenpool-gnu-make-posix.cc
create mode 100644 src/tokenpool-gnu-make-win32.cc
create mode 100644 src/tokenpool-gnu-make.h
diff --git a/configure.py b/configure.py
index dc8a0066b7..a239b90eef 100755
--- a/configure.py
+++ b/configure.py
@@ -517,12 +517,13 @@ def has_re2c():
'state',
'status',
'string_piece_util',
+ 'tokenpool-gnu-make',
'util',
'version']:
objs += cxx(name, variables=cxxvariables)
if platform.is_windows():
for name in ['subprocess-win32',
- 'tokenpool-none',
+ 'tokenpool-gnu-make-win32',
'includes_normalize-win32',
'msvc_helper-win32',
'msvc_helper_main-win32']:
@@ -531,8 +532,9 @@ def has_re2c():
objs += cxx('minidump-win32', variables=cxxvariables)
objs += cc('getopt')
else:
- objs += cxx('subprocess-posix')
- objs += cxx('tokenpool-gnu-make')
+ for name in ['subprocess-posix',
+ 'tokenpool-gnu-make-posix']:
+ objs += cxx(name)
if platform.is_aix():
objs += cc('getopt')
if platform.is_msvc():
diff --git a/src/build.cc b/src/build.cc
index 662e4bd7be..20c3bdc2a0 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -473,9 +473,14 @@ struct RealCommandRunner : public CommandRunner {
RealCommandRunner::RealCommandRunner(const BuildConfig& config) : config_(config) {
max_load_average_ = config.max_load_average;
- tokens_ = TokenPool::Get(config_.parallelism_from_cmdline,
- config_.verbosity == BuildConfig::VERBOSE,
- max_load_average_);
+ if ((tokens_ = TokenPool::Get()) != NULL) {
+ if (!tokens_->Setup(config_.parallelism_from_cmdline,
+ config_.verbosity == BuildConfig::VERBOSE,
+ max_load_average_)) {
+ delete tokens_;
+ tokens_ = NULL;
+ }
+ }
}
RealCommandRunner::~RealCommandRunner() {
diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc
index 66d2c2c430..ce3e2c20a4 100644
--- a/src/subprocess-win32.cc
+++ b/src/subprocess-win32.cc
@@ -13,6 +13,7 @@
// limitations under the License.
#include "subprocess.h"
+#include "tokenpool.h"
#include <assert.h>
#include <stdio.h>
@@ -256,6 +257,9 @@ bool SubprocessSet::DoWork(struct TokenPool* tokens) {
Subprocess* subproc;
OVERLAPPED* overlapped;
+ if (tokens)
+ tokens->WaitForTokenAvailability(ioport_);
+
if (!GetQueuedCompletionStatus(ioport_, &bytes_read, (PULONG_PTR)&subproc,
&overlapped, INFINITE)) {
if (GetLastError() != ERROR_BROKEN_PIPE)
@@ -266,6 +270,11 @@ bool SubprocessSet::DoWork(struct TokenPool* tokens) {
// delivered by NotifyInterrupted above.
return true;
+ if (tokens && tokens->TokenIsAvailable((ULONG_PTR)subproc)) {
+ token_available_ = true;
+ return false;
+ }
+
subproc->OnPipeReady();
if (subproc->Done()) {
diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc
index 6264c8bf11..f625963462 100644
--- a/src/subprocess_test.cc
+++ b/src/subprocess_test.cc
@@ -40,9 +40,16 @@ struct TokenPoolTest : public TokenPool {
void Reserve() {}
void Release() {}
void Clear() {}
+ bool Setup(bool ignore_unused, bool verbose, double& max_load_average) { return false; }
#ifdef _WIN32
- // @TODO
+ bool _token_available;
+ void WaitForTokenAvailability(HANDLE ioport) {
+ if (_token_available)
+ // unblock GetQueuedCompletionStatus()
+ PostQueuedCompletionStatus(ioport, 0, (ULONG_PTR) this, NULL);
+ }
+ bool TokenIsAvailable(ULONG_PTR key) { return key == (ULONG_PTR) this; }
#else
int _fd;
int GetMonitorFd() { return _fd; }
@@ -297,34 +304,48 @@ TEST_F(SubprocessTest, ReadStdin) {
}
#endif // _WIN32
-// @TODO: remove once TokenPool implementation for Windows is available
-#ifndef _WIN32
TEST_F(SubprocessTest, TokenAvailable) {
Subprocess* subproc = subprocs_.Add(kSimpleCommand);
ASSERT_NE((Subprocess *) 0, subproc);
// simulate GNUmake jobserver pipe with 1 token
+#ifdef _WIN32
+ tokens_._token_available = true;
+#else
int fds[2];
ASSERT_EQ(0u, pipe(fds));
tokens_._fd = fds[0];
ASSERT_EQ(1u, write(fds[1], "T", 1));
+#endif
subprocs_.ResetTokenAvailable();
subprocs_.DoWork(&tokens_);
+#ifdef _WIN32
+ tokens_._token_available = false;
+ // we need to loop here as we have no conrol where the token
+ // I/O completion post ends up in the queue
+ while (!subproc->Done() && !subprocs_.IsTokenAvailable()) {
+ subprocs_.DoWork(&tokens_);
+ }
+#endif
EXPECT_TRUE(subprocs_.IsTokenAvailable());
EXPECT_EQ(0u, subprocs_.finished_.size());
// remove token to let DoWork() wait for command again
+#ifndef _WIN32
char token;
ASSERT_EQ(1u, read(fds[0], &token, 1));
+#endif
while (!subproc->Done()) {
subprocs_.DoWork(&tokens_);
}
+#ifndef _WIN32
close(fds[1]);
close(fds[0]);
+#endif
EXPECT_EQ(ExitSuccess, subproc->Finish());
EXPECT_NE("", subproc->GetOutput());
@@ -337,17 +358,23 @@ TEST_F(SubprocessTest, TokenNotAvailable) {
ASSERT_NE((Subprocess *) 0, subproc);
// simulate GNUmake jobserver pipe with 0 tokens
+#ifdef _WIN32
+ tokens_._token_available = false;
+#else
int fds[2];
ASSERT_EQ(0u, pipe(fds));
tokens_._fd = fds[0];
+#endif
subprocs_.ResetTokenAvailable();
while (!subproc->Done()) {
subprocs_.DoWork(&tokens_);
}
+#ifndef _WIN32
close(fds[1]);
close(fds[0]);
+#endif
EXPECT_FALSE(subprocs_.IsTokenAvailable());
EXPECT_EQ(ExitSuccess, subproc->Finish());
@@ -355,4 +382,3 @@ TEST_F(SubprocessTest, TokenNotAvailable) {
EXPECT_EQ(1u, subprocs_.finished_.size());
}
-#endif // _WIN32
diff --git a/src/tokenpool-gnu-make-posix.cc b/src/tokenpool-gnu-make-posix.cc
new file mode 100644
index 0000000000..70d84bfff7
--- /dev/null
+++ b/src/tokenpool-gnu-make-posix.cc
@@ -0,0 +1,203 @@
+// Copyright 2016-2018 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 "tokenpool-gnu-make.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+// TokenPool implementation for GNU make jobserver - POSIX implementation
+// (http://make.mad-scientist.net/papers/jobserver-implementation/)
+struct GNUmakeTokenPoolPosix : public GNUmakeTokenPool {
+ GNUmakeTokenPoolPosix();
+ virtual ~GNUmakeTokenPoolPosix();
+
+ virtual int GetMonitorFd();
+
+ virtual const char *GetEnv(const char *name) { return getenv(name); };
+ virtual bool ParseAuth(const char *jobserver);
+ virtual bool AcquireToken();
+ virtual bool ReturnToken();
+
+ private:
+ int rfd_;
+ int wfd_;
+
+ struct sigaction old_act_;
+ bool restore_;
+
+ static int dup_rfd_;
+ static void CloseDupRfd(int signum);
+
+ bool CheckFd(int fd);
+ bool SetAlarmHandler();
+};
+
+GNUmakeTokenPoolPosix::GNUmakeTokenPoolPosix() : rfd_(-1), wfd_(-1), restore_(false) {
+}
+
+GNUmakeTokenPoolPosix::~GNUmakeTokenPoolPosix() {
+ Clear();
+ if (restore_)
+ sigaction(SIGALRM, &old_act_, NULL);
+}
+
+bool GNUmakeTokenPoolPosix::CheckFd(int fd) {
+ if (fd < 0)
+ return false;
+ int ret = fcntl(fd, F_GETFD);
+ if (ret < 0)
+ return false;
+ return true;
+}
+
+int GNUmakeTokenPoolPosix::dup_rfd_ = -1;
+
+void GNUmakeTokenPoolPosix::CloseDupRfd(int signum) {
+ close(dup_rfd_);
+ dup_rfd_ = -1;
+}
+
+bool GNUmakeTokenPoolPosix::SetAlarmHandler() {
+ struct sigaction act;
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = CloseDupRfd;
+ if (sigaction(SIGALRM, &act, &old_act_) < 0) {
+ perror("sigaction:");
+ return(false);
+ } else {
+ restore_ = true;
+ return(true);
+ }
+}
+
+bool GNUmakeTokenPoolPosix::ParseAuth(const char *jobserver) {
+ int rfd = -1;
+ int wfd = -1;
+ if ((sscanf(jobserver, "%*[^=]=%d,%d", &rfd, &wfd) == 2) &&
+ CheckFd(rfd) &&
+ CheckFd(wfd) &&
+ SetAlarmHandler()) {
+ rfd_ = rfd;
+ wfd_ = wfd;
+ return true;
+ }
+
+ return false;
+}
+
+bool GNUmakeTokenPoolPosix::AcquireToken() {
+ // Please read
+ //
+ // http://make.mad-scientist.net/papers/jobserver-implementation/
+ //
+ // for the reasoning behind the following code.
+ //
+ // Try to read one character from the pipe. Returns true on success.
+ //
+ // First check if read() would succeed without blocking.
+#ifdef USE_PPOLL
+ pollfd pollfds[] = {{rfd_, POLLIN, 0}};
+ int ret = poll(pollfds, 1, 0);
+#else
+ fd_set set;
+ struct timeval timeout = { 0, 0 };
+ FD_ZERO(&set);
+ FD_SET(rfd_, &set);
+ int ret = select(rfd_ + 1, &set, NULL, NULL, &timeout);
+#endif
+ if (ret > 0) {
+ // Handle potential race condition:
+ // - the above check succeeded, i.e. read() should not block
+ // - the character disappears before we call read()
+ //
+ // Create a duplicate of rfd_. The duplicate file descriptor dup_rfd_
+ // can safely be closed by signal handlers without affecting rfd_.
+ dup_rfd_ = dup(rfd_);
+
+ if (dup_rfd_ != -1) {
+ struct sigaction act, old_act;
+ int ret = 0;
+
+ // Temporarily replace SIGCHLD handler with our own
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = CloseDupRfd;
+ if (sigaction(SIGCHLD, &act, &old_act) == 0) {
+ struct itimerval timeout;
+
+ // install a 100ms timeout that generates SIGALARM on expiration
+ memset(&timeout, 0, sizeof(timeout));
+ timeout.it_value.tv_usec = 100 * 1000; // [ms] -> [usec]
+ if (setitimer(ITIMER_REAL, &timeout, NULL) == 0) {
+ char buf;
+
+ // Now try to read() from dup_rfd_. Return values from read():
+ //
+ // 1. token read -> 1
+ // 2. pipe closed -> 0
+ // 3. alarm expires -> -1 (EINTR)
+ // 4. child exits -> -1 (EINTR)
+ // 5. alarm expired before entering read() -> -1 (EBADF)
+ // 6. child exited before entering read() -> -1 (EBADF)
+ // 7. child exited before handler is installed -> go to 1 - 3
+ ret = read(dup_rfd_, &buf, 1);
+
+ // disarm timer
+ memset(&timeout, 0, sizeof(timeout));
+ setitimer(ITIMER_REAL, &timeout, NULL);
+ }
+
+ sigaction(SIGCHLD, &old_act, NULL);
+ }
+
+ CloseDupRfd(0);
+
+ // Case 1 from above list
+ if (ret > 0)
+ return true;
+ }
+ }
+
+ // read() would block, i.e. no token available,
+ // cases 2-6 from above list or
+ // select() / poll() / dup() / sigaction() / setitimer() failed
+ return false;
+}
+
+bool GNUmakeTokenPoolPosix::ReturnToken() {
+ const char buf = '+';
+ while (1) {
+ int ret = write(wfd_, &buf, 1);
+ if (ret > 0)
+ return true;
+ if ((ret != -1) || (errno != EINTR))
+ return false;
+ // write got interrupted - retry
+ }
+}
+
+int GNUmakeTokenPoolPosix::GetMonitorFd() {
+ return(rfd_);
+}
+
+struct TokenPool *TokenPool::Get() {
+ return new GNUmakeTokenPoolPosix;
+}
diff --git a/src/tokenpool-gnu-make-win32.cc b/src/tokenpool-gnu-make-win32.cc
new file mode 100644
index 0000000000..2719f2c1fc
--- /dev/null
+++ b/src/tokenpool-gnu-make-win32.cc
@@ -0,0 +1,237 @@
+// Copyright 2018 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 "tokenpool-gnu-make.h"
+
+// always include first to make sure other headers do the correct thing...
+#include <windows.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+// TokenPool implementation for GNU make jobserver - Win32 implementation
+// (https://www.gnu.org/software/make/manual/html_node/Windows-Jobserver.html)
+struct GNUmakeTokenPoolWin32 : public GNUmakeTokenPool {
+ GNUmakeTokenPoolWin32();
+ virtual ~GNUmakeTokenPoolWin32();
+
+ virtual void WaitForTokenAvailability(HANDLE ioport);
+ virtual bool TokenIsAvailable(ULONG_PTR key);
+
+ virtual const char *GetEnv(const char *name);
+ virtual bool ParseAuth(const char *jobserver);
+ virtual bool AcquireToken();
+ virtual bool ReturnToken();
+
+ private:
+ // Semaphore for GNU make jobserver protocol
+ HANDLE semaphore_jobserver_;
+ // Semaphore Child -> Parent
+ // - child releases it before entering wait on jobserver semaphore
+ // - parent blocks on it to know when child enters wait
+ HANDLE semaphore_enter_wait_;
+ // Semaphore Parent -> Child
+ // - parent releases it to allow child to restart loop
+ // - child blocks on it to know when to restart loop
+ HANDLE semaphore_restart_;
+ // set to false if child should exit loop and terminate thread
+ bool running_;
+ // child thread
+ HANDLE child_;
+ // I/O completion port from SubprocessSet
+ HANDLE ioport_;
+
+
+ DWORD SemaphoreThread();
+ void ReleaseSemaphore(HANDLE semaphore);
+ void WaitForObject(HANDLE object);
+ static DWORD WINAPI SemaphoreThreadWrapper(LPVOID param);
+ static void NoopAPCFunc(ULONG_PTR param);
+};
+
+GNUmakeTokenPoolWin32::GNUmakeTokenPoolWin32() : semaphore_jobserver_(NULL),
+ semaphore_enter_wait_(NULL),
+ semaphore_restart_(NULL),
+ running_(false),
+ child_(NULL),
+ ioport_(NULL) {
+}
+
+GNUmakeTokenPoolWin32::~GNUmakeTokenPoolWin32() {
+ Clear();
+ CloseHandle(semaphore_jobserver_);
+ semaphore_jobserver_ = NULL;
+
+ if (child_) {
+ // tell child thread to exit
+ running_ = false;
+ ReleaseSemaphore(semaphore_restart_);
+
+ // wait for child thread to exit
+ WaitForObject(child_);
+ CloseHandle(child_);
+ child_ = NULL;
+ }
+
+ if (semaphore_restart_) {
+ CloseHandle(semaphore_restart_);
+ semaphore_restart_ = NULL;
+ }
+
+ if (semaphore_enter_wait_) {
+ CloseHandle(semaphore_enter_wait_);
+ semaphore_enter_wait_ = NULL;
+ }
+}
+
+const char *GNUmakeTokenPoolWin32::GetEnv(const char *name) {
+ // getenv() does not work correctly together with tokenpool_tests.cc
+ static char buffer[MAX_PATH + 1];
+ if (GetEnvironmentVariable("MAKEFLAGS", buffer, sizeof(buffer)) == 0)
+ return NULL;
+ return(buffer);
+}
+
+bool GNUmakeTokenPoolWin32::ParseAuth(const char *jobserver) {
+ // match "--jobserver-auth=gmake_semaphore_<INTEGER>..."
+ const char *start = strchr(jobserver, '=');
+ if (start) {
+ const char *end = start;
+ unsigned int len;
+ char c, *auth;
+
+ while ((c = *++end) != '\0')
+ if (!(isalnum(c) || (c == '_')))
+ break;
+ len = end - start; // includes string terminator in count
+
+ if ((len > 1) && ((auth = (char *)malloc(len)) != NULL)) {
+ strncpy(auth, start + 1, len - 1);
+ auth[len - 1] = '\0';
+
+ if ((semaphore_jobserver_ = OpenSemaphore(SEMAPHORE_ALL_ACCESS, /* Semaphore access setting */
+ FALSE, /* Child processes DON'T inherit */
+ auth /* Semaphore name */
+ )) != NULL) {
+ free(auth);
+ return true;
+ }
+
+ free(auth);
+ }
+ }
+
+ return false;
+}
+
+bool GNUmakeTokenPoolWin32::AcquireToken() {
+ return WaitForSingleObject(semaphore_jobserver_, 0) == WAIT_OBJECT_0;
+}
+
+bool GNUmakeTokenPoolWin32::ReturnToken() {
+ ReleaseSemaphore(semaphore_jobserver_);
+ return true;
+}
+
+DWORD GNUmakeTokenPoolWin32::SemaphoreThread() {
+ while (running_) {
+ // indicate to parent that we are entering wait
+ ReleaseSemaphore(semaphore_enter_wait_);
+
+ // alertable wait forever on token semaphore
+ if (WaitForSingleObjectEx(semaphore_jobserver_, INFINITE, TRUE) == WAIT_OBJECT_0) {
+ // release token again for AcquireToken()
+ ReleaseSemaphore(semaphore_jobserver_);
+
+ // indicate to parent on ioport that a token might be available
+ if (!PostQueuedCompletionStatus(ioport_, 0, (ULONG_PTR) this, NULL))
+ Win32Fatal("PostQueuedCompletionStatus");
+ }
+
+ // wait for parent to allow loop restart
+ WaitForObject(semaphore_restart_);
+ // semaphore is now in nonsignaled state again for next run...
+ }
+
+ return 0;
+}
+
+DWORD WINAPI GNUmakeTokenPoolWin32::SemaphoreThreadWrapper(LPVOID param) {
+ GNUmakeTokenPoolWin32 *This = (GNUmakeTokenPoolWin32 *) param;
+ return This->SemaphoreThread();
+}
+
+void GNUmakeTokenPoolWin32::NoopAPCFunc(ULONG_PTR param) {
+}
+
+void GNUmakeTokenPoolWin32::WaitForTokenAvailability(HANDLE ioport) {
+ if (child_ == NULL) {
+ // first invocation
+ //
+ // subprocess-win32.cc uses I/O completion port (IOCP) which can't be
+ // used as a waitable object. Therefore we can't use WaitMultipleObjects()
+ // to wait on the IOCP and the token semaphore at the same time. Create
+ // a child thread that waits on the semaphore and posts an I/O completion
+ ioport_ = ioport;
+
+ // create both semaphores in nonsignaled state
+ if ((semaphore_enter_wait_ = CreateSemaphore(NULL, 0, 1, NULL))
+ == NULL)
+ Win32Fatal("CreateSemaphore/enter_wait");
+ if ((semaphore_restart_ = CreateSemaphore(NULL, 0, 1, NULL))
+ == NULL)
+ Win32Fatal("CreateSemaphore/restart");
+
+ // start child thread
+ running_ = true;
+ if ((child_ = CreateThread(NULL, 0, &SemaphoreThreadWrapper, this, 0, NULL))
+ == NULL)
+ Win32Fatal("CreateThread");
+
+ } else {
+ // all further invocations - allow child thread to loop
+ ReleaseSemaphore(semaphore_restart_);
+ }
+
+ // wait for child thread to enter wait
+ WaitForObject(semaphore_enter_wait_);
+ // semaphore is now in nonsignaled state again for next run...
+
+ // now SubprocessSet::DoWork() can enter GetQueuedCompletionStatus()...
+}
+
+bool GNUmakeTokenPoolWin32::TokenIsAvailable(ULONG_PTR key) {
+ // alert child thread to break wait on token semaphore
+ QueueUserAPC(&NoopAPCFunc, child_, (ULONG_PTR)NULL);
+
+ // return true when GetQueuedCompletionStatus() returned our key
+ return key == (ULONG_PTR) this;
+}
+
+void GNUmakeTokenPoolWin32::ReleaseSemaphore(HANDLE semaphore) {
+ if (!::ReleaseSemaphore(semaphore, 1, NULL))
+ Win32Fatal("ReleaseSemaphore");
+}
+
+void GNUmakeTokenPoolWin32::WaitForObject(HANDLE object) {
+ if (WaitForSingleObject(object, INFINITE) != WAIT_OBJECT_0)
+ Win32Fatal("WaitForSingleObject");
+}
+
+struct TokenPool *TokenPool::Get() {
+ return new GNUmakeTokenPoolWin32;
+}
diff --git a/src/tokenpool-gnu-make.cc b/src/tokenpool-gnu-make.cc
index 4132bb06d9..92ff611721 100644
--- a/src/tokenpool-gnu-make.cc
+++ b/src/tokenpool-gnu-make.cc
@@ -12,101 +12,26 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "tokenpool.h"
+#include "tokenpool-gnu-make.h"
-#include <errno.h>
-#include <fcntl.h>
-#include <poll.h>
-#include <unistd.h>
-#include <signal.h>
-#include <sys/time.h>
+#include <stdlib.h>
#include <stdio.h>
#include <string.h>
-#include <stdlib.h>
#include "line_printer.h"
-// TokenPool implementation for GNU make jobserver
-// (http://make.mad-scientist.net/papers/jobserver-implementation/)
-struct GNUmakeTokenPool : public TokenPool {
- GNUmakeTokenPool();
- virtual ~GNUmakeTokenPool();
-
- virtual bool Acquire();
- virtual void Reserve();
- virtual void Release();
- virtual void Clear();
- virtual int GetMonitorFd();
-
- bool Setup(bool ignore, bool verbose, double& max_load_average);
-
- private:
- int available_;
- int used_;
-
-#ifdef _WIN32
- // @TODO
-#else
- int rfd_;
- int wfd_;
-
- struct sigaction old_act_;
- bool restore_;
-
- static int dup_rfd_;
- static void CloseDupRfd(int signum);
-
- bool CheckFd(int fd);
- bool SetAlarmHandler();
-#endif
-
- void Return();
-};
-
+// TokenPool implementation for GNU make jobserver - common bits
// every instance owns an implicit token -> available_ == 1
-GNUmakeTokenPool::GNUmakeTokenPool() : available_(1), used_(0),
- rfd_(-1), wfd_(-1), restore_(false) {
+GNUmakeTokenPool::GNUmakeTokenPool() : available_(1), used_(0) {
}
GNUmakeTokenPool::~GNUmakeTokenPool() {
- Clear();
- if (restore_)
- sigaction(SIGALRM, &old_act_, NULL);
-}
-
-bool GNUmakeTokenPool::CheckFd(int fd) {
- if (fd < 0)
- return false;
- int ret = fcntl(fd, F_GETFD);
- if (ret < 0)
- return false;
- return true;
-}
-
-int GNUmakeTokenPool::dup_rfd_ = -1;
-
-void GNUmakeTokenPool::CloseDupRfd(int signum) {
- close(dup_rfd_);
- dup_rfd_ = -1;
-}
-
-bool GNUmakeTokenPool::SetAlarmHandler() {
- struct sigaction act;
- memset(&act, 0, sizeof(act));
- act.sa_handler = CloseDupRfd;
- if (sigaction(SIGALRM, &act, &old_act_) < 0) {
- perror("sigaction:");
- return(false);
- } else {
- restore_ = true;
- return(true);
- }
}
bool GNUmakeTokenPool::Setup(bool ignore,
bool verbose,
double& max_load_average) {
- const char *value = getenv("MAKEFLAGS");
+ const char *value = GetEnv("MAKEFLAGS");
if (value) {
// GNU make <= 4.1
const char *jobserver = strstr(value, "--jobserver-fds=");
@@ -119,20 +44,13 @@ bool GNUmakeTokenPool::Setup(bool ignore,
if (ignore) {
printer.PrintOnNewLine("ninja: warning: -jN forced on command line; ignoring GNU make jobserver.\n");
} else {
- int rfd = -1;
- int wfd = -1;
- if ((sscanf(jobserver, "%*[^=]=%d,%d", &rfd, &wfd) == 2) &&
- CheckFd(rfd) &&
- CheckFd(wfd) &&
- SetAlarmHandler()) {
+ if (ParseAuth(jobserver)) {
const char *l_arg = strstr(value, " -l");
int load_limit = -1;
if (verbose) {
printer.PrintOnNewLine("ninja: using GNU make jobserver.\n");
}
- rfd_ = rfd;
- wfd_ = wfd;
// translate GNU make -lN to ninja -lN
if (l_arg &&
@@ -154,83 +72,14 @@ bool GNUmakeTokenPool::Acquire() {
if (available_ > 0)
return true;
- // Please read
- //
- // http://make.mad-scientist.net/papers/jobserver-implementation/
- //
- // for the reasoning behind the following code.
- //
- // Try to read one character from the pipe. Returns true on success.
- //
- // First check if read() would succeed without blocking.
-#ifdef USE_PPOLL
- pollfd pollfds[] = {{rfd_, POLLIN, 0}};
- int ret = poll(pollfds, 1, 0);
-#else
- fd_set set;
- struct timeval timeout = { 0, 0 };
- FD_ZERO(&set);
- FD_SET(rfd_, &set);
- int ret = select(rfd_ + 1, &set, NULL, NULL, &timeout);
-#endif
- if (ret > 0) {
- // Handle potential race condition:
- // - the above check succeeded, i.e. read() should not block
- // - the character disappears before we call read()
- //
- // Create a duplicate of rfd_. The duplicate file descriptor dup_rfd_
- // can safely be closed by signal handlers without affecting rfd_.
- dup_rfd_ = dup(rfd_);
-
- if (dup_rfd_ != -1) {
- struct sigaction act, old_act;
- int ret = 0;
-
- // Temporarily replace SIGCHLD handler with our own
- memset(&act, 0, sizeof(act));
- act.sa_handler = CloseDupRfd;
- if (sigaction(SIGCHLD, &act, &old_act) == 0) {
- struct itimerval timeout;
-
- // install a 100ms timeout that generates SIGALARM on expiration
- memset(&timeout, 0, sizeof(timeout));
- timeout.it_value.tv_usec = 100 * 1000; // [ms] -> [usec]
- if (setitimer(ITIMER_REAL, &timeout, NULL) == 0) {
- char buf;
-
- // Now try to read() from dup_rfd_. Return values from read():
- //
- // 1. token read -> 1
- // 2. pipe closed -> 0
- // 3. alarm expires -> -1 (EINTR)
- // 4. child exits -> -1 (EINTR)
- // 5. alarm expired before entering read() -> -1 (EBADF)
- // 6. child exited before entering read() -> -1 (EBADF)
- // 7. child exited before handler is installed -> go to 1 - 3
- ret = read(dup_rfd_, &buf, 1);
-
- // disarm timer
- memset(&timeout, 0, sizeof(timeout));
- setitimer(ITIMER_REAL, &timeout, NULL);
- }
-
- sigaction(SIGCHLD, &old_act, NULL);
- }
-
- CloseDupRfd(0);
-
- // Case 1 from above list
- if (ret > 0) {
- available_++;
- return true;
- }
- }
+ if (AcquireToken()) {
+ // token acquired
+ available_++;
+ return true;
+ } else {
+ // no token available
+ return false;
}
-
- // read() would block, i.e. no token available,
- // cases 2-6 from above list or
- // select() / poll() / dup() / sigaction() / setitimer() failed
- return false;
}
void GNUmakeTokenPool::Reserve() {
@@ -239,15 +88,8 @@ void GNUmakeTokenPool::Reserve() {
}
void GNUmakeTokenPool::Return() {
- const char buf = '+';
- while (1) {
- int ret = write(wfd_, &buf, 1);
- if (ret > 0)
- available_--;
- if ((ret != -1) || (errno != EINTR))
- return;
- // write got interrupted - retry
- }
+ if (ReturnToken())
+ available_--;
}
void GNUmakeTokenPool::Release() {
@@ -263,18 +105,3 @@ void GNUmakeTokenPool::Clear() {
while (available_ > 1)
Return();
}
-
-int GNUmakeTokenPool::GetMonitorFd() {
- return(rfd_);
-}
-
-struct TokenPool *TokenPool::Get(bool ignore,
- bool verbose,
- double& max_load_average) {
- GNUmakeTokenPool *tokenpool = new GNUmakeTokenPool;
- if (tokenpool->Setup(ignore, verbose, max_load_average))
- return tokenpool;
- else
- delete tokenpool;
- return NULL;
-}
diff --git a/src/tokenpool-gnu-make.h b/src/tokenpool-gnu-make.h
new file mode 100644
index 0000000000..d3852088e2
--- /dev/null
+++ b/src/tokenpool-gnu-make.h
@@ -0,0 +1,40 @@
+// Copyright 2016-2018 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 "tokenpool.h"
+
+// interface to GNU make token pool
+struct GNUmakeTokenPool : public TokenPool {
+ GNUmakeTokenPool();
+ virtual ~GNUmakeTokenPool();
+
+ // token pool implementation
+ virtual bool Acquire();
+ virtual void Reserve();
+ virtual void Release();
+ virtual void Clear();
+ virtual bool Setup(bool ignore, bool verbose, double& max_load_average);
+
+ // platform specific implementation
+ virtual const char *GetEnv(const char *name) = 0;
+ virtual bool ParseAuth(const char *jobserver) = 0;
+ virtual bool AcquireToken() = 0;
+ virtual bool ReturnToken() = 0;
+
+ private:
+ int available_;
+ int used_;
+
+ void Return();
+};
diff --git a/src/tokenpool-none.cc b/src/tokenpool-none.cc
index 4c592875b4..613d16882d 100644
--- a/src/tokenpool-none.cc
+++ b/src/tokenpool-none.cc
@@ -17,8 +17,6 @@
#include <stdlib.h>
// No-op TokenPool implementation
-struct TokenPool *TokenPool::Get(bool ignore,
- bool verbose,
- double& max_load_average) {
+struct TokenPool *TokenPool::Get() {
return NULL;
}
diff --git a/src/tokenpool.h b/src/tokenpool.h
index 4bf477f20c..1be8e1d5ce 100644
--- a/src/tokenpool.h
+++ b/src/tokenpool.h
@@ -1,4 +1,4 @@
-// Copyright 2016-2017 Google Inc. All Rights Reserved.
+// Copyright 2016-2018 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.
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
// interface to token pool
struct TokenPool {
virtual ~TokenPool() {}
@@ -21,14 +25,18 @@ struct TokenPool {
virtual void Release() = 0;
virtual void Clear() = 0;
+ // returns false if token pool setup failed
+ virtual bool Setup(bool ignore, bool verbose, double& max_load_average) = 0;
+
#ifdef _WIN32
- // @TODO
+ virtual void WaitForTokenAvailability(HANDLE ioport) = 0;
+ // returns true if a token has become available
+ // key is result from GetQueuedCompletionStatus()
+ virtual bool TokenIsAvailable(ULONG_PTR key) = 0;
#else
virtual int GetMonitorFd() = 0;
#endif
// returns NULL if token pool is not available
- static struct TokenPool *Get(bool ignore,
- bool verbose,
- double& max_load_average);
+ static struct TokenPool *Get();
};
diff --git a/src/tokenpool_test.cc b/src/tokenpool_test.cc
index 6c89064ca4..8d4fd7d33a 100644
--- a/src/tokenpool_test.cc
+++ b/src/tokenpool_test.cc
@@ -16,13 +16,25 @@
#include "test.h"
-#ifndef _WIN32
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
#include <stdio.h>
#include <stdlib.h>
-#include <unistd.h>
+#ifdef _WIN32
+// should contain all valid characters
+#define SEMAPHORE_NAME "abcdefghijklmnopqrstwxyz01234567890_"
+#define AUTH_FORMAT(tmpl) "foo " tmpl "=%s bar"
+#define ENVIRONMENT_CLEAR() SetEnvironmentVariable("MAKEFLAGS", NULL)
+#define ENVIRONMENT_INIT(v) SetEnvironmentVariable("MAKEFLAGS", v)
+#else
+#define AUTH_FORMAT(tmpl) "foo " tmpl "=%d,%d bar"
#define ENVIRONMENT_CLEAR() unsetenv("MAKEFLAGS")
-#define ENVIRONMENT_INIT(v) setenv("MAKEFLAGS", v, true);
+#define ENVIRONMENT_INIT(v) setenv("MAKEFLAGS", v, true)
#endif
namespace {
@@ -32,43 +44,60 @@ const double kLoadAverageDefault = -1.23456789;
struct TokenPoolTest : public testing::Test {
double load_avg_;
TokenPool *tokens_;
-#ifndef _WIN32
char buf_[1024];
+#ifdef _WIN32
+ const char *semaphore_name_;
+ HANDLE semaphore_;
+#else
int fds_[2];
#endif
virtual void SetUp() {
load_avg_ = kLoadAverageDefault;
tokens_ = NULL;
-#ifndef _WIN32
ENVIRONMENT_CLEAR();
+#ifdef _WIN32
+ semaphore_name_ = SEMAPHORE_NAME;
+ if ((semaphore_ = CreateSemaphore(0, 0, 2, SEMAPHORE_NAME)) == NULL)
+#else
if (pipe(fds_) < 0)
- ASSERT_TRUE(false);
#endif
+ ASSERT_TRUE(false);
}
- void CreatePool(const char *format, bool ignore_jobserver) {
-#ifndef _WIN32
+ void CreatePool(const char *format, bool ignore_jobserver = false) {
if (format) {
- sprintf(buf_, format, fds_[0], fds_[1]);
+ sprintf(buf_, format,
+#ifdef _WIN32
+ semaphore_name_
+#else
+ fds_[0], fds_[1]
+#endif
+ );
ENVIRONMENT_INIT(buf_);
}
-#endif
- tokens_ = TokenPool::Get(ignore_jobserver, false, load_avg_);
+ if ((tokens_ = TokenPool::Get()) != NULL) {
+ if (!tokens_->Setup(ignore_jobserver, false, load_avg_)) {
+ delete tokens_;
+ tokens_ = NULL;
+ }
+ }
}
void CreateDefaultPool() {
- CreatePool("foo --jobserver-auth=%d,%d bar", false);
+ CreatePool(AUTH_FORMAT("--jobserver-auth"));
}
virtual void TearDown() {
if (tokens_)
delete tokens_;
-#ifndef _WIN32
+#ifdef _WIN32
+ CloseHandle(semaphore_);
+#else
close(fds_[0]);
close(fds_[1]);
- ENVIRONMENT_CLEAR();
#endif
+ ENVIRONMENT_CLEAR();
}
};
@@ -82,10 +111,9 @@ TEST_F(TokenPoolTest, NoTokenPool) {
EXPECT_EQ(kLoadAverageDefault, load_avg_);
}
-#ifndef _WIN32
TEST_F(TokenPoolTest, SuccessfulOldSetup) {
// GNUmake <= 4.1
- CreatePool("foo --jobserver-fds=%d,%d bar", false);
+ CreatePool(AUTH_FORMAT("--jobserver-fds"));
EXPECT_NE(NULL, tokens_);
EXPECT_EQ(kLoadAverageDefault, load_avg_);
@@ -100,19 +128,37 @@ TEST_F(TokenPoolTest, SuccessfulNewSetup) {
}
TEST_F(TokenPoolTest, IgnoreWithJN) {
- CreatePool("foo --jobserver-auth=%d,%d bar", true);
+ CreatePool(AUTH_FORMAT("--jobserver-auth"), true);
EXPECT_EQ(NULL, tokens_);
EXPECT_EQ(kLoadAverageDefault, load_avg_);
}
TEST_F(TokenPoolTest, HonorLN) {
- CreatePool("foo -l9 --jobserver-auth=%d,%d bar", false);
+ CreatePool(AUTH_FORMAT("-l9 --jobserver-auth"));
EXPECT_NE(NULL, tokens_);
EXPECT_EQ(9.0, load_avg_);
}
+#ifdef _WIN32
+TEST_F(TokenPoolTest, SemaphoreNotFound) {
+ semaphore_name_ = SEMAPHORE_NAME "_foobar";
+ CreateDefaultPool();
+
+ EXPECT_EQ(NULL, tokens_);
+ EXPECT_EQ(kLoadAverageDefault, load_avg_);
+}
+
+TEST_F(TokenPoolTest, TokenIsAvailable) {
+ CreateDefaultPool();
+
+ ASSERT_NE(NULL, tokens_);
+ EXPECT_EQ(kLoadAverageDefault, load_avg_);
+
+ EXPECT_TRUE(tokens_->TokenIsAvailable((ULONG_PTR)tokens_));
+}
+#else
TEST_F(TokenPoolTest, MonitorFD) {
CreateDefaultPool();
@@ -121,6 +167,7 @@ TEST_F(TokenPoolTest, MonitorFD) {
EXPECT_EQ(fds_[0], tokens_->GetMonitorFd());
}
+#endif
TEST_F(TokenPoolTest, ImplicitToken) {
CreateDefaultPool();
@@ -147,7 +194,13 @@ TEST_F(TokenPoolTest, TwoTokens) {
EXPECT_FALSE(tokens_->Acquire());
// jobserver offers 2nd token
+#ifdef _WIN32
+ LONG previous;
+ ASSERT_TRUE(ReleaseSemaphore(semaphore_, 1, &previous));
+ ASSERT_EQ(0, previous);
+#else
ASSERT_EQ(1u, write(fds_[1], "T", 1));
+#endif
EXPECT_TRUE(tokens_->Acquire());
tokens_->Reserve();
EXPECT_FALSE(tokens_->Acquire());
@@ -160,8 +213,14 @@ TEST_F(TokenPoolTest, TwoTokens) {
tokens_->Release();
EXPECT_TRUE(tokens_->Acquire());
- // there must be one token in the pipe
+ // there must be one token available
+#ifdef _WIN32
+ EXPECT_EQ(WAIT_OBJECT_0, WaitForSingleObject(semaphore_, 0));
+ EXPECT_TRUE(ReleaseSemaphore(semaphore_, 1, &previous));
+ EXPECT_EQ(0, previous);
+#else
EXPECT_EQ(1u, read(fds_[0], buf_, sizeof(buf_)));
+#endif
// implicit token
EXPECT_TRUE(tokens_->Acquire());
@@ -179,7 +238,13 @@ TEST_F(TokenPoolTest, Clear) {
EXPECT_FALSE(tokens_->Acquire());
// jobserver offers 2nd & 3rd token
+#ifdef _WIN32
+ LONG previous;
+ ASSERT_TRUE(ReleaseSemaphore(semaphore_, 2, &previous));
+ ASSERT_EQ(0, previous);
+#else
ASSERT_EQ(2u, write(fds_[1], "TT", 2));
+#endif
EXPECT_TRUE(tokens_->Acquire());
tokens_->Reserve();
EXPECT_TRUE(tokens_->Acquire());
@@ -189,10 +254,16 @@ TEST_F(TokenPoolTest, Clear) {
tokens_->Clear();
EXPECT_TRUE(tokens_->Acquire());
- // there must be two tokens in the pipe
+ // there must be two tokens available
+#ifdef _WIN32
+ EXPECT_EQ(WAIT_OBJECT_0, WaitForSingleObject(semaphore_, 0));
+ EXPECT_EQ(WAIT_OBJECT_0, WaitForSingleObject(semaphore_, 0));
+ EXPECT_TRUE(ReleaseSemaphore(semaphore_, 2, &previous));
+ EXPECT_EQ(0, previous);
+#else
EXPECT_EQ(2u, read(fds_[0], buf_, sizeof(buf_)));
+#endif
// implicit token
EXPECT_TRUE(tokens_->Acquire());
}
-#endif
From 2b9c81c0ec1226d8795e7725529f13be41eaa385 Mon Sep 17 00:00:00 2001
From: Stefan Becker <chemobejk@gmail.com>
Date: Fri, 14 Dec 2018 13:27:11 +0200
Subject: [PATCH 11/11] Prepare PR for merging - part II
- remove unnecessary "struct" from TokenPool
- add PAPCFUNC cast to QueryUserAPC()
- remove hard-coded MAKEFLAGS string from win32
- remove useless build test CompleteNoWork
- rename TokenPoolTest to TestTokenPool
- add tokenpool modules to CMake build
- remove unused no-op TokenPool implementation
- fix errors flagged by codespell & clang-tidy
- address review comments from
https://github.com/ninja-build/ninja/pull/1140#pullrequestreview-195195803
https://github.com/ninja-build/ninja/pull/1140#pullrequestreview-185089255
https://github.com/ninja-build/ninja/pull/1140#issuecomment-473898963
https://github.com/ninja-build/ninja/pull/1140#issuecomment-596624610
---
CMakeLists.txt | 8 ++++-
src/build.cc | 2 +-
src/build_test.cc | 12 +------
src/subprocess-posix.cc | 4 +--
src/subprocess-win32.cc | 2 +-
src/subprocess.h | 2 +-
src/subprocess_test.cc | 26 +++++++-------
src/tokenpool-gnu-make-posix.cc | 21 +++++------
src/tokenpool-gnu-make-win32.cc | 36 ++++++++++---------
src/tokenpool-gnu-make.cc | 63 +++++++++++++++++----------------
src/tokenpool-gnu-make.h | 6 ++--
src/tokenpool-none.cc | 22 ------------
src/tokenpool.h | 2 +-
src/tokenpool_test.cc | 8 ++---
14 files changed, 94 insertions(+), 120 deletions(-)
delete mode 100644 src/tokenpool-none.cc
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 57ae548f5b..e2876fe413 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -112,6 +112,7 @@ add_library(libninja OBJECT
src/state.cc
src/status.cc
src/string_piece_util.cc
+ src/tokenpool-gnu-make.cc
src/util.cc
src/version.cc
)
@@ -123,9 +124,13 @@ if(WIN32)
src/msvc_helper_main-win32.cc
src/getopt.c
src/minidump-win32.cc
+ src/tokenpool-gnu-make-win32.cc
)
else()
- target_sources(libninja PRIVATE src/subprocess-posix.cc)
+ target_sources(libninja PRIVATE
+ src/subprocess-posix.cc
+ src/tokenpool-gnu-make-posix.cc
+ )
if(CMAKE_SYSTEM_NAME STREQUAL "OS400" OR CMAKE_SYSTEM_NAME STREQUAL "AIX")
target_sources(libninja PRIVATE src/getopt.c)
endif()
@@ -204,6 +209,7 @@ if(BUILD_TESTING)
src/string_piece_util_test.cc
src/subprocess_test.cc
src/test.cc
+ src/tokenpool_test.cc
src/util_test.cc
)
if(WIN32)
diff --git a/src/build.cc b/src/build.cc
index 20c3bdc2a0..854df08c2a 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -467,7 +467,7 @@ struct RealCommandRunner : public CommandRunner {
// copy of config_.max_load_average; can be modified by TokenPool setup
double max_load_average_;
SubprocessSet subprocs_;
- TokenPool *tokens_;
+ TokenPool* tokens_;
map<const Subprocess*, Edge*> subproc_to_edge_;
};
diff --git a/src/build_test.cc b/src/build_test.cc
index dd41dfbe1d..8901c9518f 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -4098,7 +4098,7 @@ struct BuildTokenTest : public BuildTest {
void ExpectWaitForCommand(int count, ...);
private:
- void EnqueueBooleans(vector<bool>& booleans, int count, va_list ao);
+ void EnqueueBooleans(vector<bool>& booleans, int count, va_list ap);
};
void BuildTokenTest::SetUp() {
@@ -4144,16 +4144,6 @@ void BuildTokenTest::EnqueueBooleans(vector<bool>& booleans, int count, va_list
}
}
-TEST_F(BuildTokenTest, CompleteNoWork) {
- // plan should not execute anything
- string err;
-
- EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ("", err);
-
- EXPECT_EQ(0u, token_command_runner_.commands_ran_.size());
-}
-
TEST_F(BuildTokenTest, DoNotAquireToken) {
// plan should execute one command
string err;
diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc
index 74451b0be2..31839276c4 100644
--- a/src/subprocess-posix.cc
+++ b/src/subprocess-posix.cc
@@ -250,7 +250,7 @@ Subprocess *SubprocessSet::Add(const string& command, bool use_console) {
}
#ifdef USE_PPOLL
-bool SubprocessSet::DoWork(struct TokenPool* tokens) {
+bool SubprocessSet::DoWork(TokenPool* tokens) {
vector<pollfd> fds;
nfds_t nfds = 0;
@@ -315,7 +315,7 @@ bool SubprocessSet::DoWork(struct TokenPool* tokens) {
}
#else // !defined(USE_PPOLL)
-bool SubprocessSet::DoWork(struct TokenPool* tokens) {
+bool SubprocessSet::DoWork(TokenPool* tokens) {
fd_set set;
int nfds = 0;
FD_ZERO(&set);
diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc
index ce3e2c20a4..2926e9a221 100644
--- a/src/subprocess-win32.cc
+++ b/src/subprocess-win32.cc
@@ -252,7 +252,7 @@ Subprocess *SubprocessSet::Add(const string& command, bool use_console) {
return subprocess;
}
-bool SubprocessSet::DoWork(struct TokenPool* tokens) {
+bool SubprocessSet::DoWork(TokenPool* tokens) {
DWORD bytes_read;
Subprocess* subproc;
OVERLAPPED* overlapped;
diff --git a/src/subprocess.h b/src/subprocess.h
index 9ea67ea477..1ec78171e8 100644
--- a/src/subprocess.h
+++ b/src/subprocess.h
@@ -86,7 +86,7 @@ struct SubprocessSet {
~SubprocessSet();
Subprocess* Add(const std::string& command, bool use_console = false);
- bool DoWork(struct TokenPool* tokens);
+ bool DoWork(TokenPool* tokens);
Subprocess* NextFinished();
void Clear();
diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc
index f625963462..7b146f31be 100644
--- a/src/subprocess_test.cc
+++ b/src/subprocess_test.cc
@@ -35,7 +35,7 @@ const char* kSimpleCommand = "cmd /c dir \\";
const char* kSimpleCommand = "ls /";
#endif
-struct TokenPoolTest : public TokenPool {
+struct TestTokenPool : public TokenPool {
bool Acquire() { return false; }
void Reserve() {}
void Release() {}
@@ -58,7 +58,7 @@ struct TokenPoolTest : public TokenPool {
struct SubprocessTest : public testing::Test {
SubprocessSet subprocs_;
- TokenPoolTest tokens_;
+ TestTokenPool tokens_;
};
} // anonymous namespace
@@ -73,7 +73,7 @@ TEST_F(SubprocessTest, BadCommandStderr) {
// Pretend we discovered that stderr was ready for writing.
subprocs_.DoWork(NULL);
}
- ASSERT_EQ(false, subprocs_.IsTokenAvailable());
+ ASSERT_FALSE(subprocs_.IsTokenAvailable());
EXPECT_EQ(ExitFailure, subproc->Finish());
EXPECT_NE("", subproc->GetOutput());
@@ -89,7 +89,7 @@ TEST_F(SubprocessTest, NoSuchCommand) {
// Pretend we discovered that stderr was ready for writing.
subprocs_.DoWork(NULL);
}
- ASSERT_EQ(false, subprocs_.IsTokenAvailable());
+ ASSERT_FALSE(subprocs_.IsTokenAvailable());
EXPECT_EQ(ExitFailure, subproc->Finish());
EXPECT_NE("", subproc->GetOutput());
@@ -109,7 +109,7 @@ TEST_F(SubprocessTest, InterruptChild) {
while (!subproc->Done()) {
subprocs_.DoWork(NULL);
}
- ASSERT_EQ(false, subprocs_.IsTokenAvailable());
+ ASSERT_FALSE(subprocs_.IsTokenAvailable());
EXPECT_EQ(ExitInterrupted, subproc->Finish());
}
@@ -135,7 +135,7 @@ TEST_F(SubprocessTest, InterruptChildWithSigTerm) {
while (!subproc->Done()) {
subprocs_.DoWork(NULL);
}
- ASSERT_EQ(false, subprocs_.IsTokenAvailable());
+ ASSERT_FALSE(subprocs_.IsTokenAvailable());
EXPECT_EQ(ExitInterrupted, subproc->Finish());
}
@@ -161,7 +161,7 @@ TEST_F(SubprocessTest, InterruptChildWithSigHup) {
while (!subproc->Done()) {
subprocs_.DoWork(NULL);
}
- ASSERT_EQ(false, subprocs_.IsTokenAvailable());
+ ASSERT_FALSE(subprocs_.IsTokenAvailable());
EXPECT_EQ(ExitInterrupted, subproc->Finish());
}
@@ -190,7 +190,7 @@ TEST_F(SubprocessTest, Console) {
while (!subproc->Done()) {
subprocs_.DoWork(NULL);
}
- ASSERT_EQ(false, subprocs_.IsTokenAvailable());
+ ASSERT_FALSE(subprocs_.IsTokenAvailable());
EXPECT_EQ(ExitSuccess, subproc->Finish());
}
@@ -206,7 +206,7 @@ TEST_F(SubprocessTest, SetWithSingle) {
while (!subproc->Done()) {
subprocs_.DoWork(NULL);
}
- ASSERT_EQ(false, subprocs_.IsTokenAvailable());
+ ASSERT_FALSE(subprocs_.IsTokenAvailable());
ASSERT_EQ(ExitSuccess, subproc->Finish());
ASSERT_NE("", subproc->GetOutput());
@@ -243,7 +243,7 @@ TEST_F(SubprocessTest, SetWithMulti) {
ASSERT_GT(subprocs_.running_.size(), 0u);
subprocs_.DoWork(NULL);
}
- ASSERT_EQ(false, subprocs_.IsTokenAvailable());
+ ASSERT_FALSE(subprocs_.IsTokenAvailable());
ASSERT_EQ(0u, subprocs_.running_.size());
ASSERT_EQ(3u, subprocs_.finished_.size());
@@ -278,7 +278,7 @@ TEST_F(SubprocessTest, SetWithLots) {
subprocs_.ResetTokenAvailable();
while (!subprocs_.running_.empty())
subprocs_.DoWork(NULL);
- ASSERT_EQ(false, subprocs_.IsTokenAvailable());
+ ASSERT_FALSE(subprocs_.IsTokenAvailable());
for (size_t i = 0; i < procs.size(); ++i) {
ASSERT_EQ(ExitSuccess, procs[i]->Finish());
ASSERT_NE("", procs[i]->GetOutput());
@@ -298,7 +298,7 @@ TEST_F(SubprocessTest, ReadStdin) {
while (!subproc->Done()) {
subprocs_.DoWork(NULL);
}
- ASSERT_EQ(false, subprocs_.IsTokenAvailable());
+ ASSERT_FALSE(subprocs_.IsTokenAvailable());
ASSERT_EQ(ExitSuccess, subproc->Finish());
ASSERT_EQ(1u, subprocs_.finished_.size());
}
@@ -322,7 +322,7 @@ TEST_F(SubprocessTest, TokenAvailable) {
subprocs_.DoWork(&tokens_);
#ifdef _WIN32
tokens_._token_available = false;
- // we need to loop here as we have no conrol where the token
+ // we need to loop here as we have no control where the token
// I/O completion post ends up in the queue
while (!subproc->Done() && !subprocs_.IsTokenAvailable()) {
subprocs_.DoWork(&tokens_);
diff --git a/src/tokenpool-gnu-make-posix.cc b/src/tokenpool-gnu-make-posix.cc
index 70d84bfff7..353bda226a 100644
--- a/src/tokenpool-gnu-make-posix.cc
+++ b/src/tokenpool-gnu-make-posix.cc
@@ -32,8 +32,8 @@ struct GNUmakeTokenPoolPosix : public GNUmakeTokenPool {
virtual int GetMonitorFd();
- virtual const char *GetEnv(const char *name) { return getenv(name); };
- virtual bool ParseAuth(const char *jobserver);
+ virtual const char* GetEnv(const char* name) { return getenv(name); };
+ virtual bool ParseAuth(const char* jobserver);
virtual bool AcquireToken();
virtual bool ReturnToken();
@@ -64,9 +64,7 @@ bool GNUmakeTokenPoolPosix::CheckFd(int fd) {
if (fd < 0)
return false;
int ret = fcntl(fd, F_GETFD);
- if (ret < 0)
- return false;
- return true;
+ return ret >= 0;
}
int GNUmakeTokenPoolPosix::dup_rfd_ = -1;
@@ -82,14 +80,13 @@ bool GNUmakeTokenPoolPosix::SetAlarmHandler() {
act.sa_handler = CloseDupRfd;
if (sigaction(SIGALRM, &act, &old_act_) < 0) {
perror("sigaction:");
- return(false);
- } else {
- restore_ = true;
- return(true);
+ return false;
}
+ restore_ = true;
+ return true;
}
-bool GNUmakeTokenPoolPosix::ParseAuth(const char *jobserver) {
+bool GNUmakeTokenPoolPosix::ParseAuth(const char* jobserver) {
int rfd = -1;
int wfd = -1;
if ((sscanf(jobserver, "%*[^=]=%d,%d", &rfd, &wfd) == 2) &&
@@ -195,9 +192,9 @@ bool GNUmakeTokenPoolPosix::ReturnToken() {
}
int GNUmakeTokenPoolPosix::GetMonitorFd() {
- return(rfd_);
+ return rfd_;
}
-struct TokenPool *TokenPool::Get() {
+TokenPool* TokenPool::Get() {
return new GNUmakeTokenPoolPosix;
}
diff --git a/src/tokenpool-gnu-make-win32.cc b/src/tokenpool-gnu-make-win32.cc
index 2719f2c1fc..b2bb52fadb 100644
--- a/src/tokenpool-gnu-make-win32.cc
+++ b/src/tokenpool-gnu-make-win32.cc
@@ -14,7 +14,8 @@
#include "tokenpool-gnu-make.h"
-// always include first to make sure other headers do the correct thing...
+// Always include this first.
+// Otherwise the other system headers don't work correctly under Win32
#include <windows.h>
#include <ctype.h>
@@ -32,8 +33,8 @@ struct GNUmakeTokenPoolWin32 : public GNUmakeTokenPool {
virtual void WaitForTokenAvailability(HANDLE ioport);
virtual bool TokenIsAvailable(ULONG_PTR key);
- virtual const char *GetEnv(const char *name);
- virtual bool ParseAuth(const char *jobserver);
+ virtual const char* GetEnv(const char* name);
+ virtual bool ParseAuth(const char* jobserver);
virtual bool AcquireToken();
virtual bool ReturnToken();
@@ -98,19 +99,19 @@ GNUmakeTokenPoolWin32::~GNUmakeTokenPoolWin32() {
}
}
-const char *GNUmakeTokenPoolWin32::GetEnv(const char *name) {
+const char* GNUmakeTokenPoolWin32::GetEnv(const char* name) {
// getenv() does not work correctly together with tokenpool_tests.cc
static char buffer[MAX_PATH + 1];
- if (GetEnvironmentVariable("MAKEFLAGS", buffer, sizeof(buffer)) == 0)
+ if (GetEnvironmentVariable(name, buffer, sizeof(buffer)) == 0)
return NULL;
- return(buffer);
+ return buffer;
}
-bool GNUmakeTokenPoolWin32::ParseAuth(const char *jobserver) {
+bool GNUmakeTokenPoolWin32::ParseAuth(const char* jobserver) {
// match "--jobserver-auth=gmake_semaphore_<INTEGER>..."
- const char *start = strchr(jobserver, '=');
+ const char* start = strchr(jobserver, '=');
if (start) {
- const char *end = start;
+ const char* end = start;
unsigned int len;
char c, *auth;
@@ -119,14 +120,15 @@ bool GNUmakeTokenPoolWin32::ParseAuth(const char *jobserver) {
break;
len = end - start; // includes string terminator in count
- if ((len > 1) && ((auth = (char *)malloc(len)) != NULL)) {
+ if ((len > 1) && ((auth = (char*)malloc(len)) != NULL)) {
strncpy(auth, start + 1, len - 1);
auth[len - 1] = '\0';
- if ((semaphore_jobserver_ = OpenSemaphore(SEMAPHORE_ALL_ACCESS, /* Semaphore access setting */
- FALSE, /* Child processes DON'T inherit */
- auth /* Semaphore name */
- )) != NULL) {
+ if ((semaphore_jobserver_ =
+ OpenSemaphore(SEMAPHORE_ALL_ACCESS, /* Semaphore access setting */
+ FALSE, /* Child processes DON'T inherit */
+ auth /* Semaphore name */
+ )) != NULL) {
free(auth);
return true;
}
@@ -171,7 +173,7 @@ DWORD GNUmakeTokenPoolWin32::SemaphoreThread() {
}
DWORD WINAPI GNUmakeTokenPoolWin32::SemaphoreThreadWrapper(LPVOID param) {
- GNUmakeTokenPoolWin32 *This = (GNUmakeTokenPoolWin32 *) param;
+ GNUmakeTokenPoolWin32* This = (GNUmakeTokenPoolWin32*) param;
return This->SemaphoreThread();
}
@@ -216,7 +218,7 @@ void GNUmakeTokenPoolWin32::WaitForTokenAvailability(HANDLE ioport) {
bool GNUmakeTokenPoolWin32::TokenIsAvailable(ULONG_PTR key) {
// alert child thread to break wait on token semaphore
- QueueUserAPC(&NoopAPCFunc, child_, (ULONG_PTR)NULL);
+ QueueUserAPC((PAPCFUNC)&NoopAPCFunc, child_, (ULONG_PTR)NULL);
// return true when GetQueuedCompletionStatus() returned our key
return key == (ULONG_PTR) this;
@@ -232,6 +234,6 @@ void GNUmakeTokenPoolWin32::WaitForObject(HANDLE object) {
Win32Fatal("WaitForSingleObject");
}
-struct TokenPool *TokenPool::Get() {
+TokenPool* TokenPool::Get() {
return new GNUmakeTokenPoolWin32;
}
diff --git a/src/tokenpool-gnu-make.cc b/src/tokenpool-gnu-make.cc
index 92ff611721..60e0552924 100644
--- a/src/tokenpool-gnu-make.cc
+++ b/src/tokenpool-gnu-make.cc
@@ -31,36 +31,37 @@ GNUmakeTokenPool::~GNUmakeTokenPool() {
bool GNUmakeTokenPool::Setup(bool ignore,
bool verbose,
double& max_load_average) {
- const char *value = GetEnv("MAKEFLAGS");
- if (value) {
- // GNU make <= 4.1
- const char *jobserver = strstr(value, "--jobserver-fds=");
+ const char* value = GetEnv("MAKEFLAGS");
+ if (!value)
+ return false;
+
+ // GNU make <= 4.1
+ const char* jobserver = strstr(value, "--jobserver-fds=");
+ if (!jobserver)
// GNU make => 4.2
- if (!jobserver)
- jobserver = strstr(value, "--jobserver-auth=");
- if (jobserver) {
- LinePrinter printer;
-
- if (ignore) {
- printer.PrintOnNewLine("ninja: warning: -jN forced on command line; ignoring GNU make jobserver.\n");
- } else {
- if (ParseAuth(jobserver)) {
- const char *l_arg = strstr(value, " -l");
- int load_limit = -1;
-
- if (verbose) {
- printer.PrintOnNewLine("ninja: using GNU make jobserver.\n");
- }
-
- // translate GNU make -lN to ninja -lN
- if (l_arg &&
- (sscanf(l_arg + 3, "%d ", &load_limit) == 1) &&
- (load_limit > 0)) {
- max_load_average = load_limit;
- }
-
- return true;
+ jobserver = strstr(value, "--jobserver-auth=");
+ if (jobserver) {
+ LinePrinter printer;
+
+ if (ignore) {
+ printer.PrintOnNewLine("ninja: warning: -jN forced on command line; ignoring GNU make jobserver.\n");
+ } else {
+ if (ParseAuth(jobserver)) {
+ const char* l_arg = strstr(value, " -l");
+ int load_limit = -1;
+
+ if (verbose) {
+ printer.PrintOnNewLine("ninja: using GNU make jobserver.\n");
+ }
+
+ // translate GNU make -lN to ninja -lN
+ if (l_arg &&
+ (sscanf(l_arg + 3, "%d ", &load_limit) == 1) &&
+ (load_limit > 0)) {
+ max_load_average = load_limit;
}
+
+ return true;
}
}
}
@@ -76,10 +77,10 @@ bool GNUmakeTokenPool::Acquire() {
// token acquired
available_++;
return true;
- } else {
- // no token available
- return false;
}
+
+ // no token available
+ return false;
}
void GNUmakeTokenPool::Reserve() {
diff --git a/src/tokenpool-gnu-make.h b/src/tokenpool-gnu-make.h
index d3852088e2..c94cca5e2d 100644
--- a/src/tokenpool-gnu-make.h
+++ b/src/tokenpool-gnu-make.h
@@ -17,7 +17,7 @@
// interface to GNU make token pool
struct GNUmakeTokenPool : public TokenPool {
GNUmakeTokenPool();
- virtual ~GNUmakeTokenPool();
+ ~GNUmakeTokenPool();
// token pool implementation
virtual bool Acquire();
@@ -27,8 +27,8 @@ struct GNUmakeTokenPool : public TokenPool {
virtual bool Setup(bool ignore, bool verbose, double& max_load_average);
// platform specific implementation
- virtual const char *GetEnv(const char *name) = 0;
- virtual bool ParseAuth(const char *jobserver) = 0;
+ virtual const char* GetEnv(const char* name) = 0;
+ virtual bool ParseAuth(const char* jobserver) = 0;
virtual bool AcquireToken() = 0;
virtual bool ReturnToken() = 0;
diff --git a/src/tokenpool-none.cc b/src/tokenpool-none.cc
deleted file mode 100644
index 613d16882d..0000000000
--- a/src/tokenpool-none.cc
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2016-2018 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 "tokenpool.h"
-
-#include <stdlib.h>
-
-// No-op TokenPool implementation
-struct TokenPool *TokenPool::Get() {
- return NULL;
-}
diff --git a/src/tokenpool.h b/src/tokenpool.h
index 1be8e1d5ce..931c22754d 100644
--- a/src/tokenpool.h
+++ b/src/tokenpool.h
@@ -38,5 +38,5 @@ struct TokenPool {
#endif
// returns NULL if token pool is not available
- static struct TokenPool *Get();
+ static TokenPool* Get();
};
diff --git a/src/tokenpool_test.cc b/src/tokenpool_test.cc
index 8d4fd7d33a..8d3061cb30 100644
--- a/src/tokenpool_test.cc
+++ b/src/tokenpool_test.cc
@@ -43,10 +43,10 @@ const double kLoadAverageDefault = -1.23456789;
struct TokenPoolTest : public testing::Test {
double load_avg_;
- TokenPool *tokens_;
+ TokenPool* tokens_;
char buf_[1024];
#ifdef _WIN32
- const char *semaphore_name_;
+ const char* semaphore_name_;
HANDLE semaphore_;
#else
int fds_[2];
@@ -65,7 +65,7 @@ struct TokenPoolTest : public testing::Test {
ASSERT_TRUE(false);
}
- void CreatePool(const char *format, bool ignore_jobserver = false) {
+ void CreatePool(const char* format, bool ignore_jobserver = false) {
if (format) {
sprintf(buf_, format,
#ifdef _WIN32
@@ -209,7 +209,7 @@ TEST_F(TokenPoolTest, TwoTokens) {
tokens_->Release();
EXPECT_TRUE(tokens_->Acquire());
- // release implict token - must return 2nd token back to jobserver
+ // release implicit token - must return 2nd token back to jobserver
tokens_->Release();
EXPECT_TRUE(tokens_->Acquire());