mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
f15ef6cdeec5 Improve IPC client disconnected exceptions 4a9a387e68b4 test: Add test coverage for client & server disconnections a848ec60d490 refactor: Add clang thread safety annotations to EventLoop 4b97111ada84 refactor: Remove DestructorCatcher and AsyncCallable 276eb8f99d05 refactor: Drop addClient/removeClient methods 025a77ec2e46 refactor: Use EventLoopRef instead of addClient/removeClient 394f966e93f8 refactor: Add ProxyContext EventLoop* member c1aa2d7dd546 proxy-io.h: Add EventLoopRef RAII class handle addClient/removeClient refcounting 2e02532f4720 proxy-io.h: Add more detailed EventLoop comment git-subtree-dir: src/ipc/libmultiprocess git-subtree-split: f15ef6cdeec54c83f52e00de47d57b09f9a5f03b
738 lines
31 KiB
C++
738 lines
31 KiB
C++
// Copyright (c) 2019 The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#ifndef MP_PROXY_TYPES_H
|
|
#define MP_PROXY_TYPES_H
|
|
|
|
#include <mp/proxy-io.h>
|
|
|
|
#include <exception>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <typeindex>
|
|
#include <vector>
|
|
|
|
namespace mp {
|
|
|
|
template <typename Value>
|
|
class ValueField
|
|
{
|
|
public:
|
|
ValueField(Value& value) : m_value(value) {}
|
|
ValueField(Value&& value) : m_value(value) {}
|
|
Value& m_value;
|
|
|
|
Value& get() { return m_value; }
|
|
Value& init() { return m_value; }
|
|
bool has() { return true; }
|
|
};
|
|
|
|
template <typename Accessor, typename Struct>
|
|
struct StructField
|
|
{
|
|
template <typename S>
|
|
StructField(S& struct_) : m_struct(struct_)
|
|
{
|
|
}
|
|
Struct& m_struct;
|
|
|
|
// clang-format off
|
|
template<typename A = Accessor> auto get() const -> decltype(A::get(this->m_struct)) { return A::get(this->m_struct); }
|
|
template<typename A = Accessor> auto has() const -> std::enable_if_t<A::optional, bool> { return A::getHas(m_struct); }
|
|
template<typename A = Accessor> auto has() const -> std::enable_if_t<!A::optional && A::boxed, bool> { return A::has(m_struct); }
|
|
template<typename A = Accessor> auto has() const -> std::enable_if_t<!A::optional && !A::boxed, bool> { return true; }
|
|
template<typename A = Accessor> auto want() const -> std::enable_if_t<A::requested, bool> { return A::getWant(m_struct); }
|
|
template<typename A = Accessor> auto want() const -> std::enable_if_t<!A::requested, bool> { return true; }
|
|
template<typename A = Accessor, typename... Args> decltype(auto) set(Args&&... args) const { return A::set(this->m_struct, std::forward<Args>(args)...); }
|
|
template<typename A = Accessor, typename... Args> decltype(auto) init(Args&&... args) const { return A::init(this->m_struct, std::forward<Args>(args)...); }
|
|
template<typename A = Accessor> auto setHas() const -> std::enable_if_t<A::optional> { return A::setHas(m_struct); }
|
|
template<typename A = Accessor> auto setHas() const -> std::enable_if_t<!A::optional> { }
|
|
template<typename A = Accessor> auto setWant() const -> std::enable_if_t<A::requested> { return A::setWant(m_struct); }
|
|
template<typename A = Accessor> auto setWant() const -> std::enable_if_t<!A::requested> { }
|
|
// clang-format on
|
|
};
|
|
|
|
|
|
|
|
// Destination parameter type that can be passed to ReadField function as an
|
|
// alternative to ReadDestUpdate. It allows the ReadField implementation to call
|
|
// the provided emplace_fn function with constructor arguments, so it only needs
|
|
// to determine the arguments, and can let the emplace function decide how to
|
|
// actually construct the read destination object. For example, if a std::string
|
|
// is being read, the ReadField call will call the custom emplace_fn with char*
|
|
// and size_t arguments, and the emplace function can decide whether to call the
|
|
// constructor via the operator or make_shared or emplace or just return a
|
|
// temporary string that is moved from.
|
|
template <typename LocalType, typename EmplaceFn>
|
|
struct ReadDestEmplace
|
|
{
|
|
ReadDestEmplace(TypeList<LocalType>, EmplaceFn&& emplace_fn) : m_emplace_fn(emplace_fn) {}
|
|
|
|
//! Simple case. If ReadField impementation calls this construct() method
|
|
//! with constructor arguments, just pass them on to the emplace function.
|
|
template <typename... Args>
|
|
decltype(auto) construct(Args&&... args)
|
|
{
|
|
return m_emplace_fn(std::forward<Args>(args)...);
|
|
}
|
|
|
|
//! More complicated case. If ReadField implementation works by calling this
|
|
//! update() method, adapt it call construct() instead. This requires
|
|
//! LocalType to have a default constructor to create new object that can be
|
|
//! passed to update()
|
|
template <typename UpdateFn>
|
|
decltype(auto) update(UpdateFn&& update_fn)
|
|
{
|
|
if constexpr (std::is_const_v<std::remove_reference_t<std::invoke_result_t<EmplaceFn>>>) {
|
|
// If destination type is const, default construct temporary
|
|
// to pass to update, then call move constructor via construct() to
|
|
// move from that temporary.
|
|
std::remove_cv_t<LocalType> temp;
|
|
update_fn(temp);
|
|
return construct(std::move(temp));
|
|
} else {
|
|
// Default construct object and pass it to update_fn.
|
|
decltype(auto) temp = construct();
|
|
update_fn(temp);
|
|
return temp;
|
|
}
|
|
}
|
|
EmplaceFn& m_emplace_fn;
|
|
};
|
|
|
|
//! Helper function to create a ReadDestEmplace object that constructs a
|
|
//! temporary, ReadField can return.
|
|
template <typename LocalType>
|
|
auto ReadDestTemp()
|
|
{
|
|
return ReadDestEmplace{TypeList<LocalType>(), [&](auto&&... args) -> decltype(auto) {
|
|
return LocalType{std::forward<decltype(args)>(args)...};
|
|
}};
|
|
}
|
|
|
|
//! Destination parameter type that can be passed to ReadField function as an
|
|
//! alternative to ReadDestEmplace. Instead of requiring an emplace callback to
|
|
//! construct a new value, it just takes a reference to an existing value and
|
|
//! assigns a new value to it.
|
|
template <typename Value>
|
|
struct ReadDestUpdate
|
|
{
|
|
ReadDestUpdate(Value& value) : m_value(value) {}
|
|
|
|
//! Simple case. If ReadField works by calling update() just forward arguments to update_fn.
|
|
template <typename UpdateFn>
|
|
Value& update(UpdateFn&& update_fn)
|
|
{
|
|
update_fn(m_value);
|
|
return m_value;
|
|
}
|
|
|
|
//! More complicated case. If ReadField works by calling construct(), need
|
|
//! to reconstruct m_value in place.
|
|
template <typename... Args>
|
|
Value& construct(Args&&... args)
|
|
{
|
|
m_value.~Value();
|
|
new (&m_value) Value(std::forward<Args>(args)...);
|
|
return m_value;
|
|
}
|
|
|
|
Value& m_value;
|
|
};
|
|
|
|
template <typename... LocalTypes, typename... Args>
|
|
decltype(auto) ReadField(TypeList<LocalTypes...>, Args&&... args)
|
|
{
|
|
return CustomReadField(TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2>(), std::forward<Args>(args)...);
|
|
}
|
|
|
|
template <typename LocalType, typename Input>
|
|
void ThrowField(TypeList<LocalType>, InvokeContext& invoke_context, Input&& input)
|
|
{
|
|
ReadField(
|
|
TypeList<LocalType>(), invoke_context, input, ReadDestEmplace(TypeList<LocalType>(),
|
|
[](auto&& ...args) -> const LocalType& { throw LocalType{std::forward<decltype(args)>(args)...}; }));
|
|
}
|
|
|
|
//! Special case for generic std::exception. It's an abstract type so it can't
|
|
//! be created directly. Rethrow as std::runtime_error so callers expecting it
|
|
//! will still catch it.
|
|
template <typename Input>
|
|
void ThrowField(TypeList<std::exception>, InvokeContext& invoke_context, Input&& input)
|
|
{
|
|
auto data = input.get();
|
|
throw std::runtime_error(std::string(CharCast(data.begin()), data.size()));
|
|
}
|
|
|
|
template <typename... Values>
|
|
bool CustomHasValue(InvokeContext& invoke_context, Values&&... value)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
template <typename... LocalTypes, typename Context, typename... Values, typename Output>
|
|
void BuildField(TypeList<LocalTypes...>, Context& context, Output&& output, Values&&... values)
|
|
{
|
|
if (CustomHasValue(context, std::forward<Values>(values)...)) {
|
|
CustomBuildField(TypeList<LocalTypes...>(), Priority<3>(), context, std::forward<Values>(values)...,
|
|
std::forward<Output>(output));
|
|
}
|
|
}
|
|
|
|
// Adapter to let BuildField overloads methods work set & init list elements as
|
|
// if they were fields of a struct. If BuildField is changed to use some kind of
|
|
// accessor class instead of calling method pointers, then then maybe this could
|
|
// go away or be simplified, because would no longer be a need to return
|
|
// ListOutput method pointers emulating capnp struct method pointers..
|
|
template <typename ListType>
|
|
struct ListOutput;
|
|
|
|
template <typename T, ::capnp::Kind kind>
|
|
struct ListOutput<::capnp::List<T, kind>>
|
|
{
|
|
using Builder = typename ::capnp::List<T, kind>::Builder;
|
|
|
|
ListOutput(Builder& builder, size_t index) : m_builder(builder), m_index(index) {}
|
|
Builder& m_builder;
|
|
size_t m_index;
|
|
|
|
// clang-format off
|
|
decltype(auto) get() const { return this->m_builder[this->m_index]; }
|
|
decltype(auto) init() const { return this->m_builder[this->m_index]; }
|
|
template<typename B = Builder, typename Arg> decltype(auto) set(Arg&& arg) const { return static_cast<B&>(this->m_builder).set(m_index, std::forward<Arg>(arg)); }
|
|
template<typename B = Builder, typename Arg> decltype(auto) init(Arg&& arg) const { return static_cast<B&>(this->m_builder).init(m_index, std::forward<Arg>(arg)); }
|
|
// clang-format on
|
|
};
|
|
|
|
template <typename LocalType, typename Value, typename Output>
|
|
void CustomBuildField(TypeList<LocalType>, Priority<0>, InvokeContext& invoke_context, Value&& value, Output&& output)
|
|
{
|
|
output.set(BuildPrimitive(invoke_context, std::forward<Value>(value), TypeList<decltype(output.get())>()));
|
|
}
|
|
|
|
//! PassField override for callable interface reference arguments.
|
|
template <typename Accessor, typename LocalType, typename ServerContext, typename Fn, typename... Args>
|
|
auto PassField(Priority<1>, TypeList<LocalType&>, ServerContext& server_context, Fn&& fn, Args&&... args)
|
|
-> Require<typename decltype(Accessor::get(server_context.call_context.getParams()))::Calls>
|
|
{
|
|
// Just create a temporary ProxyClient if argument is a reference to an
|
|
// interface client. If argument needs to have a longer lifetime and not be
|
|
// destroyed after this call, a CustomPassField overload can be implemented
|
|
// to bypass this code, and a custom ProxyServerMethodTraits overload can be
|
|
// implemented in order to read the capability pointer out of params and
|
|
// construct a ProxyClient with a longer lifetime.
|
|
const auto& params = server_context.call_context.getParams();
|
|
const auto& input = Make<StructField, Accessor>(params);
|
|
using Interface = typename Decay<decltype(input.get())>::Calls;
|
|
auto param = std::make_unique<ProxyClient<Interface>>(input.get(), server_context.proxy_server.m_context.connection, false);
|
|
fn.invoke(server_context, std::forward<Args>(args)..., *param);
|
|
}
|
|
|
|
template <typename... Args>
|
|
void MaybeBuildField(std::true_type, Args&&... args)
|
|
{
|
|
BuildField(std::forward<Args>(args)...);
|
|
}
|
|
template <typename... Args>
|
|
void MaybeBuildField(std::false_type, Args&&...)
|
|
{
|
|
}
|
|
template <typename... Args>
|
|
void MaybeReadField(std::true_type, Args&&... args)
|
|
{
|
|
ReadField(std::forward<Args>(args)...);
|
|
}
|
|
template <typename... Args>
|
|
void MaybeReadField(std::false_type, Args&&...)
|
|
{
|
|
}
|
|
|
|
template <typename LocalType, typename Value, typename Output>
|
|
void MaybeSetWant(TypeList<LocalType*>, Priority<1>, Value&& value, Output&& output)
|
|
{
|
|
if (value) {
|
|
output.setWant();
|
|
}
|
|
}
|
|
|
|
template <typename LocalTypes, typename... Args>
|
|
void MaybeSetWant(LocalTypes, Priority<0>, Args&&...)
|
|
{
|
|
}
|
|
|
|
//! Default PassField implementation calling MaybeReadField/MaybeBuildField.
|
|
template <typename Accessor, typename LocalType, typename ServerContext, typename Fn, typename... Args>
|
|
void PassField(Priority<0>, TypeList<LocalType>, ServerContext& server_context, Fn&& fn, Args&&... args)
|
|
{
|
|
InvokeContext& invoke_context = server_context;
|
|
using ArgType = RemoveCvRef<LocalType>;
|
|
std::optional<ArgType> param;
|
|
const auto& params = server_context.call_context.getParams();
|
|
MaybeReadField(std::integral_constant<bool, Accessor::in>(), TypeList<ArgType>(), invoke_context,
|
|
Make<StructField, Accessor>(params), ReadDestEmplace(TypeList<ArgType>(), [&](auto&&... args) -> auto& {
|
|
param.emplace(std::forward<decltype(args)>(args)...);
|
|
return *param;
|
|
}));
|
|
if constexpr (Accessor::in) {
|
|
assert(param);
|
|
} else {
|
|
if (!param) param.emplace();
|
|
}
|
|
fn.invoke(server_context, std::forward<Args>(args)..., static_cast<LocalType&&>(*param));
|
|
auto&& results = server_context.call_context.getResults();
|
|
MaybeBuildField(std::integral_constant<bool, Accessor::out>(), TypeList<LocalType>(), invoke_context,
|
|
Make<StructField, Accessor>(results), *param);
|
|
}
|
|
|
|
//! Default PassField implementation for count(0) arguments, calling ReadField/BuildField
|
|
template <typename Accessor, typename ServerContext, typename Fn, typename... Args>
|
|
void PassField(Priority<0>, TypeList<>, ServerContext& server_context, const Fn& fn, Args&&... args)
|
|
{
|
|
const auto& params = server_context.call_context.getParams();
|
|
const auto& input = Make<StructField, Accessor>(params);
|
|
ReadField(TypeList<>(), server_context, input);
|
|
fn.invoke(server_context, std::forward<Args>(args)...);
|
|
auto&& results = server_context.call_context.getResults();
|
|
BuildField(TypeList<>(), server_context, Make<StructField, Accessor>(results));
|
|
}
|
|
|
|
template <typename Derived, size_t N = 0>
|
|
struct IterateFieldsHelper
|
|
{
|
|
template <typename Arg1, typename Arg2, typename ParamList, typename NextFn, typename... NextFnArgs>
|
|
void handleChain(Arg1&& arg1, Arg2&& arg2, ParamList, NextFn&& next_fn, NextFnArgs&&... next_fn_args)
|
|
{
|
|
using S = Split<N, ParamList>;
|
|
handleChain(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), typename S::First());
|
|
next_fn.handleChain(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), typename S::Second(),
|
|
std::forward<NextFnArgs>(next_fn_args)...);
|
|
}
|
|
|
|
template <typename Arg1, typename Arg2, typename ParamList>
|
|
void handleChain(Arg1&& arg1, Arg2&& arg2, ParamList)
|
|
{
|
|
static_cast<Derived*>(this)->handleField(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), ParamList());
|
|
}
|
|
private:
|
|
IterateFieldsHelper() = default;
|
|
friend Derived;
|
|
};
|
|
|
|
struct IterateFields : IterateFieldsHelper<IterateFields, 0>
|
|
{
|
|
template <typename Arg1, typename Arg2, typename ParamList>
|
|
void handleField(Arg1&&, Arg2&&, ParamList)
|
|
{
|
|
}
|
|
};
|
|
|
|
template <typename Exception, typename Accessor>
|
|
struct ClientException
|
|
{
|
|
struct BuildParams : IterateFieldsHelper<BuildParams, 0>
|
|
{
|
|
template <typename Params, typename ParamList>
|
|
void handleField(InvokeContext& invoke_context, Params& params, ParamList)
|
|
{
|
|
}
|
|
|
|
BuildParams(ClientException* client_exception) : m_client_exception(client_exception) {}
|
|
ClientException* m_client_exception;
|
|
};
|
|
|
|
struct ReadResults : IterateFieldsHelper<ReadResults, 0>
|
|
{
|
|
template <typename Results, typename ParamList>
|
|
void handleField(InvokeContext& invoke_context, Results& results, ParamList)
|
|
{
|
|
StructField<Accessor, Results> input(results);
|
|
if (input.has()) {
|
|
ThrowField(TypeList<Exception>(), invoke_context, input);
|
|
}
|
|
}
|
|
|
|
ReadResults(ClientException* client_exception) : m_client_exception(client_exception) {}
|
|
ClientException* m_client_exception;
|
|
};
|
|
};
|
|
|
|
template <typename Accessor, typename... Types>
|
|
struct ClientParam
|
|
{
|
|
ClientParam(Types&&... values) : m_values(values...) {}
|
|
|
|
struct BuildParams : IterateFieldsHelper<BuildParams, sizeof...(Types)>
|
|
{
|
|
template <typename... Args>
|
|
void handleField(Args&&... args)
|
|
{
|
|
callBuild<0>(std::forward<Args>(args)...);
|
|
}
|
|
|
|
// TODO Possible optimization to speed up compile time:
|
|
// https://stackoverflow.com/a/7858971 Using enable_if below to check
|
|
// position when unpacking tuple might be slower than pattern matching
|
|
// approach in the stack overflow solution
|
|
template <size_t I, typename... Args>
|
|
auto callBuild(Args&&... args) -> std::enable_if_t<(I < sizeof...(Types))>
|
|
{
|
|
callBuild<I + 1>(std::forward<Args>(args)..., std::get<I>(m_client_param->m_values));
|
|
}
|
|
|
|
template <size_t I, typename Params, typename ParamList, typename... Values>
|
|
auto callBuild(ClientInvokeContext& invoke_context, Params& params, ParamList, Values&&... values) ->
|
|
std::enable_if_t<(I == sizeof...(Types))>
|
|
{
|
|
MaybeBuildField(std::integral_constant<bool, Accessor::in>(), ParamList(), invoke_context,
|
|
Make<StructField, Accessor>(params), std::forward<Values>(values)...);
|
|
MaybeSetWant(
|
|
ParamList(), Priority<1>(), std::forward<Values>(values)..., Make<StructField, Accessor>(params));
|
|
}
|
|
|
|
BuildParams(ClientParam* client_param) : m_client_param(client_param) {}
|
|
ClientParam* m_client_param;
|
|
};
|
|
|
|
struct ReadResults : IterateFieldsHelper<ReadResults, sizeof...(Types)>
|
|
{
|
|
template <typename... Args>
|
|
void handleField(Args&&... args)
|
|
{
|
|
callRead<0>(std::forward<Args>(args)...);
|
|
}
|
|
|
|
template <int I, typename... Args>
|
|
auto callRead(Args&&... args) -> std::enable_if_t<(I < sizeof...(Types))>
|
|
{
|
|
callRead<I + 1>(std::forward<Args>(args)..., std::get<I>(m_client_param->m_values));
|
|
}
|
|
|
|
template <int I, typename Results, typename... Params, typename... Values>
|
|
auto callRead(ClientInvokeContext& invoke_context, Results& results, TypeList<Params...>, Values&&... values)
|
|
-> std::enable_if_t<I == sizeof...(Types)>
|
|
{
|
|
MaybeReadField(std::integral_constant<bool, Accessor::out>(), TypeList<Decay<Params>...>(), invoke_context,
|
|
Make<StructField, Accessor>(results), ReadDestUpdate(values)...);
|
|
}
|
|
|
|
ReadResults(ClientParam* client_param) : m_client_param(client_param) {}
|
|
ClientParam* m_client_param;
|
|
};
|
|
|
|
std::tuple<Types&&...> m_values;
|
|
};
|
|
|
|
template <typename Accessor, typename... Types>
|
|
ClientParam<Accessor, Types...> MakeClientParam(Types&&... values)
|
|
{
|
|
return {std::forward<Types>(values)...};
|
|
}
|
|
|
|
struct ServerCall
|
|
{
|
|
// FIXME: maybe call call_context.releaseParams()
|
|
template <typename ServerContext, typename... Args>
|
|
decltype(auto) invoke(ServerContext& server_context, TypeList<>, Args&&... args) const
|
|
{
|
|
return ProxyServerMethodTraits<typename decltype(server_context.call_context.getParams())::Reads>::invoke(
|
|
server_context,
|
|
std::forward<Args>(args)...);
|
|
}
|
|
};
|
|
|
|
struct ServerDestroy
|
|
{
|
|
template <typename ServerContext, typename... Args>
|
|
void invoke(ServerContext& server_context, TypeList<>, Args&&... args) const
|
|
{
|
|
server_context.proxy_server.invokeDestroy(std::forward<Args>(args)...);
|
|
}
|
|
};
|
|
|
|
template <typename Accessor, typename Parent>
|
|
struct ServerRet : Parent
|
|
{
|
|
ServerRet(Parent parent) : Parent(parent) {}
|
|
|
|
template <typename ServerContext, typename... Args>
|
|
void invoke(ServerContext& server_context, TypeList<>, Args&&... args) const
|
|
{
|
|
auto&& result = Parent::invoke(server_context, TypeList<>(), std::forward<Args>(args)...);
|
|
auto&& results = server_context.call_context.getResults();
|
|
InvokeContext& invoke_context = server_context;
|
|
BuildField(TypeList<decltype(result)>(), invoke_context, Make<StructField, Accessor>(results),
|
|
std::forward<decltype(result)>(result));
|
|
}
|
|
};
|
|
|
|
template <typename Exception, typename Accessor, typename Parent>
|
|
struct ServerExcept : Parent
|
|
{
|
|
ServerExcept(Parent parent) : Parent(parent) {}
|
|
|
|
template <typename ServerContext, typename... Args>
|
|
void invoke(ServerContext& server_context, TypeList<>, Args&&... args) const
|
|
{
|
|
try {
|
|
return Parent::invoke(server_context, TypeList<>(), std::forward<Args>(args)...);
|
|
} catch (const Exception& exception) {
|
|
auto&& results = server_context.call_context.getResults();
|
|
BuildField(TypeList<Exception>(), server_context, Make<StructField, Accessor>(results), exception);
|
|
}
|
|
}
|
|
};
|
|
|
|
//! Helper for CustomPassField below. Call Accessor::get method if it has one,
|
|
//! otherwise return capnp::Void.
|
|
template <typename Accessor, typename Message>
|
|
decltype(auto) MaybeGet(Message&& message, decltype(Accessor::get(message))* enable = nullptr)
|
|
{
|
|
return Accessor::get(message);
|
|
}
|
|
|
|
template <typename Accessor>
|
|
::capnp::Void MaybeGet(...)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
template <class Accessor>
|
|
void CustomPassField();
|
|
|
|
//! PassField override calling CustomPassField function, if it exists.
|
|
//! Defining a CustomPassField or CustomPassMessage overload is useful for
|
|
//! input/output parameters. If an overload is not defined these parameters will
|
|
//! just be deserialized on the server side with ReadField into a temporary
|
|
//! variable, then the server method will be called passing the temporary
|
|
//! variable as a parameter, then the temporary variable will be serialized and
|
|
//! sent back to the client with BuildField. But if a PassField or PassMessage
|
|
//! overload is defined, the overload is called with a callback to invoke and
|
|
//! pass parameters to the server side function, and run arbitrary code before
|
|
//! and after invoking the function.
|
|
template <typename Accessor, typename... Args>
|
|
auto PassField(Priority<2>, Args&&... args) -> decltype(CustomPassField<Accessor>(std::forward<Args>(args)...))
|
|
{
|
|
return CustomPassField<Accessor>(std::forward<Args>(args)...);
|
|
};
|
|
|
|
template <int argc, typename Accessor, typename Parent>
|
|
struct ServerField : Parent
|
|
{
|
|
ServerField(Parent parent) : Parent(parent) {}
|
|
|
|
const Parent& parent() const { return *this; }
|
|
|
|
template <typename ServerContext, typename ArgTypes, typename... Args>
|
|
decltype(auto) invoke(ServerContext& server_context, ArgTypes, Args&&... args) const
|
|
{
|
|
return PassField<Accessor>(Priority<2>(),
|
|
typename Split<argc, ArgTypes>::First(),
|
|
server_context,
|
|
this->parent(),
|
|
typename Split<argc, ArgTypes>::Second(),
|
|
std::forward<Args>(args)...);
|
|
}
|
|
};
|
|
|
|
template <int argc, typename Accessor, typename Parent>
|
|
ServerField<argc, Accessor, Parent> MakeServerField(Parent parent)
|
|
{
|
|
return {parent};
|
|
}
|
|
|
|
template <typename Request>
|
|
struct CapRequestTraits;
|
|
|
|
template <typename _Params, typename _Results>
|
|
struct CapRequestTraits<::capnp::Request<_Params, _Results>>
|
|
{
|
|
using Params = _Params;
|
|
using Results = _Results;
|
|
};
|
|
|
|
//! Entry point called by all generated ProxyClient destructors. This only logs
|
|
//! the object destruction. The actual cleanup happens in the ProxyClient base
|
|
//! destructor.
|
|
template <typename Client>
|
|
void clientDestroy(Client& client)
|
|
{
|
|
if (client.m_context.connection) {
|
|
client.m_context.loop->log() << "IPC client destroy " << typeid(client).name();
|
|
} else {
|
|
KJ_LOG(INFO, "IPC interrupted client destroy", typeid(client).name());
|
|
}
|
|
}
|
|
|
|
template <typename Server>
|
|
void serverDestroy(Server& server)
|
|
{
|
|
server.m_context.loop->log() << "IPC server destroy " << typeid(server).name();
|
|
}
|
|
|
|
//! Entry point called by generated client code that looks like:
|
|
//!
|
|
//! ProxyClient<ClassName>::M0::Result ProxyClient<ClassName>::methodName(M0::Param<0> arg0, M0::Param<1> arg1) {
|
|
//! typename M0::Result result;
|
|
//! clientInvoke(*this, &InterfaceName::Client::methodNameRequest, MakeClientParam<...>(arg0), MakeClientParam<...>(arg1), MakeClientParam<...>(result));
|
|
//! return result;
|
|
//! }
|
|
//!
|
|
//! Ellipses above are where generated Accessor<> type declarations are inserted.
|
|
template <typename ProxyClient, typename GetRequest, typename... FieldObjs>
|
|
void clientInvoke(ProxyClient& proxy_client, const GetRequest& get_request, FieldObjs&&... fields)
|
|
{
|
|
if (!g_thread_context.waiter) {
|
|
assert(g_thread_context.thread_name.empty());
|
|
g_thread_context.thread_name = ThreadName(proxy_client.m_context.loop->m_exe_name);
|
|
// If next assert triggers, it means clientInvoke is being called from
|
|
// the capnp event loop thread. This can happen when a ProxyServer
|
|
// method implementation that runs synchronously on the event loop
|
|
// thread tries to make a blocking callback to the client. Any server
|
|
// method that makes a blocking callback or blocks in general needs to
|
|
// run asynchronously off the event loop thread. This is easy to fix by
|
|
// just adding a 'context :Proxy.Context' argument to the capnp method
|
|
// declaration so the server method runs in a dedicated thread.
|
|
assert(!g_thread_context.loop_thread);
|
|
g_thread_context.waiter = std::make_unique<Waiter>();
|
|
proxy_client.m_context.loop->logPlain()
|
|
<< "{" << g_thread_context.thread_name
|
|
<< "} IPC client first request from current thread, constructing waiter";
|
|
}
|
|
ClientInvokeContext invoke_context{*proxy_client.m_context.connection, g_thread_context};
|
|
std::exception_ptr exception;
|
|
std::string kj_exception;
|
|
bool done = false;
|
|
const char* disconnected = nullptr;
|
|
proxy_client.m_context.loop->sync([&]() {
|
|
if (!proxy_client.m_context.connection) {
|
|
const std::unique_lock<std::mutex> lock(invoke_context.thread_context.waiter->m_mutex);
|
|
done = true;
|
|
disconnected = "IPC client method called after disconnect.";
|
|
invoke_context.thread_context.waiter->m_cv.notify_all();
|
|
return;
|
|
}
|
|
|
|
auto request = (proxy_client.m_client.*get_request)(nullptr);
|
|
using Request = CapRequestTraits<decltype(request)>;
|
|
using FieldList = typename ProxyClientMethodTraits<typename Request::Params>::Fields;
|
|
IterateFields().handleChain(invoke_context, request, FieldList(), typename FieldObjs::BuildParams{&fields}...);
|
|
proxy_client.m_context.loop->logPlain()
|
|
<< "{" << invoke_context.thread_context.thread_name << "} IPC client send "
|
|
<< TypeName<typename Request::Params>() << " " << LogEscape(request.toString());
|
|
|
|
proxy_client.m_context.loop->m_task_set->add(request.send().then(
|
|
[&](::capnp::Response<typename Request::Results>&& response) {
|
|
proxy_client.m_context.loop->logPlain()
|
|
<< "{" << invoke_context.thread_context.thread_name << "} IPC client recv "
|
|
<< TypeName<typename Request::Results>() << " " << LogEscape(response.toString());
|
|
try {
|
|
IterateFields().handleChain(
|
|
invoke_context, response, FieldList(), typename FieldObjs::ReadResults{&fields}...);
|
|
} catch (...) {
|
|
exception = std::current_exception();
|
|
}
|
|
const std::unique_lock<std::mutex> lock(invoke_context.thread_context.waiter->m_mutex);
|
|
done = true;
|
|
invoke_context.thread_context.waiter->m_cv.notify_all();
|
|
},
|
|
[&](const ::kj::Exception& e) {
|
|
if (e.getType() == ::kj::Exception::Type::DISCONNECTED) {
|
|
disconnected = "IPC client method call interrupted by disconnect.";
|
|
} else {
|
|
kj_exception = kj::str("kj::Exception: ", e).cStr();
|
|
proxy_client.m_context.loop->logPlain()
|
|
<< "{" << invoke_context.thread_context.thread_name << "} IPC client exception " << kj_exception;
|
|
}
|
|
const std::unique_lock<std::mutex> lock(invoke_context.thread_context.waiter->m_mutex);
|
|
done = true;
|
|
invoke_context.thread_context.waiter->m_cv.notify_all();
|
|
}));
|
|
});
|
|
|
|
std::unique_lock<std::mutex> lock(invoke_context.thread_context.waiter->m_mutex);
|
|
invoke_context.thread_context.waiter->wait(lock, [&done]() { return done; });
|
|
if (exception) std::rethrow_exception(exception);
|
|
if (!kj_exception.empty()) proxy_client.m_context.loop->raise() << kj_exception;
|
|
if (disconnected) proxy_client.m_context.loop->raise() << disconnected;
|
|
}
|
|
|
|
//! Invoke callable `fn()` that may return void. If it does return void, replace
|
|
//! return value with value of `ret()`. This is useful for avoiding code
|
|
//! duplication and branching in generic code that forwards calls to functions.
|
|
template <typename Fn, typename Ret>
|
|
auto ReplaceVoid(Fn&& fn, Ret&& ret) ->
|
|
std::enable_if_t<std::is_same_v<void, decltype(fn())>, decltype(ret())>
|
|
{
|
|
fn();
|
|
return ret();
|
|
}
|
|
|
|
//! Overload of above for non-void `fn()` case.
|
|
template <typename Fn, typename Ret>
|
|
auto ReplaceVoid(Fn&& fn, Ret&& ret) ->
|
|
std::enable_if_t<!std::is_same_v<void, decltype(fn())>, decltype(fn())>
|
|
{
|
|
return fn();
|
|
}
|
|
|
|
extern std::atomic<int> server_reqs;
|
|
|
|
//! Entry point called by generated server code that looks like:
|
|
//!
|
|
//! kj::Promise<void> ProxyServer<InterfaceName>::methodName(CallContext call_context) {
|
|
//! return serverInvoke(*this, call_context, MakeServerField<0, ...>(MakeServerField<1, ...>(Make<ServerRet, ...>(ServerCall()))));
|
|
//! }
|
|
//!
|
|
//! Ellipses above are where generated Accessor<> type declarations are inserted.
|
|
template <typename Server, typename CallContext, typename Fn>
|
|
kj::Promise<void> serverInvoke(Server& server, CallContext& call_context, Fn fn)
|
|
{
|
|
auto params = call_context.getParams();
|
|
using Params = decltype(params);
|
|
using Results = typename decltype(call_context.getResults())::Builds;
|
|
|
|
int req = ++server_reqs;
|
|
server.m_context.loop->log() << "IPC server recv request #" << req << " "
|
|
<< TypeName<typename Params::Reads>() << " " << LogEscape(params.toString());
|
|
|
|
try {
|
|
using ServerContext = ServerInvokeContext<Server, CallContext>;
|
|
using ArgList = typename ProxyClientMethodTraits<typename Params::Reads>::Params;
|
|
ServerContext server_context{server, call_context, req};
|
|
// ReplaceVoid is used to support fn.invoke implementations that
|
|
// execute asynchronously and return promises, as well as
|
|
// implementations that execute synchronously and return void. The
|
|
// invoke function will be synchronous by default, but asynchronous if
|
|
// an mp.Context argument is passed, and the mp.Context PassField
|
|
// overload returns a promise executing the request in a worker thread
|
|
// and waiting for it to complete.
|
|
return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
|
|
[&]() { return kj::Promise<CallContext>(kj::mv(call_context)); })
|
|
.then([&server, req](CallContext call_context) {
|
|
server.m_context.loop->log() << "IPC server send response #" << req << " " << TypeName<Results>()
|
|
<< " " << LogEscape(call_context.getResults().toString());
|
|
});
|
|
} catch (const std::exception& e) {
|
|
server.m_context.loop->log() << "IPC server unhandled exception: " << e.what();
|
|
throw;
|
|
} catch (...) {
|
|
server.m_context.loop->log() << "IPC server unhandled exception";
|
|
throw;
|
|
}
|
|
}
|
|
|
|
//! Map to convert client interface pointers to ProxyContext struct references
|
|
//! at runtime using typeids.
|
|
struct ProxyTypeRegister {
|
|
template<typename Interface>
|
|
ProxyTypeRegister(TypeList<Interface>) {
|
|
types().emplace(typeid(Interface), [](void* iface) -> ProxyContext& { return static_cast<typename mp::ProxyType<Interface>::Client&>(*static_cast<Interface*>(iface)).m_context; });
|
|
}
|
|
using Types = std::map<std::type_index, ProxyContext&(*)(void*)>;
|
|
static Types& types() { static Types types; return types; }
|
|
};
|
|
|
|
} // namespace mp
|
|
|
|
#endif // MP_PROXY_TYPES_H
|