Skip to content

Commit 7b35dc7

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 the NinjaMain class 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. Calls to functions are excluded from Windows builds pending Windows-specific support for the jobserver. Co-authored-by: Martin Hundebøll <[email protected]> Co-developed-by: Martin Hundebøll <[email protected]> Signed-off-by: Martin Hundebøll <[email protected]> Signed-off-by: Michael Pratt <[email protected]>
1 parent 4b7d399 commit 7b35dc7

File tree

9 files changed

+391
-44
lines changed

9 files changed

+391
-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: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,26 @@ Edge* Plan::FindWork() {
162162
if (ready_.empty())
163163
return NULL;
164164

165+
// TODO: jobserver client support for Windows
166+
#ifndef _WIN32
167+
// Only initiate work if the jobserver can acquire a token.
168+
if (builder_ && builder_->jobserver_ &&
169+
builder_->jobserver_->Enabled() &&
170+
!builder_->jobserver_->Acquire()) {
171+
return NULL;
172+
}
173+
#endif
174+
165175
Edge* work = ready_.top();
166176
ready_.pop();
177+
178+
// TODO: jobserver client support for Windows
179+
#ifndef _WIN32
180+
// Mark this edge as using a job token to be released when finished.
181+
if (builder_ && builder_->jobserver_)
182+
work->has_job_token_ = builder_->jobserver_->Enabled();
183+
#endif
184+
167185
return work;
168186
}
169187

@@ -199,6 +217,16 @@ bool Plan::EdgeFinished(Edge* edge, EdgeResult result, string* err) {
199217
edge->pool()->EdgeFinished(*edge);
200218
edge->pool()->RetrieveReadyEdges(&ready_);
201219

220+
// TODO: jobserver client support for Windows
221+
#ifndef _WIN32
222+
// If jobserver is used, return the token for this job.
223+
if (builder_ && builder_->jobserver_ &&
224+
edge->has_job_token_) {
225+
builder_->jobserver_->Release();
226+
edge->has_job_token_ = false;
227+
}
228+
#endif
229+
202230
// The rest of this function only applies to successful commands.
203231
if (result != kEdgeSucceeded)
204232
return true;
@@ -592,14 +620,17 @@ void Plan::Dump() const {
592620
}
593621

594622
struct RealCommandRunner : public CommandRunner {
595-
explicit RealCommandRunner(const BuildConfig& config) : config_(config) {}
623+
explicit RealCommandRunner(const BuildConfig& config, Jobserver* jobserver) :
624+
config_(config), jobserver_(jobserver) {}
625+
596626
size_t CanRunMore() const override;
597627
bool StartCommand(Edge* edge) override;
598628
bool WaitForCommand(Result* result) override;
599629
vector<Edge*> GetActiveEdges() override;
600630
void Abort() override;
601631

602632
const BuildConfig& config_;
633+
Jobserver* jobserver_;
603634
SubprocessSet subprocs_;
604635
map<const Subprocess*, Edge*> subproc_to_edge_;
605636
};
@@ -614,6 +645,10 @@ vector<Edge*> RealCommandRunner::GetActiveEdges() {
614645

615646
void RealCommandRunner::Abort() {
616647
subprocs_.Clear();
648+
// TODO: jobserver client support for Windows
649+
#ifndef _WIN32
650+
jobserver_->Clear();
651+
#endif
617652
}
618653

619654
size_t RealCommandRunner::CanRunMore() const {
@@ -628,6 +663,18 @@ size_t RealCommandRunner::CanRunMore() const {
628663
capacity = load_capacity;
629664
}
630665

666+
// TODO: jobserver client support for Windows
667+
#ifndef _WIN32
668+
int job_tokens = jobserver_->Tokens();
669+
670+
// When initialized, behave as if the implicit token is acquired already.
671+
// Otherwise, this happens after a token is released but before it is replaced,
672+
// so the base capacity is represented by job_tokens + 1 when positive.
673+
// Add an extra loop on capacity for each job in order to get an extra token.
674+
if (job_tokens)
675+
capacity = abs(job_tokens) - subproc_number + 2;
676+
#endif
677+
631678
if (capacity < 0)
632679
capacity = 0;
633680

@@ -667,10 +714,10 @@ bool RealCommandRunner::WaitForCommand(Result* result) {
667714
return true;
668715
}
669716

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),
717+
Builder::Builder(State* state, const BuildConfig& config, Jobserver* jobserver,
718+
BuildLog* build_log, DepsLog* deps_log, DiskInterface* disk_interface,
719+
Status* status, int64_t start_time_millis) : state_(state),
720+
config_(config), jobserver_(jobserver), plan_(this), status_(status),
674721
start_time_millis_(start_time_millis), disk_interface_(disk_interface),
675722
explanations_(g_explaining ? new Explanations() : nullptr),
676723
scan_(state, build_log, deps_log, disk_interface,
@@ -775,7 +822,7 @@ bool Builder::Build(string* err) {
775822
if (config_.dry_run)
776823
command_runner_.reset(new DryRunCommandRunner);
777824
else
778-
command_runner_.reset(new RealCommandRunner(config_));
825+
command_runner_.reset(new RealCommandRunner(config_, jobserver_));
779826
}
780827

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

src/build.h

Lines changed: 5 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;
@@ -187,9 +188,9 @@ struct BuildConfig {
187188

188189
/// Builder wraps the build process: starting commands, updating status.
189190
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);
191+
Builder(State* state, const BuildConfig& config, Jobserver* jobserver,
192+
BuildLog* build_log, DepsLog* deps_log, DiskInterface* disk_interface,
193+
Status* status, int64_t start_time_millis);
193194
~Builder();
194195

195196
/// Clean up after interrupted commands by deleting output files.
@@ -224,6 +225,7 @@ struct Builder {
224225

225226
State* state_;
226227
const BuildConfig& config_;
228+
Jobserver* jobserver_;
227229
Plan plan_;
228230
std::unique_ptr<CommandRunner> command_runner_;
229231
Status* status_;

0 commit comments

Comments
 (0)