blob: 83ee5468ec5685dedde52a37ec79d44d5cb5ec15 [file]
// Copyright 2024 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.
#pragma once
#include <stdint.h>
#include <string>
#include <utility>
#include "scoped_handle.h"
#include "string_piece.h"
///////////////////////////////////////////////////////////////////////////
///
/// IpcService
///
/// A class used to model an inter-process service, where one process implements
/// a server and waits for connections from clients. This implementation only
/// supports serving one client at a time, and both clients and servers must
/// be on the same machine (no networking).
///
/// Usage on the server side is the following:
///
/// - Call IpcService::StartServer() to try to start a new instance. Only
/// one server per local machine is supported. On success returns a new
/// valid instance.
///
/// - Call AcceptPeer() on the service instance to wait for a client
/// connection synchronously (a timeout can be passed). On success, this
/// returns a new ScopedHandle that can be used to read from / write to
/// the peer (client).
///
/// - To accept a new client, first close the current peer handle, then
/// call AcceptPeer() again.
///
/// Usage on the server side is the following:
///
/// - Call IpcService::ConnectTo() to try to connect to an existing server
/// synchronously. This will return immediately if there is no server
/// started for the given service name on the local machine. If a server
/// exists or is busy, this will block until AcceptPeer() is called on
/// the server side. A timeout can be passed. On success, this returns
/// a ScopedHandle that can be used to write to / read from the server.
///
/// - Optionally, call IpcService::HasServer() to just check whether a
/// server was started for a given service name on the local machine.
/// Note that calling this method is racy.
///
/// The server and client can be in the same process, or separate ones
/// that can communicate with a Unix domain socket (on Posix), or a
/// named pipe (on Windows).
///
class IpcService {
public:
/// Default constructor creates invalid instance.
IpcService();
/// Destructor cleans up necessary system resources.
~IpcService();
/// Move operations are allowed.
IpcService(IpcService&&) noexcept;
IpcService& operator=(IpcService&&) noexcept;
/// Copy operations are forbidden.
IpcService(const IpcService&) = delete;
IpcService& operator=(const IpcService&) = delete;
/// Return true if this is a valid (started) instance.
bool IsValid() const;
explicit operator bool() const { return IsValid(); }
/// Return true if |service_name| is already being served on
/// this machine, e.g. by another thread or process.
static bool HasServer(StringPiece service_name);
/// Start a new IpcService instance. This will fail if the service
/// identified by |service_name| is already served on this machine,
/// e.g. from another thread of process.
///
/// On failure, set |*error| and return an invalid instance.
/// On success, return a new valid instance.
static IpcService StartServer(StringPiece service_name, std::string* error);
/// Wait until a new client connects to the service, using an
/// IpcService::ConnectTo() call from another thread or process. |timeout_ms|
/// is a timeout in milliseconds, a negative value indicates waiting
/// indefinitely.
///
/// - On success, return a new valid peer ScopedHandle (representing the
/// connected server-side endpoint of the pipe). This can be used to
/// read or write bytes from / to the client. Closing the handle
/// will gracefully close the pipe from the server side (so buffered
/// writes will not be lost.
///
/// NOTE: This implementation supports one connected client at a time,
/// hence once a peer handle has been returned, any other call to
/// AcceptPeer() will fail until the peer handle is closed.
///
/// - On timeout, set |*did_timeout| to true, set |*error| to the hard
/// coded "timed out" string, and return an invalid ScopedHandle value.
///
/// - On error, set |*did_teimout| to false, set |*error| to a relevant
/// error message, and return an invalid ScopedHandle value.
///
ScopedHandle AcceptPeer(int64_t timeout_ms, bool* did_timeout,
std::string* error);
/// Stop current server instance. Called implicitly by destructor.
void Stop();
/// Try to connect to a service identified by |service_name|, which is must be
/// being served by another thread or process on the local machine. This will
/// fail immediately if not server was started for the service.
///
/// If the server has not called AcceptPeer() yet, or is busy serving another
/// client, this will wait until the timeout expiration, or the server is
/// stopped, whichever happens first.
///
/// NOTE: On Posix, this means that the Unix domain socket used by the service
/// has a backlog size of 0.
///
/// |timeout_ms| is a timeout in milliseconds, a negative value means blocking
/// indefinitely.
///
/// On success, return a new handle to read from / send to bytes with the
/// server (on Posix, socket will be in blocking mode).
///
/// On timeout, set |*did_timeout| to true and |*error| to the hard-coded
/// "timed out" string, then return an invalid ScopedHandle value.
///
/// On other erroris, set |*did_timeout| to false and |*error| to the
/// corresponding error message, then return an invalid ScopedHandle value.
///
static ScopedHandle ConnectTo(StringPiece service_name, int64_t timeout_ms,
bool* did_timeout, std::string* error);
#ifndef _WIN32
/// POSIX only: Connect asychronously to local |service_name|. On success, set
/// |*did_connect| and return a valid handle. On failure, set |*error_message|
/// and return an invalid handle.
///
/// Note that the connection can happen immediately, in which case
/// |*did_connect| will be set to true.
///
/// The returned handle is always in non-blocking mode, and if |*did_connect|
/// is false, the caller should wait for a writable event on it (using
/// select() or one of its variants) then use GetNativeAsyncConnectStatus() on
/// the get() value to determine whether the connection succeeded
/// or failed.
static ScopedHandle AsyncConnectTo(StringPiece service_name,
bool* did_connect,
std::string* error_message);
#endif // !_WIN32
private:
#ifdef _WIN32
IpcService(ScopedHandle pipe_handle, std::wstring&& pipe_path)
: pipe_handle_(std::move(pipe_handle)), pipe_path_(std::move(pipe_path)) {
}
ScopedHandle pipe_handle_;
std::wstring pipe_path_;
#else
IpcService(ScopedHandle server_handle, std::string&& socket_path)
: server_handle_(std::move(server_handle)),
socket_path_(std::move(socket_path)) {}
ScopedHandle server_handle_;
std::string socket_path_;
#endif // !_WIN32
};