Skip to content

Commit 96e2c8a

Browse files
Stefan Beckerstefanb2
Stefan Becker
authored andcommitted
Add GNU make jobserver client support
- add new TokenPool interface - GNU make implementation for TokenPool parses and verifies the magic information from the MAKEFLAGS environment variable - RealCommandRunner tries to acquire TokenPool * if no token pool is available then there is no change in behaviour - When a token pool is available then RealCommandRunner behaviour changes as follows * CanRunMore() only returns true if TokenPool::Acquire() returns true * StartCommand() calls TokenPool::Reserve() * WaitForCommand() calls TokenPool::Release() Documentation for GNU make jobserver http://make.mad-scientist.net/papers/jobserver-implementation/ Fixes ninja-build#1139
1 parent e234a7b commit 96e2c8a

File tree

6 files changed

+308
-20
lines changed

6 files changed

+308
-20
lines changed

Diff for: configure.py

+2
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,7 @@ def has_re2c():
499499
objs += cxx(name)
500500
if platform.is_windows():
501501
for name in ['subprocess-win32',
502+
'tokenpool-none',
502503
'includes_normalize-win32',
503504
'msvc_helper-win32',
504505
'msvc_helper_main-win32']:
@@ -508,6 +509,7 @@ def has_re2c():
508509
objs += cc('getopt')
509510
else:
510511
objs += cxx('subprocess-posix')
512+
objs += cxx('tokenpool-gnu-make')
511513
if platform.is_aix():
512514
objs += cc('getopt')
513515
if platform.is_msvc():

Diff for: src/build.cc

+39-20
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "graph.h"
3939
#include "state.h"
4040
#include "subprocess.h"
41+
#include "tokenpool.h"
4142
#include "util.h"
4243

