| // 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_ |