blob: 5c89d8e32336c80b2174982aa002c579fac95feb [file] [edit]
// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NINJA_SUBPROCESS_H_
#define NINJA_SUBPROCESS_H_
#include <memory>
#include <queue>
#include <string>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#else
#include <signal.h>
#endif
#include "async_loop.h"
#include "exit_status.h"
#include "interrupt_handling.h"
#include "process_tree.h"
#include "scoped_handle.h"
class EnvironmentBlock;
struct SubprocessSet;
/// Subprocess wraps a single async subprocess. It is entirely
/// passive: it expects the caller to notify it when its fds are ready
/// for reading, as well as call Finish() to reap the child once done()
/// is true.
struct Subprocess {
explicit Subprocess(SubprocessSet& subprocess_set, bool use_console);
~Subprocess();
/// Returns ExitSuccess on successful process exit, ExitInterrupted if
/// the process was interrupted, ExitFailure if it otherwise failed.
/// Should only be called on an instance where Done() returns true.
ExitStatus Finish();
/// Return true if the subprocess has completed. Only used for testing.
bool Done() const;
/// Retrieve buffered output (combined stdout/stderr) for a given
/// subprocess. Will be empty for subprocesses belonging to the 'console'
/// pool. Only call this when Done() returns true, or after a call to
/// Finish().
const std::string& GetOutput() const;
#ifndef _WIN32
/// Return true if the subprocess was force-finished by the SubprocessSet.
/// This happens when the corresponding process exited early but its pipe
/// remained open for more than 30 seconds (by default). This corresponds
/// to a process that launches a background or daemonized child that inherits
/// the pipe descriptor and keeps it open without writing to it.
bool WasForcedFinished() const { return force_finished_; }
#endif // _WIN32
/// How to run a given Subprocess.
///
/// Local: run as a separate process in the same group and session.
/// Group: run as a separate process group in the same session.
/// Session: run as a separate process group and session.
enum class RunMode { Local = 0, Group = 1, Session = 2 };
/// What type of output is being used by a subprocess
///
/// Direct:
/// The subprocess sends its outputs directly to the parent Ninja process
/// output streams. Ninja does not see or buffers it. This matches the
/// upstream behavior for actions in the 'console' pool.
///
/// PipeBuffer:
/// The subprocess sends its outputs to a pipe, Ninja buffers the content
/// but will only print it when the command has completed. This is the
/// default behavior for actions not in the console pool.
///
/// PipeProxy:
/// The subprocess sends its outputs to a pipe, Ninja buffers the content
/// but also sends it as soon as possible to its own output. On completion
/// the output will not be re-printed. This is the Fuchsia-specific behavior
/// for commands on the 'console' pool, and allows the output to be
/// written to the --error_logging_output file.
///
/// PseudoTerminalProxy:
/// Same as PipeProxy, but a pty instead of a pipe is allocated. This mode
/// is Fuchsia-specific and is enabled when Ninja's outputs are interactive
/// terminals themselves.
///
enum class OutputMode {
Direct,
PipeBuffer,
PipeProxy,
PseudoTerminalProxy,
};
private:
bool Start(const std::string& command,
const std::string* description = nullptr);
void Stop();
void OnPipeReady();
std::string buf_;
#ifdef _WIN32
/// Set up pipe_ as the parent-side pipe of the subprocess; return the
/// other end of the pipe, usable in the child process.
ScopedHandle SetupPipe();
/// Handle to the child process.
ScopedHandle child_;
std::unique_ptr<AsyncHandle> async_pipe_;
bool is_reading_ = false;
#else
/// In POSIX platforms it is necessary to use waitpid(WNOHANG) to know whether
/// a certain subprocess has finished. This is done for terminal subprocesses.
/// However, this also causes the subprocess to be reaped before Finish() is
/// called, so we need to store the ExitStatus so that a later Finish()
/// invocation can return it.
ExitStatus exit_status_;
/// Call waitpid() on the subprocess with the provided options and update the
/// pid_ and exit_status_ fields.
/// Return a boolean indicating whether the subprocess has indeed terminated.
bool TryFinish(int waitpid_options);
/// PID of the subprocess.
pid_t pid_ = -1;
/// Whether the subprocess has been reaped. Even if this is true, the
/// Subprocess instance can still be active if it's waiting for bytes from
/// the pipe, so the caller shouldn't consider the process as "done" yet.
bool reaped_ = false;
/// A flag indicating that the subprocess was force-finished by the
/// SubprocessSet. This happens when a process exits early by its pipe is
/// still open for more than 30 seconds (by default).
bool force_finished_ = false;
/// In non-console mode, this is the read-side of a pipe that was created
/// specifically for this subprocess. The write-side of the pipe is given to
/// the subprocess as combined stdout and stderr.
/// In console mode no pipe is created: async_fd_ is null, and process
/// termination is detected by detecting SIGCHLD signals and using
/// waitpid(WNOHANG).
std::unique_ptr<AsyncHandle> async_fd_;
/// A timer that will be created when the process is reaped but the pipe is
/// still open. After 30 seconds without any bytes coming from the pipe,
/// the timer will fire and force-finish the sub-process.
std::unique_ptr<AsyncTimer> async_timer_;
#endif
SubprocessSet& subprocess_set_;
std::string description_;
RunMode run_mode_ = RunMode::Local;
OutputMode output_mode_ = OutputMode::PipeBuffer;
char read_buf_[4 << 10];
friend struct SubprocessSet;
};
/// SubprocessSet runs a ppoll/pselect() loop around a set of Subprocesses.
/// DoWork() waits for any state change in subprocesses; finished_
/// is a queue of subprocesses as they finish.
struct SubprocessSet {
SubprocessSet();
SubprocessSet(AsyncLoop& async_loop,
const EnvironmentBlock* environment = nullptr);
~SubprocessSet();
/// The result of DoWork(), in increasing order of priority.
/// If multiple events happen at the same time, the larger
/// value will be reported.
enum class WorkResult {
NoWork,
JobserverTokenAvailable,
SubprocFinished,
Interrupted,
};
/// Start a new subprocess, and return a pointer to the corresponding
/// instance, which is still owned by the SubprocessSet. \arg command
/// is the command to launch, \arg description is an optional description
/// for the command, and \arg use_console should be true for commands
/// that are launched in the console pool.
Subprocess* Add(const std::string& command,
const std::string* description = nullptr,
bool use_console = false);
/// Wait until one of the events described by WorkResult happen.
/// This may return WorkResult::NoWork if there are no subprocesses
/// to wait for.
WorkResult DoWork();
/// Return next finished process, or null if there is none.
/// The returned instance must be destroyed before this SubprocessSet.
std::unique_ptr<Subprocess> NextFinished();
/// Return true if the next NextFinished() call would result a non-null value.
bool HasFinished() const { return !finished_.empty(); }
/// Remove all remaining processes forcibly.
void Clear();
/// Called when a subprocess completes.
void OnProcessCompletion(Subprocess* subprocess);
std::vector<std::unique_ptr<Subprocess>> running_;
std::queue<std::unique_ptr<Subprocess>> finished_;
AsyncLoop& async_loop_;
// Always catch interrupts in this AsyncLoop.
AsyncLoop::ScopedInterruptCatcher interrupt_catcher_;
const EnvironmentBlock* environment_ = nullptr;
#ifndef _WIN32
void CheckForTerminatedProcesses();
void SetJobserverFD(int fd);
std::unique_ptr<AsyncSigChild> async_sigchild_;
std::unique_ptr<AsyncFdReadyFlags> async_jobserver_ready_;
/// If a subprocess pipe remains open for this long after the subprocess
/// has exited, then we force-finish the subprocess. This can be overridden
/// by unit-tests to avoid waiting 30 seconds when they run.
int64_t pipe_timeout_ms_ = 30 * 1000;
#endif // !_WIN32
private:
/// Report currently running subprocesses and their process trees to stderr.
void ReportRunningSubprocesses(const ProcessTree::SystemSnapshot& snapshot);
};
#endif // NINJA_SUBPROCESS_H_