Skip to content

Commit 7aa3333

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 63a8584 commit 7aa3333

File tree

5 files changed

+276
-4
lines changed

5 files changed

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

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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 bool interrupted_;
53+
static void SetInterruptedFlag(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+
bool GNUmakeTokenPool::interrupted_;
83+
84+
void GNUmakeTokenPool::SetInterruptedFlag(int signum) {
85+
interrupted_ = true;
86+
}
87+
88+
bool GNUmakeTokenPool::SetAlarmHandler() {
89+
struct sigaction act;
90+
memset(&act, 0, sizeof(act));
91+
act.sa_handler = SetInterruptedFlag;
92+
if (sigaction(SIGALRM, &act, &old_act_) < 0) {
93+
perror("sigaction:");
94+
return(false);
95+
} else {
96+
restore_ = true;
97+
return(true);
98+
}
99+
}
100+
101+
bool GNUmakeTokenPool::Setup() {
102+
const char *value = getenv("MAKEFLAGS");
103+
if (value) {
104+
// GNU make <= 4.1
105+
const char *jobserver = strstr(value, "--jobserver-fds=");
106+
// GNU make => 4.2
107+
if (!jobserver)
108+
jobserver = strstr(value, "--jobserver-auth=");
109+
if (jobserver) {
110+
int rfd = -1;
111+
int wfd = -1;
112+
if ((sscanf(jobserver, "%*[^=]=%d,%d", &rfd, &wfd) == 2) &&
113+
CheckFd(rfd) &&
114+
CheckFd(wfd) &&
115+
SetAlarmHandler()) {
116+
printf("ninja: using GNU make jobserver.\n");
117+
rfd_ = rfd;
118+
wfd_ = wfd;
119+
return true;
120+
}
121+
}
122+
}
123+
124+
return false;
125+
}
126+
127+
bool GNUmakeTokenPool::Acquire() {
128+
if (available_ > 0)
129+
return true;
130+
131+
#ifdef USE_PPOLL
132+
pollfd pollfds[] = {{rfd_, POLLIN, 0}};
133+
int ret = poll(pollfds, 1, 0);
134+
#else
135+
fd_set set;
136+
struct timeval timeout = { 0, 0 };
137+
FD_ZERO(&set);
138+
FD_SET(rfd_, &set);
139+
int ret = select(rfd_ + 1, &set, NULL, NULL, &timeout);
140+
#endif
141+
if (ret > 0) {
142+
char buf;
143+
144+
interrupted_ = false;
145+
alarm(1);
146+
int ret = read(rfd_, &buf, 1);
147+
alarm(0);
148+
149+
if (ret > 0) {
150+
available_++;
151+
return true;
152+
}
153+
154+
if (interrupted_)
155+
perror("blocked on token");
156+
}
157+
return false;
158+
}
159+
160+
void GNUmakeTokenPool::Reserve() {
161+
available_--;
162+
used_++;
163+
}
164+
165+
void GNUmakeTokenPool::Return() {
166+
const char buf = '+';
167+
while (1) {
168+
int ret = write(wfd_, &buf, 1);
169+
if (ret > 0)
170+
available_--;
171+
if ((ret != -1) || (errno != EINTR))
172+
return;
173+
// write got interrupted - retry
174+
}
175+
}
176+
177+
void GNUmakeTokenPool::Release() {
178+
available_++;
179+
used_--;
180+
if (available_ > 1)
181+
Return();
182+
}
183+
184+
void GNUmakeTokenPool::Clear() {
185+
while (used_ > 0)
186+
Release();
187+
while (available_ > 1)
188+
Return();
189+
}
190+
191+
struct TokenPool *TokenPool::Get(void) {
192+
GNUmakeTokenPool *tokenpool = new GNUmakeTokenPool;
193+
if (tokenpool->Setup())
194+
return tokenpool;
195+
else
196+
delete tokenpool;
197+
return NULL;
198+
}

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)