Skip to content

Commit e6d3d5b

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 aa79fbe commit e6d3d5b

File tree

5 files changed

+226
-4
lines changed

5 files changed

+226
-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 {
@@ -495,8 +496,8 @@ void Plan::Dump() {
495496
}
496497

497498
struct RealCommandRunner : public CommandRunner {
498-
explicit RealCommandRunner(const BuildConfig& config) : config_(config) {}
499-
virtual ~RealCommandRunner() {}
499+
explicit RealCommandRunner(const BuildConfig& config);
500+
virtual ~RealCommandRunner();
500501
virtual bool CanRunMore();
501502
virtual bool StartCommand(Edge* edge);
502503
virtual bool WaitForCommand(Result* result);
@@ -505,9 +506,18 @@ struct RealCommandRunner : public CommandRunner {
505506

506507
const BuildConfig& config_;
507508
SubprocessSet subprocs_;
509+
TokenPool *tokens_;
508510
map<Subprocess*, Edge*> subproc_to_edge_;
509511
};
510512

513+
RealCommandRunner::RealCommandRunner(const BuildConfig& config) : config_(config) {
514+
tokens_ = TokenPool::Get();
515+
}
516+
517+
RealCommandRunner::~RealCommandRunner() {
518+
delete tokens_;
519+
}
520+
511521
vector<Edge*> RealCommandRunner::GetActiveEdges() {
512522
vector<Edge*> edges;
513523
for (map<Subprocess*, Edge*>::iterator e = subproc_to_edge_.begin();
@@ -518,21 +528,27 @@ vector<Edge*> RealCommandRunner::GetActiveEdges() {
518528

519529
void RealCommandRunner::Abort() {
520530
subprocs_.Clear();
531+
if (tokens_)
532+
tokens_->Clear();
521533
}
522534

523535
bool RealCommandRunner::CanRunMore() {
524536
size_t subproc_number =
525537
subprocs_.running_.size() + subprocs_.finished_.size();
526538
return (int)subproc_number < config_.parallelism
527-
&& ((subprocs_.running_.empty() || config_.max_load_average <= 0.0f)
528-
|| GetLoadAverage() < config_.max_load_average);
539+
&& (subprocs_.running_.empty()
540+
|| ((!tokens_ || tokens_->Acquire())
541+
&& (config_.max_load_average <= 0.0f
542+
|| GetLoadAverage() < config_.max_load_average)));
529543
}
530544

531545
bool RealCommandRunner::StartCommand(Edge* edge) {
532546
string command = edge->EvaluateCommand();
533547
Subprocess* subproc = subprocs_.Add(command, edge->use_console());
534548
if (!subproc)
535549
return false;
550+
if (tokens_)
551+
tokens_->Reserve();
536552
subproc_to_edge_.insert(make_pair(subproc, edge));
537553

538554
return true;
@@ -546,6 +562,9 @@ bool RealCommandRunner::WaitForCommand(Result* result) {
546562
return false;
547563
}
548564

565+
if (tokens_)
566+
tokens_->Release();
567+
549568
result->status = subproc->Finish();
550569
result->output = subproc->GetOutput();
551570

src/tokenpool-gnu-make.cc

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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+
// TokenPool implementation for GNU make jobserver
25+
// (http://make.mad-scientist.net/papers/jobserver-implementation/)
26+
struct GNUmakeTokenPool : public TokenPool {
27+
GNUmakeTokenPool();
28+
virtual ~GNUmakeTokenPool();
29+
30+
virtual bool Acquire();
31+
virtual void Reserve();
32+
virtual void Release();
33+
virtual void Clear();
34+
35+
bool Setup();
36+
37+
private:
38+
int available_;
39+
int used_;
40+
41+
#ifdef _WIN32
42+
// @TODO
43+
#else
44+
int rfd_;
45+
int wfd_;
46+
47+
bool CheckFd(int fd);
48+
#endif
49+
50+
void Return();
51+
};
52+
53+
// every instance owns an implicit token -> available_ == 1
54+
GNUmakeTokenPool::GNUmakeTokenPool() : available_(1), used_(0),
55+
rfd_(-1), wfd_(-1) {
56+
}
57+
58+
GNUmakeTokenPool::~GNUmakeTokenPool() {
59+
Clear();
60+
}
61+
62+
bool GNUmakeTokenPool::CheckFd(int fd) {
63+
if (fd < 0)
64+
return false;
65+
int ret = fcntl(fd, F_GETFD);
66+
if (ret < 0)
67+
return false;
68+
return true;
69+
}
70+
71+
bool GNUmakeTokenPool::Setup() {
72+
const char *value = getenv("MAKEFLAGS");
73+
if (value) {
74+
const char *jobserver = strstr(value, "--jobserver-fds=");
75+
if (jobserver) {
76+
int rfd = -1;
77+
int wfd = -1;
78+
if ((sscanf(jobserver, "--jobserver-fds=%d,%d", &rfd, &wfd) == 2) &&
79+
CheckFd(rfd) &&
80+
CheckFd(wfd)) {
81+
rfd_ = rfd;
82+
wfd_ = wfd;
83+
return true;
84+
}
85+
}
86+
}
87+
88+
return false;
89+
}
90+
91+
bool GNUmakeTokenPool::Acquire() {
92+
if (available_ > 0)
93+
return true;
94+
95+
#ifdef USE_PPOLL
96+
pollfd pollfds[] = {{rfd_, POLLIN, 0}};
97+
int ret = poll(pollfds, 1, 0);
98+
#else
99+
fd_set set;
100+
struct timeval timeout = { 0, 0 };
101+
FD_ZERO(&set);
102+
FD_SET(rfd_, &set);
103+
int ret = select(rfd_ + 1, &set, NULL, NULL, &timeout);
104+
#endif
105+
if (ret > 0) {
106+
char buf;
107+
int ret = read(rfd_, &buf, 1);
108+
if (ret > 0) {
109+
available_++;
110+
return true;
111+
}
112+
}
113+
return false;
114+
}
115+
116+
void GNUmakeTokenPool::Reserve() {
117+
available_--;
118+
used_++;
119+
}
120+
121+
void GNUmakeTokenPool::Return() {
122+
const char buf = '+';
123+
if (write(wfd_, &buf, 1) > 0)
124+
available_--;
125+
}
126+
127+
void GNUmakeTokenPool::Release() {
128+
available_++;
129+
used_--;
130+
if (available_ > 1)
131+
Return();
132+
}
133+
134+
void GNUmakeTokenPool::Clear() {
135+
while (used_ > 0)
136+
Release();
137+
while (available_ > 1)
138+
Return();
139+
}
140+
141+
struct TokenPool *TokenPool::Get(void) {
142+
GNUmakeTokenPool *tokenpool = new GNUmakeTokenPool;
143+
if (tokenpool->Setup())
144+
return tokenpool;
145+
else
146+
delete tokenpool;
147+
return NULL;
148+
}

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)