| From afec30f5caf4b051827ffdd822ebd27c58219fee 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/ |
| |
| --- a/configure.py |
| +++ b/configure.py |
| @@ -543,11 +543,13 @@ for name in ['build', |
| '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-gnu-make-win32', |
| 'includes_normalize-win32', |
| 'msvc_helper-win32', |
| 'msvc_helper_main-win32']: |
| @@ -556,7 +558,9 @@ if platform.is_windows(): |
| objs += cxx('minidump-win32', variables=cxxvariables) |
| objs += cc('getopt') |
| else: |
| - objs += cxx('subprocess-posix') |
| + for name in ['subprocess-posix', |
| + 'tokenpool-gnu-make-posix']: |
| + objs += cxx(name) |
| if platform.is_aix(): |
| objs += cc('getopt') |
| if platform.is_msvc(): |
| @@ -639,6 +643,7 @@ if gtest_src_dir: |
| 'string_piece_util_test', |
| 'subprocess_test', |
| 'test', |
| + 'tokenpool_test', |
| 'util_test', |
| ] |
| if platform.is_windows(): |
| --- a/src/build.cc |
| +++ b/src/build.cc |
| @@ -39,6 +39,7 @@ |
| #include "state.h" |
| #include "status.h" |
| #include "subprocess.h" |
| +#include "tokenpool.h" |
| #include "util.h" |
| |
| using namespace std; |
| @@ -50,24 +51,29 @@ struct DryRunCommandRunner : public Comm |
| virtual ~DryRunCommandRunner() {} |
| |
| // Overridden from CommandRunner: |
| - virtual size_t CanRunMore() const; |
| + virtual size_t CanRunMore(); |
| + 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_; |
| }; |
| |
| -size_t DryRunCommandRunner::CanRunMore() const { |
| +size_t DryRunCommandRunner::CanRunMore() { |
| return SIZE_MAX; |
| } |
| |
| +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; |
| |
| @@ -160,7 +166,7 @@ void Plan::EdgeWanted(const Edge* edge) |
| } |
| |
| Edge* Plan::FindWork() { |
| - if (ready_.empty()) |
| + if (!more_ready()) |
| return NULL; |
| |
| Edge* work = ready_.top(); |
| @@ -595,19 +601,39 @@ void Plan::Dump() const { |
| } |
| |
| struct RealCommandRunner : public CommandRunner { |
| - explicit RealCommandRunner(const BuildConfig& config) : config_(config) {} |
| - virtual ~RealCommandRunner() {} |
| - virtual size_t CanRunMore() const; |
| + explicit RealCommandRunner(const BuildConfig& config); |
| + virtual ~RealCommandRunner(); |
| + virtual size_t CanRunMore(); |
| + 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(); |
| |
| 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) { |
| + max_load_average_ = config.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() { |
| + delete tokens_; |
| +} |
| + |
| vector<Edge*> RealCommandRunner::GetActiveEdges() { |
| vector<Edge*> edges; |
| for (map<const Subprocess*, Edge*>::iterator e = subproc_to_edge_.begin(); |
| @@ -618,9 +644,11 @@ vector<Edge*> RealCommandRunner::GetActi |
| |
| void RealCommandRunner::Abort() { |
| subprocs_.Clear(); |
| + if (tokens_) |
| + tokens_->Clear(); |
| } |
| |
| -size_t RealCommandRunner::CanRunMore() const { |
| +size_t RealCommandRunner::CanRunMore() { |
| size_t subproc_number = |
| subprocs_.running_.size() + subprocs_.finished_.size(); |
| |
| @@ -635,6 +663,13 @@ size_t RealCommandRunner::CanRunMore() c |
| if (capacity < 0) |
| capacity = 0; |
| |
| + if (tokens_) { |
| + if (AcquireToken()) |
| + return SIZE_MAX; |
| + else |
| + capacity = 0; |
| + } |
| + |
| if (capacity == 0 && subprocs_.running_.empty()) |
| // Ensure that we make progress. |
| capacity = 1; |
| @@ -642,24 +677,42 @@ size_t RealCommandRunner::CanRunMore() c |
| return capacity; |
| } |
| |
| +bool RealCommandRunner::AcquireToken() { |
| + return (!tokens_ || tokens_->Acquire()); |
| +} |
| + |
| bool RealCommandRunner::StartCommand(Edge* edge) { |
| string command = edge->EvaluateCommand(); |
| 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; |
| } |
| |
| -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(); |
| + |
| result->status = subproc->Finish(); |
| result->output = subproc->GetOutput(); |
| |
| @@ -790,7 +843,8 @@ 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) { |
| + bool can_run_more = failures_allowed && plan_.more_ready(); |
| + if (can_run_more) { |
| size_t capacity = command_runner_->CanRunMore(); |
| while (capacity > 0) { |
| Edge* edge = plan_.FindWork(); |
| @@ -833,7 +887,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(); |
| @@ -841,6 +895,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(); |
| --- a/src/build.h |
| +++ b/src/build.h |
| @@ -51,6 +51,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; |
| |
| @@ -145,7 +148,8 @@ private: |
| /// RealCommandRunner is an implementation that actually runs commands. |
| struct CommandRunner { |
| virtual ~CommandRunner() {} |
| - virtual size_t CanRunMore() const = 0; |
| + virtual size_t CanRunMore() = 0; |
| + virtual bool AcquireToken() = 0; |
| virtual bool StartCommand(Edge* edge) = 0; |
| |
| /// The result of waiting for a command. |
| @@ -157,7 +161,9 @@ 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; |
| + /// 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*>(); } |
| virtual void Abort() {} |
| @@ -165,7 +171,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 { |
| @@ -177,6 +184,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. |
| --- /dev/null |
| +++ b/src/tokenpool-gnu-make.cc |
| @@ -0,0 +1,108 @@ |
| +// 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 <stdlib.h> |
| +#include <stdio.h> |
| +#include <string.h> |
| + |
| +#include "line_printer.h" |
| + |
| +// TokenPool implementation for GNU make jobserver - common bits |
| +// every instance owns an implicit token -> available_ == 1 |
| +GNUmakeTokenPool::GNUmakeTokenPool() : available_(1), used_(0) { |
| +} |
| + |
| +GNUmakeTokenPool::~GNUmakeTokenPool() { |
| +} |
| + |
| +bool GNUmakeTokenPool::Setup(bool ignore, |
| + bool verbose, |
| + double& max_load_average) { |
| + 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 |
| + 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; |
| + } |
| + } |
| + } |
| + |
| + return false; |
| +} |
| + |
| +bool GNUmakeTokenPool::Acquire() { |
| + if (available_ > 0) |
| + return true; |
| + |
| + if (AcquireToken()) { |
| + // token acquired |
| + available_++; |
| + return true; |
| + } |
| + |
| + // no token available |
| + return false; |
| +} |
| + |
| +void GNUmakeTokenPool::Reserve() { |
| + available_--; |
| + used_++; |
| +} |
| + |
| +void GNUmakeTokenPool::Return() { |
| + if (ReturnToken()) |
| + available_--; |
| +} |
| + |
| +void GNUmakeTokenPool::Release() { |
| + available_++; |
| + used_--; |
| + if (available_ > 1) |
| + Return(); |
| +} |
| + |
| +void GNUmakeTokenPool::Clear() { |
| + while (used_ > 0) |
| + Release(); |
| + while (available_ > 1) |
| + Return(); |
| +} |
| --- /dev/null |
| +++ b/src/tokenpool.h |
| @@ -0,0 +1,42 @@ |
| +// 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. |
| + |
| +#ifdef _WIN32 |
| +#include <windows.h> |
| +#endif |
| + |
| +// 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 false if token pool setup failed |
| + virtual bool Setup(bool ignore, bool verbose, double& max_load_average) = 0; |
| + |
| +#ifdef _WIN32 |
| + 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 TokenPool* Get(); |
| +}; |
| --- a/src/build_test.cc |
| +++ b/src/build_test.cc |
| @@ -15,6 +15,7 @@ |
| #include "build.h" |
| |
| #include <assert.h> |
| +#include <stdarg.h> |
| #include <climits> |
| #include <stdint.h> |
| |
| @@ -521,9 +522,10 @@ struct FakeCommandRunner : public Comman |
| max_active_edges_(1), fs_(fs) {} |
| |
| // CommandRunner impl |
| - virtual size_t CanRunMore() const; |
| + virtual size_t CanRunMore(); |
| + 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(); |
| |
| @@ -622,13 +624,17 @@ void BuildTest::RebuildTarget(const stri |
| builder.command_runner_.release(); |
| } |
| |
| -size_t FakeCommandRunner::CanRunMore() const { |
| +size_t FakeCommandRunner::CanRunMore() { |
| if (active_edges_.size() < max_active_edges_) |
| return SIZE_MAX; |
| |
| return 0; |
| } |
| |
| +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) |
| @@ -720,7 +726,7 @@ bool FakeCommandRunner::StartCommand(Edg |
| return true; |
| } |
| |
| -bool FakeCommandRunner::WaitForCommand(Result* result) { |
| +bool FakeCommandRunner::WaitForCommand(Result* result, bool more_ready) { |
| if (active_edges_.empty()) |
| return false; |
| |
| @@ -4380,3 +4386,355 @@ TEST_F(BuildTest, ValidationWithCircular |
| 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(); |
| + 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() { |
| + if (can_run_more_.size() == 0) { |
| + EXPECT_FALSE("unexpected call to CommandRunner::CanRunMore()"); |
| + return false; |
| + } |
| + |
| + bool result = can_run_more_[0]; |
| + |
| + can_run_more_.erase( |
| + 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 ap); |
| +}; |
| + |
| +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, 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"); |
| +} |
| --- 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_ |
| --- 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 str |
| } |
| |
| #ifdef USE_PPOLL |
| -bool SubprocessSet::DoWork() { |
| +bool SubprocessSet::DoWork(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(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) |
| --- 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> |
| @@ -251,11 +252,14 @@ Subprocess *SubprocessSet::Add(const str |
| return subprocess; |
| } |
| |
| -bool SubprocessSet::DoWork() { |
| +bool SubprocessSet::DoWork(TokenPool* tokens) { |
| DWORD bytes_read; |
| 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() { |
| // delivered by NotifyInterrupted above. |
| return true; |
| |
| + if (tokens && tokens->TokenIsAvailable((ULONG_PTR)subproc)) { |
| + token_available_ = true; |
| + return false; |
| + } |
| + |
| subproc->OnPipeReady(); |
| |
| if (subproc->Done()) { |
| --- 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(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_; |
| --- 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,30 @@ const char* kSimpleCommand = "cmd /c dir |
| const char* kSimpleCommand = "ls /"; |
| #endif |
| |
| +struct TestTokenPool : public TokenPool { |
| + bool Acquire() { return false; } |
| + void Reserve() {} |
| + void Release() {} |
| + void Clear() {} |
| + bool Setup(bool ignore_unused, bool verbose, double& max_load_average) { return false; } |
| + |
| +#ifdef _WIN32 |
| + 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; } |
| +#endif |
| +}; |
| + |
| struct SubprocessTest : public testing::Test { |
| SubprocessSet subprocs_; |
| + TestTokenPool tokens_; |
| }; |
| |
| } // anonymous namespace |
| @@ -45,10 +68,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_FALSE(subprocs_.IsTokenAvailable()); |
| |
| EXPECT_EQ(ExitFailure, subproc->Finish()); |
| EXPECT_NE("", subproc->GetOutput()); |
| @@ -59,10 +84,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_FALSE(subprocs_.IsTokenAvailable()); |
| |
| EXPECT_EQ(ExitFailure, subproc->Finish()); |
| EXPECT_NE("", subproc->GetOutput()); |
| @@ -78,9 +105,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_FALSE(subprocs_.IsTokenAvailable()); |
| |
| EXPECT_EQ(ExitInterrupted, subproc->Finish()); |
| } |
| @@ -90,7 +119,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 +131,11 @@ TEST_F(SubprocessTest, InterruptChildWit |
| Subprocess* subproc = subprocs_.Add("kill -TERM $$"); |
| ASSERT_NE((Subprocess *) 0, subproc); |
| |
| + subprocs_.ResetTokenAvailable(); |
| while (!subproc->Done()) { |
| - subprocs_.DoWork(); |
| + subprocs_.DoWork(NULL); |
| } |
| + ASSERT_FALSE(subprocs_.IsTokenAvailable()); |
| |
| EXPECT_EQ(ExitInterrupted, subproc->Finish()); |
| } |
| @@ -114,7 +145,7 @@ TEST_F(SubprocessTest, InterruptParentWi |
| ASSERT_NE((Subprocess *) 0, subproc); |
| |
| while (!subproc->Done()) { |
| - bool interrupted = subprocs_.DoWork(); |
| + bool interrupted = subprocs_.DoWork(NULL); |
| if (interrupted) |
| return; |
| } |
| @@ -126,9 +157,11 @@ TEST_F(SubprocessTest, InterruptChildWit |
| Subprocess* subproc = subprocs_.Add("kill -HUP $$"); |
| ASSERT_NE((Subprocess *) 0, subproc); |
| |
| + subprocs_.ResetTokenAvailable(); |
| while (!subproc->Done()) { |
| - subprocs_.DoWork(); |
| + subprocs_.DoWork(NULL); |
| } |
| + ASSERT_FALSE(subprocs_.IsTokenAvailable()); |
| |
| EXPECT_EQ(ExitInterrupted, subproc->Finish()); |
| } |
| @@ -138,7 +171,7 @@ TEST_F(SubprocessTest, InterruptParentWi |
| ASSERT_NE((Subprocess *) 0, subproc); |
| |
| while (!subproc->Done()) { |
| - bool interrupted = subprocs_.DoWork(); |
| + bool interrupted = subprocs_.DoWork(NULL); |
| if (interrupted) |
| return; |
| } |
| @@ -153,9 +186,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_FALSE(subprocs_.IsTokenAvailable()); |
| |
| EXPECT_EQ(ExitSuccess, subproc->Finish()); |
| } |
| @@ -167,9 +202,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_FALSE(subprocs_.IsTokenAvailable()); |
| ASSERT_EQ(ExitSuccess, subproc->Finish()); |
| ASSERT_NE("", subproc->GetOutput()); |
| |
| @@ -200,12 +237,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_FALSE(subprocs_.IsTokenAvailable()); |
| ASSERT_EQ(0u, subprocs_.running_.size()); |
| ASSERT_EQ(3u, subprocs_.finished_.size()); |
| |
| @@ -237,8 +275,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_FALSE(subprocs_.IsTokenAvailable()); |
| for (size_t i = 0; i < procs.size(); ++i) { |
| ASSERT_EQ(ExitSuccess, procs[i]->Finish()); |
| ASSERT_NE("", procs[i]->GetOutput()); |
| @@ -254,10 +294,91 @@ 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_FALSE(subprocs_.IsTokenAvailable()); |
| ASSERT_EQ(ExitSuccess, subproc->Finish()); |
| ASSERT_EQ(1u, subprocs_.finished_.size()); |
| } |
| #endif // _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 control 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()); |
| + |
| + 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 |
| +#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()); |
| + EXPECT_NE("", subproc->GetOutput()); |
| + |
| + EXPECT_EQ(1u, subprocs_.finished_.size()); |
| +} |
| --- a/src/ninja.cc |
| +++ b/src/ninja.cc |
| @@ -1466,6 +1466,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; |
| } |
| --- /dev/null |
| +++ b/src/tokenpool_test.cc |
| @@ -0,0 +1,279 @@ |
| +// 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" |
| + |
| +#ifdef _WIN32 |
| +#include <windows.h> |
| +#else |
| +#include <unistd.h> |
| +#endif |
| + |
| +#include <stdio.h> |
| +#include <stdlib.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) |
| +#endif |
| + |
| +namespace { |
| + |
| +const double kLoadAverageDefault = -1.23456789; |
| + |
| +struct TokenPoolTest : public testing::Test { |
| + double load_avg_; |
| + TokenPool* tokens_; |
| + char buf_[1024]; |
| +#ifdef _WIN32 |
| + const char* semaphore_name_; |
| + HANDLE semaphore_; |
| +#else |
| + int fds_[2]; |
| + |
| + char random() { |
| + return int((rand() / double(RAND_MAX)) * 256); |
| + } |
| +#endif |
| + |
| + virtual void SetUp() { |
| + load_avg_ = kLoadAverageDefault; |
| + tokens_ = NULL; |
| + ENVIRONMENT_CLEAR(); |
| +#ifdef _WIN32 |
| + semaphore_name_ = SEMAPHORE_NAME; |
| + if ((semaphore_ = CreateSemaphore(0, 0, 2, SEMAPHORE_NAME)) == NULL) |
| +#else |
| + if (pipe(fds_) < 0) |
| +#endif |
| + ASSERT_TRUE(false); |
| + } |
| + |
| + void CreatePool(const char* format, bool ignore_jobserver = false) { |
| + if (format) { |
| + sprintf(buf_, format, |
| +#ifdef _WIN32 |
| + semaphore_name_ |
| +#else |
| + fds_[0], fds_[1] |
| +#endif |
| + ); |
| + ENVIRONMENT_INIT(buf_); |
| + } |
| + if ((tokens_ = TokenPool::Get()) != NULL) { |
| + if (!tokens_->Setup(ignore_jobserver, false, load_avg_)) { |
| + delete tokens_; |
| + tokens_ = NULL; |
| + } |
| + } |
| + } |
| + |
| + void CreateDefaultPool() { |
| + CreatePool(AUTH_FORMAT("--jobserver-auth")); |
| + } |
| + |
| + virtual void TearDown() { |
| + if (tokens_) |
| + delete tokens_; |
| +#ifdef _WIN32 |
| + CloseHandle(semaphore_); |
| +#else |
| + close(fds_[0]); |
| + close(fds_[1]); |
| +#endif |
| + ENVIRONMENT_CLEAR(); |
| + } |
| +}; |
| + |
| +} // anonymous namespace |
| + |
| +// verifies none implementation |
| +TEST_F(TokenPoolTest, NoTokenPool) { |
| + CreatePool(NULL, false); |
| + |
| + EXPECT_EQ(NULL, tokens_); |
| + EXPECT_EQ(kLoadAverageDefault, load_avg_); |
| +} |
| + |
| +TEST_F(TokenPoolTest, SuccessfulOldSetup) { |
| + // GNUmake <= 4.1 |
| + CreatePool(AUTH_FORMAT("--jobserver-fds")); |
| + |
| + 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(AUTH_FORMAT("--jobserver-auth"), true); |
| + |
| + EXPECT_EQ(NULL, tokens_); |
| + EXPECT_EQ(kLoadAverageDefault, load_avg_); |
| +} |
| + |
| +TEST_F(TokenPoolTest, HonorLN) { |
| + 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(); |
| + |
| + ASSERT_NE(NULL, tokens_); |
| + EXPECT_EQ(kLoadAverageDefault, load_avg_); |
| + |
| + EXPECT_EQ(fds_[0], tokens_->GetMonitorFd()); |
| +} |
| +#endif |
| + |
| +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 |
| +#ifdef _WIN32 |
| + LONG previous; |
| + ASSERT_TRUE(ReleaseSemaphore(semaphore_, 1, &previous)); |
| + ASSERT_EQ(0, previous); |
| +#else |
| + char test_tokens[1] = { random() }; |
| + ASSERT_EQ(1u, write(fds_[1], test_tokens, sizeof(test_tokens))); |
| +#endif |
| + EXPECT_TRUE(tokens_->Acquire()); |
| + tokens_->Reserve(); |
| + EXPECT_FALSE(tokens_->Acquire()); |
| + |
| + // release 2nd token |
| + tokens_->Release(); |
| + EXPECT_TRUE(tokens_->Acquire()); |
| + |
| + // release implicit token - must return 2nd token back to jobserver |
| + tokens_->Release(); |
| + EXPECT_TRUE(tokens_->Acquire()); |
| + |
| + // 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_))); |
| + EXPECT_EQ(test_tokens[0], buf_[0]); |
| +#endif |
| + |
| + // 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 |
| +#ifdef _WIN32 |
| + LONG previous; |
| + ASSERT_TRUE(ReleaseSemaphore(semaphore_, 2, &previous)); |
| + ASSERT_EQ(0, previous); |
| +#else |
| + char test_tokens[2] = { random(), random() }; |
| + ASSERT_EQ(2u, write(fds_[1], test_tokens, sizeof(test_tokens))); |
| +#endif |
| + 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 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_))); |
| + // tokens are pushed onto a stack, hence returned in reverse order |
| + EXPECT_EQ(test_tokens[0], buf_[1]); |
| + EXPECT_EQ(test_tokens[1], buf_[0]); |
| +#endif |
| + |
| + // implicit token |
| + EXPECT_TRUE(tokens_->Acquire()); |
| +} |
| --- /dev/null |
| +++ b/src/tokenpool-gnu-make-posix.cc |
| @@ -0,0 +1,214 @@ |
| +// 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> |
| +#include <stack> |
| + |
| +// 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_; |
| + |
| + // See https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html |
| + // |
| + // It’s important that when you release the job slot, you write back |
| + // the same character you read. Don’t assume that all tokens are the |
| + // same character different characters may have different meanings to |
| + // GNU make. The order is not important, since make has no idea in |
| + // what order jobs will complete anyway. |
| + // |
| + std::stack<char> tokens_; |
| + |
| + 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); |
| + return ret >= 0; |
| +} |
| + |
| +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; |
| + } |
| + 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; |
| + char buf; |
| + |
| + // 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) { |
| + // 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) { |
| + tokens_.push(buf); |
| + 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 = tokens_.top(); |
| + while (1) { |
| + int ret = write(wfd_, &buf, 1); |
| + if (ret > 0) { |
| + tokens_.pop(); |
| + return true; |
| + } |
| + if ((ret != -1) || (errno != EINTR)) |
| + return false; |
| + // write got interrupted - retry |
| + } |
| +} |
| + |
| +int GNUmakeTokenPoolPosix::GetMonitorFd() { |
| + return rfd_; |
| +} |
| + |
| +TokenPool* TokenPool::Get() { |
| + return new GNUmakeTokenPoolPosix; |
| +} |
| --- /dev/null |
| +++ b/src/tokenpool-gnu-make-win32.cc |
| @@ -0,0 +1,239 @@ |
| +// 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 this first. |
| +// Otherwise the other system headers don't work correctly under Win32 |
| +#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(name, 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((PAPCFUNC)&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"); |
| +} |
| + |
| +TokenPool* TokenPool::Get() { |
| + return new GNUmakeTokenPoolWin32; |
| +} |
| --- /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(); |
| + ~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(); |
| +}; |
| --- a/CMakeLists.txt |
| +++ b/CMakeLists.txt |
| @@ -142,6 +142,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 |
| ) |
| @@ -153,13 +154,17 @@ if(WIN32) |
| src/msvc_helper_main-win32.cc |
| src/getopt.c |
| src/minidump-win32.cc |
| + src/tokenpool-gnu-make-win32.cc |
| ) |
| # Build getopt.c, which can be compiled as either C or C++, as C++ |
| # so that build environments which lack a C compiler, but have a C++ |
| # compiler may build ninja. |
| set_source_files_properties(src/getopt.c PROPERTIES LANGUAGE CXX) |
| 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) |
| # Build getopt.c, which can be compiled as either C or C++, as C++ |
| @@ -286,6 +291,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) |