Skip to content

Commit 0e5b8ca

Browse files
author
Stefan Becker
committed
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 680efe4 commit 0e5b8ca

File tree

5 files changed

+289
-4
lines changed

5 files changed

+289
-4
lines changed

configure.py

+2
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,7 @@ def has_re2c():
494494
objs += cxx(name)
495495
if platform.is_windows():
496496
for name in ['subprocess-win32',
497+
'tokenpool-none',
497498
'includes_normalize-win32',
498499
'msvc_helper-win32',
499500
'msvc_helper_main-win32']:
@@ -503,6 +504,7 @@ def has_re2c():
503504
objs += cc('getopt')
504505
else:
505506
objs += cxx('subprocess-posix')
507+
objs += cxx('tokenpool-gnu-make')
506508
if platform.is_aix():
507509
objs += cc('getopt')
508510
if platform.is_msvc():

src/build.cc

+23-4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "graph.h"
3434
#include "state.h"
3535
#include "subprocess.h"
36+
#include "tokenpool.h"
3637
#include "util.h"
3738

3839
namespace {
@@ -502,8 +503,8 @@ void Plan::Dump() {
502503
}
503504

504505
struct RealCommandRunner : public CommandRunner {
505-
explicit RealCommandRunner(const BuildConfig& config) : config_(config) {}
506-
virtual ~RealCommandRunner() {}
506+
explicit RealCommandRunner(const BuildConfig& config);
507+
virtual ~RealCommandRunner();
507508
virtual bool CanRunMore();
508509
virtual bool StartCommand(Edge* edge);
509510
virtual bool WaitForCommand(Result* result);
@@ -512,9 +513,18 @@ struct RealCommandRunner : public CommandRunner {
512513

513514
const BuildConfig& config_;
514515
SubprocessSet subprocs_;
516+
TokenPool *tokens_;
515517
map<Subprocess*, Edge*> subproc_to_edge_;
516518
};
517519

520+
RealCommandRunner::RealCommandRunner(const BuildConfig& config) : config_(config) {
521+
tokens_ = TokenPool::Get();
522+
}
523+
524+
RealCommandRunner::~RealCommandRunner() {
525+
delete tokens_;
526+
}
527+
518528
vector<Edge*> RealCommandRunner::GetActiveEdges() {
519529
vector<Edge*> edges;
520530
for (map<Subprocess*, Edge*>::iterator e = subproc_to_edge_.begin();
@@ -525,21 +535,27 @@ vector<Edge*> RealCommandRunner::GetActiveEdges() {
525535

526536
void RealCommandRunner::Abort() {
527537
subprocs_.Clear();
538+
if (tokens_)
539+
tokens_->Clear();
528540
}
529541

530542
bool RealCommandRunner::CanRunMore() {
531543
size_t subproc_number =
532544
subprocs_.running_.size() + subprocs_.finished_.size();
533545
return (int)subproc_number < config_.parallelism
534-
&& ((subprocs_.running_.empty() || config_.max_load_average <= 0.0f)
535-
|| GetLoadAverage() < config_.max_load_average);
546+
&& (subprocs_.running_.empty()
547+
|| ((!tokens_ || tokens_->Acquire())
548+
&& (config_.max_load_average <= 0.0f
549+
|| GetLoadAverage() < config_.max_load_average)));
536550
}
537551

538552
bool RealCommandRunner::StartCommand(Edge* edge) {
539553
string command = edge->EvaluateCommand();
540554
Subprocess* subproc = subprocs_.Add(command, edge->use_console());
541555
if (!subproc)
542556
return false;
557+
if (tokens_)
558+
tokens_->Reserve();
543559
subproc_to_edge_.insert(make_pair(subproc, edge));
544560

545561
return true;
@@ -553,6 +569,9 @@ bool RealCommandRunner::WaitForCommand(Result* result) {
553569
return false;
554570
}
555571

572+
if (tokens_)
573+
tokens_->Release();
574+
556575
result->status = subproc->Finish();
557576
result->output = subproc->GetOutput();
558577

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+
}

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+
}

src/tokenpool.h

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
// interface to token pool
16+
struct TokenPool {
17+
virtual ~TokenPool() {}
18+
19+
virtual bool Acquire() = 0;
20+
virtual void Reserve() = 0;
21+
virtual void Release() = 0;
22+
virtual void Clear() = 0;
23+
24+
// returns NULL if token pool is not available
25+
static struct TokenPool *Get(void);
26+
};

0 commit comments

Comments
 (0)