4344
namespace {
@@ -347,7 +348,7 @@ bool Plan::AddSubTarget(Node* node, Node* dependent, string* err) {
347348
}
348349

349350
Edge* Plan::FindWork() {
350-
if (ready_.empty())
351+
if (!more_ready())
351352
return NULL;
352353
set<Edge*>::iterator e = ready_.begin();
353354
Edge* edge = *e;
@@ -485,8 +486,8 @@ void Plan::Dump() {
485486
}
486487

487488
struct RealCommandRunner : public CommandRunner {
488-
explicit RealCommandRunner(const BuildConfig& config) : config_(config) {}
489-
virtual ~RealCommandRunner() {}
489+
explicit RealCommandRunner(const BuildConfig& config);
490+
virtual ~RealCommandRunner();
490491
virtual bool CanRunMore();
491492
virtual bool StartCommand(Edge* edge);
492493
virtual bool WaitForCommand(Result* result);
@@ -495,9 +496,18 @@ struct RealCommandRunner : public CommandRunner {
495496

496497
const BuildConfig& config_;
497498
SubprocessSet subprocs_;
499+
TokenPool *tokens_;
498500
map<Subprocess*, Edge*> subproc_to_edge_;
499501
};
500502

503+
RealCommandRunner::RealCommandRunner(const BuildConfig& config) : config_(config) {
504+
tokens_ = TokenPool::Get();
505+
}
506+
507+
RealCommandRunner::~RealCommandRunner() {
508+
delete tokens_;
509+
}
510+
501511
vector<Edge*> RealCommandRunner::GetActiveEdges() {
502512
vector<Edge*> edges;
503513
for (map<Subprocess*, Edge*>::iterator e = subproc_to_edge_.begin();
@@ -508,21 +518,27 @@ vector<Edge*> RealCommandRunner::GetActiveEdges() {
508518

509519
void RealCommandRunner::Abort() {
510520
subprocs_.Clear();
521+
if (tokens_)
522+
tokens_->Clear();
511523
}
512524

513525
bool RealCommandRunner::CanRunMore() {
514526
size_t subproc_number =
515527
subprocs_.running_.size() + subprocs_.finished_.size();
516528
return (int)subproc_number < config_.parallelism
517-
&& ((subprocs_.running_.empty() || config_.max_load_average <= 0.0f)
518-
|| GetLoadAverage() < config_.max_load_average);
529+
&& (subprocs_.running_.empty() ||
530+
((config_.max_load_average <= 0.0f ||
531+
GetLoadAverage() < config_.max_load_average)
532+
&& (!tokens_ || tokens_->Acquire())));
519533
}
520534

521535
bool RealCommandRunner::StartCommand(Edge* edge) {
522536
string command = edge->EvaluateCommand();
523537
Subprocess* subproc = subprocs_.Add(command, edge->use_console());
524538
if (!subproc)
525539
return false;
540+
if (tokens_)
541+
tokens_->Reserve();
526542
subproc_to_edge_.insert(make_pair(subproc, edge));
527543

528544
return true;
@@ -536,6 +552,9 @@ bool RealCommandRunner::WaitForCommand(Result* result) {
536552
return false;
537553
}
538554

555+
if (tokens_)
556+
tokens_->Release();
557+
539558
result->status = subproc->Finish();
540559
result->output = subproc->GetOutput();
541560

@@ -644,23 +663,23 @@ bool Builder::Build(string* err) {
644663
// Second, we attempt to wait for / reap the next finished command.
645664
while (plan_.more_to_do()) {
646665
// See if we can start any more commands.
647-
if (failures_allowed && command_runner_->CanRunMore()) {
648-
if (Edge* edge = plan_.FindWork()) {
649-
if (!StartEdge(edge, err)) {
650-
Cleanup();
651-
status_->BuildFinished();
652-
return false;
653-
}
654-
655-
if (edge->is_phony()) {
656-
plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
657-
} else {
658-
++pending_commands;
659-
}
666+
if (failures_allowed && plan_.more_ready() &&
667+
command_runner_->CanRunMore()) {
668+
Edge* edge = plan_.FindWork();
669+
if (!StartEdge(edge, err)) {
670+
Cleanup();
671+
status_->BuildFinished();
672+
return false;
673+
}
660674

661-
// We made some progress; go back to the main loop.
662-
continue;
675+
if (edge->is_phony()) {
676+
plan_.EdgeFinished(edge, Plan::kEdgeSucceeded);
677+
} else {
678+
++pending_commands;
663679
}
680+
681+
// We made some progress; go back to the main loop.
682+
continue;
664683
}
665684

666685
// See if we can reap any finished commands.

Diff for: src/build.h

+3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ struct Plan {
5353
/// Returns true if there's more work to be done.
5454
bool more_to_do() const { return wanted_edges_ > 0 && command_edges_ > 0; }
5555

56+
/// Returns true if there's more edges ready to start
57+
bool more_ready() const { return !ready_.empty(); }
58+
5659
/// Dumps the current state of the plan.
5760
void Dump();
5861

Diff for: src/tokenpool-gnu-make.cc

+211
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// Copyright 2016 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "tokenpool.h"
16+
17+
#include <errno.h>
18+
#include <fcntl.h>
19+
#include <poll.h>
20+
#include <unistd.h>
21+
#include <signal.h>
22+
#include <stdio.h>
23+
#include <string.h>
24+
#include <stdlib.h>
25+
26+
// TokenPool implementation for GNU make jobserver
27+
// (http://make.mad-scientist.net/papers/jobserver-implementation/)
28+
struct GNUmakeTokenPool : public TokenPool {
29+
GNUmakeTokenPool();
30+
virtual ~GNUmakeTokenPool();
31+
32+
virtual bool Acquire();
33+
virtual void Reserve();
34+
virtual void Release();
35+
virtual void Clear();
36+
37+
bool Setup();
38+
39+
private:
40+
int available_;
41+
int used_;
42+
43+
#ifdef _WIN32
44+
// @TODO
45+
#else
46+
int rfd_;
47+
int wfd_;
48+
49+
struct sigaction old_act_;
50+
bool restore_;
51+
52+
static int dup_rfd_;
53+
static void CloseDupRfd(int signum);
54+
55+
bool CheckFd(int fd);
56+
bool SetAlarmHandler();
57+
#endif
58+
59+
void Return();
60+
};
61+
62+
// every instance owns an implicit token -> available_ == 1
63+
GNUmakeTokenPool::GNUmakeTokenPool() : available_(1), used_(0),
64+
rfd_(-1), wfd_(-1), restore_(false) {
65+
}
66+
67+
GNUmakeTokenPool::~GNUmakeTokenPool() {
68+
Clear();
69+
if (restore_)
70+
sigaction(SIGALRM, &old_act_, NULL);
71+
}
72+
73+
bool GNUmakeTokenPool::CheckFd(int fd) {
74+
if (fd < 0)
75+
return false;
76+
int ret = fcntl(fd, F_GETFD);
77+
if (ret < 0)
78+
return false;
79+
return true;
80+
}
81+
82+
int GNUmakeTokenPool::dup_rfd_ = -1;
83+
84+
void GNUmakeTokenPool::CloseDupRfd(int signum) {
85+
close(dup_rfd_);
86+
dup_rfd_ = -1;
87+
}
88+
89+
bool GNUmakeTokenPool::SetAlarmHandler() {
90+
struct sigaction act;
91+
memset(&act, 0, sizeof(act));
92+
act.sa_handler = CloseDupRfd;
93+
if (sigaction(SIGALRM, &act, &old_act_) < 0) {
94+
perror("sigaction:");
95+
return(false);
96+
} else {
97+
restore_ = true;
98+
return(true);
99+
}
100+
}
101+
102+
bool GNUmakeTokenPool::Setup() {
103+
const char *value = getenv("MAKEFLAGS");
104+
if (value) {
105+
// GNU make <= 4.1
106+
const char *jobserver = strstr(value, "--jobserver-fds=");
107+
// GNU make => 4.2
108+
if (!jobserver)
109+
jobserver = strstr(value, "--jobserver-auth=");
110+
if (jobserver) {
111+
int rfd = -1;
112+
int wfd = -1;
113+
if ((sscanf(jobserver, "%*[^=]=%d,%d", &rfd, &wfd) == 2) &&
114+
CheckFd(rfd) &&
115+
CheckFd(wfd) &&
116+
SetAlarmHandler()) {
117+
printf("ninja: using GNU make jobserver.\n");
118+
rfd_ = rfd;
119+
wfd_ = wfd;
120+
return true;
121+
}
122+
}
123+
}
124+
125+
return false;
126+
}
127+
128+
bool GNUmakeTokenPool::Acquire() {
129+
if (available_ > 0)
130+
return true;
131+
132+
#ifdef USE_PPOLL
133+
pollfd pollfds[] = {{rfd_, POLLIN, 0}};
134+
int ret = poll(pollfds, 1, 0);
135+
#else
136+
fd_set set;
137+
struct timeval timeout = { 0, 0 };
138+
FD_ZERO(&set);
139+
FD_SET(rfd_, &set);
140+
int ret = select(rfd_ + 1, &set, NULL, NULL, &timeout);
141+
#endif
142+
if (ret > 0) {
143+
dup_rfd_ = dup(rfd_);
144+
145+
if (dup_rfd_ != -1) {
146+
struct sigaction act, old_act;
147+
int ret = 0;
148+
149+
memset(&act, 0, sizeof(act));
150+
act.sa_handler = CloseDupRfd;
151+
if (sigaction(SIGCHLD, &act, &old_act) == 0) {
152+
char buf;
153+
154+
// block until token read, child exits or timeout
155+
alarm(1);
156+
ret = read(dup_rfd_, &buf, 1);
157+
alarm(0);
158+
159+
sigaction(SIGCHLD, &old_act, NULL);
160+
}
161+
162+
CloseDupRfd(0);
163+
164+
if (ret > 0) {
165+
available_++;
166+
return true;
167+
}
168+
}
169+
}
170+
return false;
171+
}
172+
173+
void GNUmakeTokenPool::Reserve() {
174+
available_--;
175+
used_++;
176+
}
177+
178+
void GNUmakeTokenPool::Return() {
179+
const char buf = '+';
180+
while (1) {
181+
int ret = write(wfd_, &buf, 1);
182+
if (ret > 0)
183+
available_--;
184+
if ((ret != -1) || (errno != EINTR))
185+
return;
186+
// write got interrupted - retry
187+
}
188+
}
189+
190+
void GNUmakeTokenPool::Release() {
191+
available_++;
192+
used_--;
193+
if (available_ > 1)
194+
Return();
195+
}
196+
197+
void GNUmakeTokenPool::Clear() {
198+
while (used_ > 0)
199+
Release();
200+
while (available_ > 1)
201+
Return();
202+
}
203+
204+
struct TokenPool *TokenPool::Get(void) {
205+
GNUmakeTokenPool *tokenpool = new GNUmakeTokenPool;
206+
if (tokenpool->Setup())
207+
return tokenpool;
208+
else
209+
delete tokenpool;
210+
return NULL;
211+
}

Diff for: src/tokenpool-none.cc

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2016 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "tokenpool.h"
16+
17+
#include <fcntl.h>
18+
#include <poll.h>
19+
#include <unistd.h>
20+
#include <stdio.h>
21+
#include <string.h>
22+
#include <stdlib.h>
23+
24+
// No-op TokenPool implementation
25+
struct TokenPool *TokenPool::Get(void) {
26+
return NULL;
27+
}

0 commit comments

Comments
 (0)