-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add GNU make jobserver style "fifo" support #2263
Changes from all commits
3e04d21
5c8dc53
a6eec5c
3d15060
1157e43
6100863
008bc6e
cf02adc
f99d747
71ad7da
05d39cb
6ec8fb0
89e4aef
0ec8c4a
8d21910
6602155
bee56b6
e615e63
66350c0
c9e21db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,7 @@ | |
#include "state.h" | ||
#include "status.h" | ||
#include "subprocess.h" | ||
#include "tokenpool.h" | ||
#include "util.h" | ||
|
||
using namespace std; | ||
|
@@ -47,8 +48,9 @@ struct DryRunCommandRunner : public CommandRunner { | |
|
||
// Overridden from CommandRunner: | ||
virtual bool CanRunMore() const; | ||
virtual bool AcquireToken(); | ||
virtual bool StartCommand(Edge* edge); | ||
virtual bool WaitForCommand(Result* result); | ||
virtual bool WaitForCommand(Result* result, bool more_ready); | ||
|
||
private: | ||
queue<Edge*> finished_; | ||
|
@@ -58,12 +60,16 @@ bool DryRunCommandRunner::CanRunMore() const { | |
return true; | ||
} | ||
|
||
bool DryRunCommandRunner::AcquireToken() { | ||
return true; | ||
} | ||
|
||
bool DryRunCommandRunner::StartCommand(Edge* edge) { | ||
finished_.push(edge); | ||
return true; | ||
} | ||
|
||
bool DryRunCommandRunner::WaitForCommand(Result* result) { | ||
bool DryRunCommandRunner::WaitForCommand(Result* result, bool more_ready) { | ||
if (finished_.empty()) | ||
return false; | ||
|
||
|
@@ -149,7 +155,7 @@ void Plan::EdgeWanted(const Edge* edge) { | |
} | ||
|
||
Edge* Plan::FindWork() { | ||
if (ready_.empty()) | ||
if (!more_ready()) | ||
return NULL; | ||
EdgeSet::iterator e = ready_.begin(); | ||
Edge* edge = *e; | ||
|
@@ -448,19 +454,46 @@ void Plan::Dump() const { | |
} | ||
|
||
struct RealCommandRunner : public CommandRunner { | ||
explicit RealCommandRunner(const BuildConfig& config) : config_(config) {} | ||
virtual ~RealCommandRunner() {} | ||
explicit RealCommandRunner(const BuildConfig& config); | ||
virtual ~RealCommandRunner(); | ||
virtual bool CanRunMore() const; | ||
virtual bool AcquireToken(); | ||
virtual bool StartCommand(Edge* edge); | ||
virtual bool WaitForCommand(Result* result); | ||
virtual bool WaitForCommand(Result* result, bool more_ready); | ||
virtual vector<Edge*> GetActiveEdges(); | ||
virtual void Abort(); | ||
|
||
const BuildConfig& config_; | ||
// copy of config_.max_load_average; can be modified by TokenPool setup | ||
double max_load_average_; | ||
SubprocessSet subprocs_; | ||
TokenPool* tokens_; | ||
map<const Subprocess*, Edge*> subproc_to_edge_; | ||
}; | ||
|
||
RealCommandRunner::RealCommandRunner(const BuildConfig& config) : config_(config) { | ||
max_load_average_ = config.max_load_average; | ||
if ((tokens_ = TokenPool::Get()) != NULL) { | ||
bool setup_ok = config_.tokenpool_master ? | ||
tokens_->SetupMaster(config_.verbosity == BuildConfig::VERBOSE, | ||
config_.parallelism, | ||
max_load_average_, | ||
config_.tokenpool_master_style) : | ||
tokens_->SetupClient(config_.parallelism_from_cmdline, | ||
config_.verbosity == BuildConfig::VERBOSE, | ||
max_load_average_); | ||
|
||
if (!setup_ok) { | ||
delete tokens_; | ||
tokens_ = NULL; | ||
} | ||
} | ||
} | ||
|
||
RealCommandRunner::~RealCommandRunner() { | ||
delete tokens_; | ||
} | ||
|
||
vector<Edge*> RealCommandRunner::GetActiveEdges() { | ||
vector<Edge*> edges; | ||
for (map<const Subprocess*, Edge*>::iterator e = subproc_to_edge_.begin(); | ||
|
@@ -471,34 +504,57 @@ vector<Edge*> RealCommandRunner::GetActiveEdges() { | |
|
||
void RealCommandRunner::Abort() { | ||
subprocs_.Clear(); | ||
if (tokens_) | ||
tokens_->Clear(); | ||
} | ||
|
||
bool RealCommandRunner::CanRunMore() const { | ||
size_t subproc_number = | ||
subprocs_.running_.size() + subprocs_.finished_.size(); | ||
return (int)subproc_number < config_.parallelism | ||
&& ((subprocs_.running_.empty() || config_.max_load_average <= 0.0f) | ||
|| GetLoadAverage() < config_.max_load_average); | ||
bool parallelism_limit_not_reached = | ||
tokens_ || // ignore config_.parallelism | ||
((int) (subprocs_.running_.size() + | ||
subprocs_.finished_.size()) < config_.parallelism); | ||
return parallelism_limit_not_reached | ||
&& (subprocs_.running_.empty() || | ||
(max_load_average_ <= 0.0f || | ||
GetLoadAverage() < max_load_average_)); | ||
} | ||
|
||
bool RealCommandRunner::AcquireToken() { | ||
return (!tokens_ || tokens_->Acquire()); | ||
} | ||
|
||
bool RealCommandRunner::StartCommand(Edge* edge) { | ||
string command = edge->EvaluateCommand(); | ||
Subprocess* subproc = subprocs_.Add(command, edge->use_console()); | ||
if (!subproc) | ||
return false; | ||
if (tokens_) | ||
tokens_->Reserve(); | ||
subproc_to_edge_.insert(make_pair(subproc, edge)); | ||
|
||
return true; | ||
} | ||
|
||
bool RealCommandRunner::WaitForCommand(Result* result) { | ||
bool RealCommandRunner::WaitForCommand(Result* result, bool more_ready) { | ||
Subprocess* subproc; | ||
while ((subproc = subprocs_.NextFinished()) == NULL) { | ||
bool interrupted = subprocs_.DoWork(); | ||
subprocs_.ResetTokenAvailable(); | ||
while (((subproc = subprocs_.NextFinished()) == NULL) && | ||
!subprocs_.IsTokenAvailable()) { | ||
bool interrupted = subprocs_.DoWork(more_ready ? tokens_ : NULL); | ||
if (interrupted) | ||
return false; | ||
} | ||
|
||
// token became available | ||
if (subproc == NULL) { | ||
result->status = ExitTokenAvailable; | ||
return true; | ||
} | ||
|
||
// command completed | ||
if (tokens_) | ||
tokens_->Release(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has to be done after Unrelated - but this is also a spot for optimization, as Ninja will get stalled here if a long-running process detaches from terminal early. |
||
|
||
result->status = subproc->Finish(); | ||
result->output = subproc->GetOutput(); | ||
|
||
|
@@ -628,45 +684,54 @@ bool Builder::Build(string* err) { | |
// command runner. | ||
// Second, we attempt to wait for / reap the next finished command. | ||
while (plan_.more_to_do()) { | ||
// See if we can start any more commands. | ||
if (failures_allowed && command_runner_->CanRunMore()) { | ||
if (Edge* edge = plan_.FindWork()) { | ||
if (edge->GetBindingBool("generator")) { | ||
// See if we can start any more commands... | ||
bool can_run_more = | ||
failures_allowed && | ||
plan_.more_ready() && | ||
command_runner_->CanRunMore(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This If you have an |
||
|
||
// ... but we also need a token to do that. | ||
if (can_run_more && command_runner_->AcquireToken()) { | ||
Edge* edge = plan_.FindWork(); | ||
if (edge->GetBindingBool("generator")) { | ||
scan_.build_log()->Close(); | ||
} | ||
|
||
if (!StartEdge(edge, err)) { | ||
if (!StartEdge(edge, err)) { | ||
Cleanup(); | ||
status_->BuildFinished(); | ||
return false; | ||
} | ||
|
||
if (edge->is_phony()) { | ||
if (!plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, err)) { | ||
Cleanup(); | ||
status_->BuildFinished(); | ||
return false; | ||
} | ||
|
||
if (edge->is_phony()) { | ||
if (!plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, err)) { | ||
Cleanup(); | ||
status_->BuildFinished(); | ||
return false; | ||
} | ||
} else { | ||
++pending_commands; | ||
} | ||
|
||
// We made some progress; go back to the main loop. | ||
continue; | ||
} else { | ||
++pending_commands; | ||
} | ||
|
||
// We made some progress; go back to the main loop. | ||
continue; | ||
} | ||
|
||
// See if we can reap any finished commands. | ||
if (pending_commands) { | ||
CommandRunner::Result result; | ||
if (!command_runner_->WaitForCommand(&result) || | ||
if (!command_runner_->WaitForCommand(&result, can_run_more) || | ||
result.status == ExitInterrupted) { | ||
Cleanup(); | ||
status_->BuildFinished(); | ||
*err = "interrupted by user"; | ||
return false; | ||
} | ||
|
||
// We might be able to start another command; start the main loop over. | ||
if (result.status == ExitTokenAvailable) | ||
continue; | ||
|
||
--pending_commands; | ||
if (!FinishCommand(&result, err)) { | ||
Cleanup(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can't be right...subprocs_.NextFinished()
in that loop above has popped any number of completed processes.Buttokens_->Release()
is only being called conditionally and only once at most.So this is leaving tokens incorrectly marked as "in-use". You would have needed to release the token together withfinished_.push(*i);
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... no, that loop above is looping while it does not pop anything. And you are also only quitting if no process had finished, and you were certain to have gotten a token instead.
Okay, this is working then, just really hard to follow and the naming got seriously confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might have wanted to put it in the
else
to the aboveif
, and put a comment on the loop explaining the non-trivial exit condition.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also the methods used here are definitely overdue for renaming, their names don't reflect what they do.