Skip to content

Commit db72b31

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 must be returned to signal a free thread. 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 this parent process, allowing it to control the number of subprocesses that ninja spawns at any given time. This functionality is desirable when ninja is used as part of a bigger build, such as builds with 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 uses all available cores, the system is overloaded. This implementation instantiates the client in real_main() and passes references to the 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 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 2f19d3a commit db72b31

File tree

9 files changed

+366
-40
lines changed

9 files changed

+366
-40
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/subprocess-posix.cc
174+
src/jobserver-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
@@ -565,6 +565,7 @@ def has_re2c() -> bool:
565565
objs += cc('getopt')
566566
else:
567567
objs += cxx('subprocess-posix')
568+
objs += cxx('jobserver-posix')
568569
if platform.is_aix():
569570
objs += cc('getopt')
570571
if platform.is_msvc():

src/build.cc

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,26 @@ Edge* Plan::FindWork() {
164164
if (ready_.empty())
165165
return NULL;
166166

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

@@ -201,6 +219,16 @@ bool Plan::EdgeFinished(Edge* edge, EdgeResult result, string* err) {
201219
edge->pool()->EdgeFinished(*edge);
202220
edge->pool()->RetrieveReadyEdges(&ready_);
203221

222+
// TODO: jobserver client support for Windows
223+
#ifndef _WIN32
224+
// If jobserver is used, return the token for this job.
225+
if (builder_ && builder_->jobserver_ &&
226+
edge->has_job_token_) {
227+
builder_->jobserver_->Release();
228+
edge->has_job_token_ = false;
229+
}
230+
#endif
231+
204232
// The rest of this function only applies to successful commands.
205233
if (result != kEdgeSucceeded)
206234
return true;
@@ -594,7 +622,9 @@ void Plan::Dump() const {
594622
}
595623

596624
struct RealCommandRunner : public CommandRunner {
597-
explicit RealCommandRunner(const BuildConfig& config) : config_(config) {}
625+
explicit RealCommandRunner(const BuildConfig& config, Jobserver* jobserver) :
626+
config_(config), jobserver_(jobserver) {}
627+
598628
virtual ~RealCommandRunner() {}
599629
virtual size_t CanRunMore() const;
600630
virtual bool StartCommand(Edge* edge);
@@ -603,6 +633,7 @@ struct RealCommandRunner : public CommandRunner {
603633
virtual void Abort();
604634

605635
const BuildConfig& config_;
636+
Jobserver* jobserver_;
606637
SubprocessSet subprocs_;
607638
map<const Subprocess*, Edge*> subproc_to_edge_;
608639
};
@@ -617,6 +648,10 @@ vector<Edge*> RealCommandRunner::GetActiveEdges() {
617648

618649
void RealCommandRunner::Abort() {
619650
subprocs_.Clear();
651+
// TODO: jobserver client support for Windows
652+
#ifndef _WIN32
653+
jobserver_->Clear();
654+
#endif
620655
}
621656

622657
size_t RealCommandRunner::CanRunMore() const {
@@ -631,6 +666,18 @@ size_t RealCommandRunner::CanRunMore() const {
631666
capacity = load_capacity;
632667
}
633668

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

@@ -670,10 +717,10 @@ bool RealCommandRunner::WaitForCommand(Result* result) {
670717
return true;
671718
}
672719

673-
Builder::Builder(State* state, const BuildConfig& config, BuildLog* build_log,
674-
DepsLog* deps_log, DiskInterface* disk_interface,
675-
Status* status, int64_t start_time_millis)
676-
: state_(state), config_(config), plan_(this), status_(status),
720+
Builder::Builder(State* state, const BuildConfig& config, Jobserver* jobserver,
721+
BuildLog* build_log, DepsLog* deps_log, DiskInterface* disk_interface,
722+
Status* status, int64_t start_time_millis) : state_(state),
723+
config_(config), jobserver_(jobserver), plan_(this), status_(status),
677724
start_time_millis_(start_time_millis), disk_interface_(disk_interface),
678725
explanations_(g_explaining ? new Explanations() : nullptr),
679726
scan_(state, build_log, deps_log, disk_interface,
@@ -778,7 +825,7 @@ bool Builder::Build(string* err) {
778825
if (config_.dry_run)
779826
command_runner_.reset(new DryRunCommandRunner);
780827
else
781-
command_runner_.reset(new RealCommandRunner(config_));
828+
command_runner_.reset(new RealCommandRunner(config_, jobserver_));
782829
}
783830

784831
// 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)