blob: d54e3f7a0b9bef95e2fe226883dc2944ad32c8b4 [file] [log] [blame]
// 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_