Optionally separate Tasks` stderr from stdout.

Fixes a serious problem where spurious output from xcrun breaks
swift's discovery of libarclite.

<rdar://problem/28573949>
This commit is contained in:
Sean Callanan
2017-01-11 11:11:23 -08:00
parent 240ab32961
commit 399709ccb8
7 changed files with 122 additions and 42 deletions

View File

@@ -72,12 +72,15 @@ public:
/// \param ReturnCode the return code of the task which finished execution.
/// \param Output the output from the task which finished execution,
/// if available. (This may not be available on all platforms.)
/// \param Errors the errors from the task which finished execution, if
/// available and SeparateErrors was true. (This may not be available on all
/// platforms.)
/// \param Context the context which was passed when the task was added
///
/// \returns true if further execution of tasks should stop,
/// false if execution should continue
typedef std::function<TaskFinishedResponse(ProcessId Pid, int ReturnCode,
StringRef Output, void *Context)>
StringRef Output, StringRef Errors, void *Context)>
TaskFinishedCallback;
/// \brief A callback which will be executed if a task exited abnormally due
@@ -88,12 +91,15 @@ public:
/// no reason could be deduced, this may be empty.
/// \param Output the output from the task which exited abnormally, if
/// available. (This may not be available on all platforms.)
/// \param Errors the errors from the task which exited abnormally, if
/// available and SeparateErrors was true. (This may not be available on all
/// platforms.)
/// \param Context the context which was passed when the task was added
///
/// \returns a TaskFinishedResponse indicating whether or not execution
/// should proceed
typedef std::function<TaskFinishedResponse(ProcessId Pid, StringRef ErrorMsg,
StringRef Output, void *Context)>
StringRef Output, StringRef Errors, void *Context)>
TaskSignalledCallback;
#pragma clang diagnostic pop
@@ -120,9 +126,10 @@ public:
/// \param Env the environment which should be used for the task;
/// must be null-terminated. If empty, inherits the parent's environment.
/// \param Context an optional context which will be associated with the task
/// \param SeparateErrors Controls whether error output is reported separately
virtual void addTask(const char *ExecPath, ArrayRef<const char *> Args,
ArrayRef<const char *> Env = llvm::None,
void *Context = nullptr);
void *Context = nullptr, bool SeparateErrors = false);
/// \brief Synchronously executes the tasks in the TaskQueue.
///
@@ -153,10 +160,13 @@ class DummyTaskQueue : public TaskQueue {
ArrayRef<const char *> Args;
ArrayRef<const char *> Env;
void *Context;
bool SeparateErrors;
DummyTask(const char *ExecPath, ArrayRef<const char *> Args,
ArrayRef<const char *> Env = llvm::None, void *Context = nullptr)
: ExecPath(ExecPath), Args(Args), Env(Env), Context(Context) {}
ArrayRef<const char *> Env = llvm::None, void *Context = nullptr,
bool SeparateErrors = false)
: ExecPath(ExecPath), Args(Args), Env(Env), Context(Context),
SeparateErrors(SeparateErrors) {}
};
std::queue<std::unique_ptr<DummyTask>> QueuedTasks;
@@ -168,7 +178,7 @@ public:
virtual void addTask(const char *ExecPath, ArrayRef<const char *> Args,
ArrayRef<const char *> Env = llvm::None,
void *Context = nullptr);
void *Context = nullptr, bool SeparateErrors = false);
virtual bool
execute(TaskBeganCallback Began = TaskBeganCallback(),

View File

@@ -112,8 +112,8 @@ bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished,
// Wait() returning a return code of -2 indicates the process received
// a signal during execution.
if (Signalled) {
TaskFinishedResponse Response = Signalled(PI.Pid, ErrMsg, StringRef(),
T->Context);
TaskFinishedResponse Response =
Signalled(PI.Pid, ErrMsg, StringRef(), StringRef(), T->Context);
ContinueExecution = Response != TaskFinishedResponse::StopExecution;
} else {
// If we don't have a Signalled callback, unconditionally stop.
@@ -124,7 +124,7 @@ bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished,
// finished.
if (Finished) {
TaskFinishedResponse Response = Finished(PI.Pid, PI.ReturnCode,
StringRef(), T->Context);
StringRef(), StringRef(), T->Context);
ContinueExecution = Response != TaskFinishedResponse::StopExecution;
} else if (PI.ReturnCode != 0) {
ContinueExecution = false;

View File

@@ -42,9 +42,10 @@ DummyTaskQueue::DummyTaskQueue(unsigned NumberOfParallelTasks)
DummyTaskQueue::~DummyTaskQueue() = default;
void DummyTaskQueue::addTask(const char *ExecPath, ArrayRef<const char *> Args,
ArrayRef<const char *> Env, void *Context) {
QueuedTasks.emplace(
std::unique_ptr<DummyTask>(new DummyTask(ExecPath, Args, Env, Context)));
ArrayRef<const char *> Env, void *Context,
bool SeparateErrors) {
QueuedTasks.emplace(std::unique_ptr<DummyTask>(
new DummyTask(ExecPath, Args, Env, Context, SeparateErrors)));
}
bool DummyTaskQueue::execute(TaskQueue::TaskBeganCallback Began,
@@ -80,7 +81,9 @@ bool DummyTaskQueue::execute(TaskQueue::TaskBeganCallback Began,
if (Finished) {
std::string Output = "Output placeholder\n";
if (Finished(P.first, 0, Output, P.second->Context) ==
std::string Errors =
P.second->SeparateErrors ? "Error placeholder\n" : "";
if (Finished(P.first, 0, Output, Errors, P.second->Context) ==
TaskFinishedResponse::StopExecution)
SubtaskFailed = true;
}

View File

@@ -58,12 +58,18 @@ class Task {
/// Context which should be associated with this task.
void *Context;
/// True if the errors of the Task should be stored in Errors instead of Output.
bool SeparateErrors;
/// The pid of this Task when executing.
pid_t Pid;
/// A pipe for reading output from the child process.
int Pipe;
/// A pipe for reading errors from the child prcess, if SeparateErrors is true.
int ErrorPipe;
/// The current state of the Task.
enum {
Preparing,
@@ -74,11 +80,16 @@ class Task {
/// Once the Task has finished, this contains the buffered output of the Task.
std::string Output;
/// Once the Task has finished, if SeparateErrors is true, this contains the errors
/// from the Task.
std::string Errors;
public:
Task(const char *ExecPath, ArrayRef<const char *> Args,
ArrayRef<const char *> Env, void *Context)
ArrayRef<const char *> Env, void *Context, bool SeparateErrors)
: ExecPath(ExecPath), Args(Args), Env(Env), Context(Context),
Pid(-1), Pipe(-1), State(Preparing) {
SeparateErrors(SeparateErrors), Pid(-1), Pipe(-1), ErrorPipe(-1),
State(Preparing) {
assert((Env.empty() || Env.back() == nullptr) &&
"Env must either be empty or null-terminated!");
}
@@ -86,17 +97,19 @@ public:
const char *getExecPath() const { return ExecPath; }
ArrayRef<const char *> getArgs() const { return Args; }
StringRef getOutput() const { return Output; }
StringRef getErrors() const { return Errors; }
void *getContext() const { return Context; }
pid_t getPid() const { return Pid; }
int getPipe() const { return Pipe; }
int getErrorPipe() const { return ErrorPipe; }
/// \brief Begins execution of this Task.
/// \returns true on error, false on success
bool execute();
/// \brief Reads data from the pipe, if any is available.
/// \brief Reads data from the pipes, if any is available.
/// \returns true on error, false on success
bool readFromPipe();
bool readFromPipes();
/// \brief Performs any post-execution work for this Task, such as reading
/// piped output and closing the pipe.
@@ -121,6 +134,12 @@ bool Task::execute() {
pipe(FullPipe);
Pipe = FullPipe[0];
int FullErrorPipe[2];
if (SeparateErrors) {
pipe(FullErrorPipe);
ErrorPipe = FullErrorPipe[0];
}
// Get the environment to pass down to the subtask.
const char *const *envp = Env.empty() ? nullptr : Env.data();
if (!envp) {
@@ -138,8 +157,19 @@ bool Task::execute() {
posix_spawn_file_actions_init(&FileActions);
posix_spawn_file_actions_adddup2(&FileActions, FullPipe[1], STDOUT_FILENO);
posix_spawn_file_actions_adddup2(&FileActions, STDOUT_FILENO, STDERR_FILENO);
if (SeparateErrors) {
posix_spawn_file_actions_adddup2(&FileActions, FullErrorPipe[1],
STDERR_FILENO);
} else {
posix_spawn_file_actions_adddup2(&FileActions, STDOUT_FILENO,
STDERR_FILENO);
}
posix_spawn_file_actions_addclose(&FileActions, FullPipe[0]);
if (SeparateErrors) {
posix_spawn_file_actions_addclose(&FileActions, FullErrorPipe[0]);
}
// Spawn the subtask.
int spawnErr = posix_spawn(&Pid, ExecPath, &FileActions, nullptr,
@@ -148,9 +178,15 @@ bool Task::execute() {
posix_spawn_file_actions_destroy(&FileActions);
close(FullPipe[1]);
if (SeparateErrors) {
close(FullErrorPipe[1]);
}
if (spawnErr != 0 || Pid == 0) {
close(FullPipe[0]);
if (SeparateErrors) {
close(FullErrorPipe[0]);
}
State = Finished;
return true;
}
@@ -159,6 +195,9 @@ bool Task::execute() {
switch (Pid) {
case -1: {
close(FullPipe[0]);
if (SeparateErrors) {
close(FullErrorPipe[0]);
}
State = Finished;
Pid = 0;
break;
@@ -166,8 +205,15 @@ bool Task::execute() {
case 0: {
// Child process: Execute the program.
dup2(FullPipe[1], STDOUT_FILENO);
if (SeparateErrors) {
dup2(FullErrorPipe[1], STDERR_FILENO);
} else {
dup2(STDOUT_FILENO, STDERR_FILENO);
}
close(FullPipe[0]);
if (SeparateErrors) {
close(FullErrorPipe[0]);
}
execve(ExecPath, const_cast<char **>(argvp), const_cast<char **>(envp));
// If the execve() failed, we should exit. Follow Unix protocol and
@@ -184,6 +230,9 @@ bool Task::execute() {
}
close(FullPipe[1]);
if (SeparateErrors) {
close(FullErrorPipe[1]);
}
if (Pid == 0)
return true;
@@ -192,7 +241,7 @@ bool Task::execute() {
return false;
}
bool Task::readFromPipe() {
static bool readFromAPipe(int Pipe, std::string &Output) {
char outputBuffer[1024];
ssize_t readBytes = 0;
while ((readBytes = read(Pipe, outputBuffer, sizeof(outputBuffer))) != 0) {
@@ -209,6 +258,14 @@ bool Task::readFromPipe() {
return false;
}
bool Task::readFromPipes() {
bool Ret = readFromAPipe(Pipe, Output);
if (SeparateErrors) {
Ret |= readFromAPipe(ErrorPipe, Errors);
}
return Ret;
}
void Task::finishExecution() {
assert(State == Executing &&
"This Task must be executing to finish execution!");
@@ -216,9 +273,12 @@ void Task::finishExecution() {
State = Finished;
// Read the output of the command, so we can use it later.
readFromPipe();
readFromPipes();
close(Pipe);
if (SeparateErrors) {
close(ErrorPipe);
}
}
bool TaskQueue::supportsBufferingOutput() {
@@ -239,8 +299,10 @@ unsigned TaskQueue::getNumberOfParallelTasks() const {
}
void TaskQueue::addTask(const char *ExecPath, ArrayRef<const char *> Args,
ArrayRef<const char *> Env, void *Context) {
std::unique_ptr<Task> T(new Task(ExecPath, Args, Env, Context));
ArrayRef<const char *> Env, void *Context,
bool SeparateErrors) {
std::unique_ptr<Task> T(
new Task(ExecPath, Args, Env, Context, SeparateErrors));
QueuedTasks.push(std::move(T));
}
@@ -279,6 +341,8 @@ bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished,
}
PollFds.push_back({ T->getPipe(), POLLIN | POLLPRI | POLLHUP, 0 });
// We should also poll T->getErrorPipe(), but this intrroduces timing
// issues with shutting down the task after reading getPipe().
ExecutingTasks[Pid] = std::move(T);
}
@@ -310,7 +374,7 @@ bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished,
Task &T = *iter->second;
if (fd.revents & POLLIN || fd.revents & POLLPRI) {
// There's data available to read.
T.readFromPipe();
T.readFromPipes();
}
if (fd.revents & POLLHUP || fd.revents & POLLERR) {
@@ -339,7 +403,7 @@ bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished,
// If we have a TaskFinishedCallback, only set SubtaskFailed to
// true if the callback returns StopExecution.
SubtaskFailed = Finished(T.getPid(), Result, T.getOutput(),
T.getContext()) ==
T.getErrors(), T.getContext()) ==
TaskFinishedResponse::StopExecution;
} else if (Result != 0) {
// Since we don't have a TaskFinishedCallback, treat a subtask
@@ -353,8 +417,8 @@ bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished,
StringRef ErrorMsg = strsignal(Signal);
if (Signalled) {
TaskFinishedResponse Response = Signalled(T.getPid(), ErrorMsg,
T.getOutput(),
TaskFinishedResponse Response =
Signalled(T.getPid(), ErrorMsg, T.getOutput(), T.getErrors(),
T.getContext());
if (Response == TaskFinishedResponse::StopExecution)
// If we have a TaskCrashedCallback, only set SubtaskFailed to

View File

@@ -482,6 +482,7 @@ int Compilation::performJobsImpl() {
// it should also schedule any additional commands which we now know need
// to run.
auto taskFinished = [&](ProcessId Pid, int ReturnCode, StringRef Output,
StringRef Errors,
void *Context) -> TaskFinishedResponse {
const Job *FinishedCmd = (const Job *)Context;
@@ -613,6 +614,7 @@ int Compilation::performJobsImpl() {
};
auto taskSignalled = [&](ProcessId Pid, StringRef ErrorMsg, StringRef Output,
StringRef Errors,
void *Context) -> TaskFinishedResponse {
const Job *SignalledCmd = (const Job *)Context;

View File

@@ -1204,6 +1204,7 @@ void Driver::buildOutputInfo(const ToolChain &TC, const DerivedArgList &Args,
[&OI](sys::ProcessId PID,
int returnCode,
StringRef output,
StringRef errors,
void *unused) -> sys::TaskFinishedResponse {
if (returnCode == 0) {
output = output.rtrim();

View File

@@ -787,11 +787,11 @@ static bool findXcodeClangPath(llvm::SmallVectorImpl<char> &path) {
if (!xcrunPath.getError()) {
const char *args[] = {"-f", "clang", nullptr};
sys::TaskQueue queue;
queue.addTask(xcrunPath->c_str(), args);
queue.execute(nullptr,
[&path](sys::ProcessId PID,
int returnCode,
StringRef output,
queue.addTask(xcrunPath->c_str(), args, /*Env=*/llvm::None,
/*Context=*/nullptr,
/*SeparateErrors=*/true);
queue.execute(nullptr, [&path](sys::ProcessId PID, int returnCode,
StringRef output, StringRef errors,
void *unused) -> sys::TaskFinishedResponse {
if (returnCode == 0) {
output = output.rtrim();