| // Copyright 2023 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_PERSISTENT_SERVICE_H_ |
| #define NINJA_PERSISTENT_SERVICE_H_ |
| |
| #include <functional> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "ipc_handle.h" |
| |
| /// PersistentService implements a "persistent server" mode for |
| /// command-line executables. The main idea is that a client process |
| /// uses an anonymous pipe (on Win32) or a Unix domain socket (on Posix) |
| /// to connect to a server process, then ask for work to be done by |
| /// the server on its behalf. |
| /// |
| /// If a server is not available, the client process will automatically launch |
| /// one, as a detached background process. Supports both Posix and Windows. |
| /// |
| /// After creating an instance, usage on the client side is: |
| /// |
| /// 1) Create a PersistentService::Config object describing how to launch |
| /// the server, if needed. |
| /// |
| /// 2) Call CreateClient() which will give an IpcHandle to communicate with |
| /// the server from the current process. |
| /// |
| /// |
| /// While usage on the server is: |
| /// |
| /// 1) Call BindService() to immediately bind the service pipe/socket. |
| /// This is optional, but is useful when the server needs to perform |
| /// a long setup operation before serving client requests. |
| /// |
| /// 2) Call RunServerThenExit() to enter an infinite loop to serve |
| /// client queries. This takes two callable objects that will be invoked |
| /// on each query. |
| /// |
| |
| class PersistentService { |
| public: |
| /// Convenience struct to hold the configuration used to spawn |
| /// a new server. This includes a command line, optional working directory, |
| /// and other options. |
| struct Config { |
| /// Constructor provides the command used to spawn the new server process. |
| explicit Config(const std::vector<std::string>& server_command) |
| : command(server_command) {} |
| |
| /// Set the working directory to be used when running the server process. |
| /// If empty (the default), not change will be applied. |
| Config& SetWorkingDir(const std::string& dir) { |
| working_dir = dir; |
| return *this; |
| } |
| |
| /// By default, the server process redirects its stdin/stdout/stderr |
| /// to /dev/null. Call this method to specify the path of a file where |
| /// server logs will be written to instead. |
| Config& SetLogFile(const std::string& log_file_path) { |
| log_file = log_file_path; |
| return *this; |
| } |
| |
| /// Add an environment variable definition that will be set before |
| /// spawning a server process. |
| Config& AddEnvVariable(const char* name, const char* value) { |
| env_vars.push_back(std::make_pair(name, value)); |
| return *this; |
| } |
| |
| /// Set a string describing version information that will be matched |
| /// against each new client connection. A mistmatch will be reported |
| /// then the server will stop automatically. |
| Config& SetVersionInfo(const std::string& info) { |
| version_info = info; |
| return *this; |
| } |
| |
| std::vector<std::string> command; |
| std::string working_dir; |
| std::string version_info; |
| std::string log_file; |
| std::vector<std::pair<std::string, std::string>> env_vars; |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////// |
| /// |
| /// C L I E N T S I D E |
| /// |
| |
| class Client { |
| public: |
| /// Create new instance. This doesn't do anything yet. |
| /// |service_name| is a unique service name, used to create a named pipe |
| /// on the host system. |
| explicit Client(const std::string& service_name); |
| |
| /// Destructor. Note that this does *not* kill any server instance |
| /// started by this client. |
| ~Client(); |
| |
| /// Returns true if a server is already running for the service. |
| bool HasServer() const; |
| |
| /// Return server PID if it exists, or -1 otherwise. |
| int GetServerPid() const; |
| |
| /// Stop a server. Return true on success (meaning there was a server, and |
| /// that it was asked to stop), and false otherwise. Note that the function |
| /// returns immediately after sending the stop command. Use |
| /// WaitForServerShutdown() to wait for its complete shutdown if needed. |
| bool StopServer(std::string* err) const; |
| |
| /// Wait until the server is properly shutdown after a StopServer() call. |
| /// Returns after 2 seconds max, return true on success, false otherwise. |
| bool WaitForServerShutdown(); |
| |
| /// Create new client connection. This will start a server automatically |
| /// if one is not already running. If an existing server has incompatible |
| /// version_info, it is also stopped and another instance restarted. |
| /// |
| /// In case of error, return an invalid IpcHandle and sets |*err|. |
| /// In case of success, the result can be used to communicate with the |
| /// server. |
| IpcHandle Connect(const Config& config, std::string* err); |
| |
| private: |
| IpcHandle RawConnect(std::string* err) const; |
| IpcHandle ConnectOrStartServer(const Config& config, |
| std::string* err) const; |
| |
| std::string service_name_; |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////// |
| /// |
| /// S E R V E R S I D E |
| /// |
| |
| class Server { |
| public: |
| /// Create new instance, |service_name| is the unique service name |
| /// and |config| is the server configuration that will be used by the |
| /// server. |
| explicit Server(const std::string& service_name); |
| |
| /// Change the connection timeout, in milliseconds, used by a server |
| /// loop when waiting for new client connections. On expiration, the server |
| /// will shut down graciously. The default value (-1) means no timeout. |
| void SetConnectionTimeoutMs(int64_t connection_timeout_ms) { |
| connection_timeout_ms_ = connection_timeout_ms; |
| } |
| |
| /// Bind to the service named pipe early. This is called implicitly by |
| /// RunServerThenExit() but can be called earlier from a server process |
| /// before expensive operations, in order to avoid races. |
| bool BindService(std::string* err); |
| |
| /// A callable that receives a version_info string from the client and |
| /// compares it to its own version. Returns an empty string when the |
| /// versions are compatible, or a failure message otherwise. |
| using VersionCheckHandler = std::function<std::string(const std::string&)>; |
| |
| /// A callable object that receives an IpcHandle corresponding to a new |
| /// client request. Returns true to indicate that the server should keep |
| /// running, or false to tell it to exit immediately. |
| using RequestHandler = std::function<bool(IpcHandle)>; |
| |
| /// Start a server in the current process, then start a loop that waits for |
| /// a client request. This function never returns (and calls exit() directly |
| /// when needed). |
| /// |
| /// For each request, the client's Config::version_info string is received |
| /// and passed to |version_check_handler| to verify that it is compatible |
| /// with the client. If this callable returns a non-empty string, the loop |
| /// exits after reporting the mismatch to the client. |
| /// |
| /// Otherwise, |request_handler| is invoked, receiving ownership of the |
| /// client connection handle. If this returns true, the function exits. |
| /// |
| /// This function should only be called when starting a server executable. |
| void RunServerThenExit(const VersionCheckHandler& version_check_handler, |
| const RequestHandler& request_handler); |
| |
| private: |
| void RunServerThenExitInternal( |
| const VersionCheckHandler& version_check_handler, |
| const RequestHandler& request_handler); |
| |
| std::string service_name_; |
| int64_t connection_timeout_ms_ = -1; |
| IpcServiceHandle service_handle_; |
| }; |
| }; |
| |
| #endif // NINJA_PERSISTENT_SERVICE_H_ |