Skip to content

Commit 9d67962

Browse files
mcprathundeboll
andcommitted
Implement GNU jobserver posix client support
The core principle of a jobserver is simple: before starting a new job (edge in ninja-speak), a token must be acquired from an external entity as approval. Once a job is finished, the token is returned to represent a free job slot. In the case of GNU Make, this external entity is the parent process which has executed Ninja and is managing the load capacity for all subprocesses which it has spawned. Introducing client support for this model allows Ninja to give load capacity management to it's parent process, allowing it to control the number of subprocesses that Ninja spawns at any given time. This functionality is desirable when Ninja is part of a bigger build, such as Yocto/OpenEmbedded, Openwrt/Linux, Buildroot, and Android. Here, multiple compile jobs are executed in parallel in order to maximize cpu utilization, but if each compile job in Ninja uses all available cores, the system is overloaded. This implementation instantiates the client in real_main() and passes pointers to the Jobserver class into other classes. All tokens are returned whenever the CommandRunner aborts, and the current number of tokens compared to the current number of running subprocesses controls the available load capacity, used to determine how many new tokens to attempt to acquire in order to try to start another job for each loop to find work. Jobserver related functions are defined as no-op for Windows pending Windows-specific support for the jobserver. Co-authored-by: Martin Hundebøll <martin@geanix.com> Co-developed-by: Martin Hundebøll <martin@geanix.com> Signed-off-by: Martin Hundebøll <martin@geanix.com> Signed-off-by: Michael Pratt <mcpratt@pm.me>
1 parent 4b7d399 commit 9d67962

File tree

9 files changed

+376
-44
lines changed

9 files changed

+376
-44
lines changed

CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,10 @@ if(WIN32)
169169
# errors by telling windows.h to not define those two.
170170
add_compile_definitions(NOMINMAX)
171171
else()
172-
target_sources(libninja PRIVATE src/subprocess-posix.cc)
172+
target_sources(libninja PRIVATE
173+
src/jobserver-posix.cc
174+
src/subprocess-posix.cc
175+
)
173176
if(CMAKE_SYSTEM_NAME STREQUAL "OS400" OR CMAKE_SYSTEM_NAME STREQUAL "AIX")
174177
target_sources(libninja PRIVATE src/getopt.c)
175178
# Build getopt.c, which can be compiled as either C or C++, as C++

configure.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,7 @@ def has_re2c() -> bool:
564564
objs += cxx('minidump-win32', variables=cxxvariables)
565565
objs += cc('getopt')
566566
else:
567+
objs += cxx('jobserver-posix')
567568
objs += cxx('subprocess-posix')
568569
if platform.is_aix():
569570
objs += cc('getopt')

src/build.cc

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,16 @@ Edge* Plan::FindWork() {
163163
return NULL;
164164

165165
Edge* work = ready_.top();
166+
167+
// Only initiate work if the jobserver client can acquire a token.
168+
if (builder_ && builder_->jobserver_ &&
169+
builder_->jobserver_->Enabled()) {
170+
int job_tokens = builder_->jobserver_->Tokens();
171+
work->job_token_ = builder_->jobserver_->Acquire();
172+
if (job_tokens == builder_->jobserver_->Tokens())
173+
return NULL;
174+
}
175+
166176
ready_.pop();
167177
return work;
168178
}
@@ -199,6 +209,10 @@ bool Plan::EdgeFinished(Edge* edge, EdgeResult result, string* err) {
199209
edge->pool()->EdgeFinished(*edge);
200210
edge->pool()->RetrieveReadyEdges(&ready_);
201211

212+
// If jobserver is used, return the token for this job.
213+
if (builder_ && builder_->jobserver_)
214+
builder_->jobserver_->Release(&edge->job_token_);
215+
202216
// The rest of this function only applies to successful commands.
203217
if (result != kEdgeSucceeded)
204218
return true;
@@ -592,14 +606,18 @@ void Plan::Dump() const {
592606
}
593607

594608
struct RealCommandRunner : public CommandRunner {
595-
explicit RealCommandRunner(const BuildConfig& config) : config_(config) {}
609+
explicit RealCommandRunner(const BuildConfig& config, Jobserver* jobserver) :
610+
config_(config), jobserver_(jobserver) {}
611+
596612
size_t CanRunMore() const override;
597613
bool StartCommand(Edge* edge) override;
598614
bool WaitForCommand(Result* result) override;
599615
vector<Edge*> GetActiveEdges() override;
616+
void ClearJobTokens(const std::vector<Edge*>&) override;
600617
void Abort() override;
601618

602619
const BuildConfig& config_;
620+
Jobserver* jobserver_;
603621
SubprocessSet subprocs_;
604622
map<const Subprocess*, Edge*> subproc_to_edge_;
605623
};
@@ -612,7 +630,13 @@ vector<Edge*> RealCommandRunner::GetActiveEdges() {
612630
return edges;
613631
}
614632

633+
void RealCommandRunner::ClearJobTokens(const std::vector<Edge*> &edges) {
634+
for (Edge* edge : edges)
635+
jobserver_->Release(&edge->job_token_);
636+
}
637+
615638
void RealCommandRunner::Abort() {
639+
ClearJobTokens(GetActiveEdges());
616640
subprocs_.Clear();
617641
}
618642

@@ -628,6 +652,14 @@ size_t RealCommandRunner::CanRunMore() const {
628652
capacity = load_capacity;
629653
}
630654

655+
// When initialized, behave as if the implicit token is acquired already.
656+
// Otherwise, this happens after a token is released but before it is replaced,
657+
// so the base capacity is represented by job_tokens + 1 when positive.
658+
// Add an extra loop on capacity for each job in order to get an extra token.
659+
int job_tokens = jobserver_->Tokens();
660+
if (job_tokens)
661+
capacity = abs(job_tokens) - subproc_number + 2;
662+
631663
if (capacity < 0)
632664
capacity = 0;
633665

@@ -667,10 +699,10 @@ bool RealCommandRunner::WaitForCommand(Result* result) {
667699
return true;
668700
}
669701

670-
Builder::Builder(State* state, const BuildConfig& config, BuildLog* build_log,
671-
DepsLog* deps_log, DiskInterface* disk_interface,
672-
Status* status, int64_t start_time_millis)
673-
: state_(state), config_(config), plan_(this), status_(status),
702+
Builder::Builder(State* state, const BuildConfig& config, Jobserver* jobserver,
703+
BuildLog* build_log, DepsLog* deps_log, DiskInterface* disk_interface,
704+
Status* status, int64_t start_time_millis) : state_(state),
705+
config_(config), jobserver_(jobserver), plan_(this), status_(status),
674706
start_time_millis_(start_time_millis), disk_interface_(disk_interface),
675707
explanations_(g_explaining ? new Explanations() : nullptr),
676708
scan_(state, build_log, deps_log, disk_interface,
@@ -775,7 +807,7 @@ bool Builder::Build(string* err) {
775807
if (config_.dry_run)
776808
command_runner_.reset(new DryRunCommandRunner);
777809
else
778-
command_runner_.reset(new RealCommandRunner(config_));
810+
command_runner_.reset(new RealCommandRunner(config_, jobserver_));
779811
}
780812

781813
// We are about to start the build process.

src/build.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "depfile_parser.h"
2525
#include "exit_status.h"
2626
#include "graph.h"
27+
#include "jobserver.h"
2728
#include "util.h" // int64_t
2829

2930
struct BuildLog;
@@ -161,6 +162,7 @@ struct CommandRunner {
161162
virtual bool WaitForCommand(Result* result) = 0;
162163

163164
virtual std::vector<Edge*> GetActiveEdges() { return std::vector<Edge*>(); }
165+
virtual void ClearJobTokens(const std::vector<Edge*>&) {}
164166
virtual void Abort() {}
165167
};
166168

@@ -187,9 +189,9 @@ struct BuildConfig {
187189

188190
/// Builder wraps the build process: starting commands, updating status.
189191
struct Builder {
190-
Builder(State* state, const BuildConfig& config, BuildLog* build_log,
191-
DepsLog* deps_log, DiskInterface* disk_interface, Status* status,
192-
int64_t start_time_millis);
192+
Builder(State* state, const BuildConfig& config, Jobserver* jobserver,
193+
BuildLog* build_log, DepsLog* deps_log, DiskInterface* disk_interface,
194+
Status* status, int64_t start_time_millis);
193195
~Builder();
194196

195197
/// Clean up after interrupted commands by deleting output files.
@@ -224,6 +226,7 @@ struct Builder {
224226

225227
State* state_;
226228
const BuildConfig& config_;
229+
Jobserver* jobserver_;
227230
Plan plan_;
228231
std::unique_ptr<CommandRunner> command_runner_;
229232
Status* status_;

0 commit comments

Comments
 (0)