// 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 #include #include #include #include #include namespace mp { template 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 struct StructField { template StructField(S& struct_) : m_struct(struct_) { } Struct& m_struct; // clang-format off template auto get() const -> decltype(A::get(this->m_struct)) { return A::get(this->m_struct); } template auto has() const -> std::enable_if_t { return A::getHas(m_struct); } template auto has() const -> std::enable_if_t { return A::has(m_struct); } template auto has() const -> std::enable_if_t { return true; } template auto want() const -> std::enable_if_t { return A::getWant(m_struct); } template auto want() const -> std::enable_if_t { return true; } template decltype(auto) set(Args&&... args) const { return A::set(this->m_struct, std::forward(args)...); } template decltype(auto) init(Args&&... args) const { return A::init(this->m_struct, std::forward(args)...); } template auto setHas() const -> std::enable_if_t { return A::setHas(m_struct); } template auto setHas() const -> std::enable_if_t { } template auto setWant() const -> std::enable_if_t { return A::setWant(m_struct); } template auto setWant() const -> std::enable_if_t { } // 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 struct ReadDestEmplace { ReadDestEmplace(TypeList, 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 decltype(auto) construct(Args&&... args) { return m_emplace_fn(std::forward(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 decltype(auto) update(UpdateFn&& update_fn) { if constexpr (std::is_const_v>>) { // 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 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 auto ReadDestTemp() { return ReadDestEmplace{TypeList(), [&](auto&&... args) -> decltype(auto) { return LocalType{std::forward(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 struct ReadDestUpdate { ReadDestUpdate(Value& value) : m_value(value) {} //! Simple case. If ReadField works by calling update() just forward arguments to update_fn. template 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 Value& construct(Args&&... args) { m_value.~Value(); new (&m_value) Value(std::forward(args)...); return m_value; } Value& m_value; }; template decltype(auto) ReadField(TypeList, Args&&... args) { return CustomReadField(TypeList...>(), Priority<2>(), std::forward(args)...); } template void ThrowField(TypeList, InvokeContext& invoke_context, Input&& input) { ReadField( TypeList(), invoke_context, input, ReadDestEmplace(TypeList(), [](auto&& ...args) -> const LocalType& { throw LocalType{std::forward(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 void ThrowField(TypeList, InvokeContext& invoke_context, Input&& input) { auto data = input.get(); throw std::runtime_error(std::string(CharCast(data.begin()), data.size())); } template bool CustomHasValue(InvokeContext& invoke_context, Values&&... value) { return true; } template void BuildField(TypeList, Context& context, Output&& output, Values&&... values) { if (CustomHasValue(context, std::forward(values)...)) { CustomBuildField(TypeList(), Priority<3>(), context, std::forward(values)..., std::forward(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 struct ListOutput; template struct ListOutput<::capnp::List> { using Builder = typename ::capnp::List::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 decltype(auto) set(Arg&& arg) const { return static_cast(this->m_builder).set(m_index, std::forward(arg)); } template decltype(auto) init(Arg&& arg) const { return static_cast(this->m_builder).init(m_index, std::forward(arg)); } // clang-format on }; template void CustomBuildField(TypeList, Priority<0>, InvokeContext& invoke_context, Value&& value, Output&& output) { output.set(BuildPrimitive(invoke_context, std::forward(value), TypeList())); } //! PassField override for callable interface reference arguments. template auto PassField(Priority<1>, TypeList, ServerContext& server_context, Fn&& fn, Args&&... args) -> Require { // 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(params); using Interface = typename Decay::Calls; auto param = std::make_unique>(input.get(), server_context.proxy_server.m_context.connection, false); fn.invoke(server_context, std::forward(args)..., *param); } template void MaybeBuildField(std::true_type, Args&&... args) { BuildField(std::forward(args)...); } template void MaybeBuildField(std::false_type, Args&&...) { } template void MaybeReadField(std::true_type, Args&&... args) { ReadField(std::forward(args)...); } template void MaybeReadField(std::false_type, Args&&...) { } template void MaybeSetWant(TypeList, Priority<1>, Value&& value, Output&& output) { if (value) { output.setWant(); } } template void MaybeSetWant(LocalTypes, Priority<0>, Args&&...) { } //! Default PassField implementation calling MaybeReadField/MaybeBuildField. template void PassField(Priority<0>, TypeList, ServerContext& server_context, Fn&& fn, Args&&... args) { InvokeContext& invoke_context = server_context; using ArgType = RemoveCvRef; std::optional param; const auto& params = server_context.call_context.getParams(); MaybeReadField(std::integral_constant(), TypeList(), invoke_context, Make(params), ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { param.emplace(std::forward(args)...); return *param; })); if constexpr (Accessor::in) { assert(param); } else { if (!param) param.emplace(); } fn.invoke(server_context, std::forward(args)..., static_cast(*param)); auto&& results = server_context.call_context.getResults(); MaybeBuildField(std::integral_constant(), TypeList(), invoke_context, Make(results), *param); } //! Default PassField implementation for count(0) arguments, calling ReadField/BuildField template 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(params); ReadField(TypeList<>(), server_context, input); fn.invoke(server_context, std::forward(args)...); auto&& results = server_context.call_context.getResults(); BuildField(TypeList<>(), server_context, Make(results)); } template struct IterateFieldsHelper { template void handleChain(Arg1&& arg1, Arg2&& arg2, ParamList, NextFn&& next_fn, NextFnArgs&&... next_fn_args) { using S = Split; handleChain(std::forward(arg1), std::forward(arg2), typename S::First()); next_fn.handleChain(std::forward(arg1), std::forward(arg2), typename S::Second(), std::forward(next_fn_args)...); } template void handleChain(Arg1&& arg1, Arg2&& arg2, ParamList) { static_cast(this)->handleField(std::forward(arg1), std::forward(arg2), ParamList()); } private: IterateFieldsHelper() = default; friend Derived; }; struct IterateFields : IterateFieldsHelper { template void handleField(Arg1&&, Arg2&&, ParamList) { } }; template struct ClientException { struct BuildParams : IterateFieldsHelper { template void handleField(InvokeContext& invoke_context, Params& params, ParamList) { } BuildParams(ClientException* client_exception) : m_client_exception(client_exception) {} ClientException* m_client_exception; }; struct ReadResults : IterateFieldsHelper { template void handleField(InvokeContext& invoke_context, Results& results, ParamList) { StructField input(results); if (input.has()) { ThrowField(TypeList(), invoke_context, input); } } ReadResults(ClientException* client_exception) : m_client_exception(client_exception) {} ClientException* m_client_exception; }; }; template struct ClientParam { ClientParam(Types&&... values) : m_values(values...) {} struct BuildParams : IterateFieldsHelper { template void handleField(Args&&... args) { callBuild<0>(std::forward(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 auto callBuild(Args&&... args) -> std::enable_if_t<(I < sizeof...(Types))> { callBuild(std::forward(args)..., std::get(m_client_param->m_values)); } template auto callBuild(ClientInvokeContext& invoke_context, Params& params, ParamList, Values&&... values) -> std::enable_if_t<(I == sizeof...(Types))> { MaybeBuildField(std::integral_constant(), ParamList(), invoke_context, Make(params), std::forward(values)...); MaybeSetWant( ParamList(), Priority<1>(), std::forward(values)..., Make(params)); } BuildParams(ClientParam* client_param) : m_client_param(client_param) {} ClientParam* m_client_param; }; struct ReadResults : IterateFieldsHelper { template void handleField(Args&&... args) { callRead<0>(std::forward(args)...); } template auto callRead(Args&&... args) -> std::enable_if_t<(I < sizeof...(Types))> { callRead(std::forward(args)..., std::get(m_client_param->m_values)); } template auto callRead(ClientInvokeContext& invoke_context, Results& results, TypeList, Values&&... values) -> std::enable_if_t { MaybeReadField(std::integral_constant(), TypeList...>(), invoke_context, Make(results), ReadDestUpdate(values)...); } ReadResults(ClientParam* client_param) : m_client_param(client_param) {} ClientParam* m_client_param; }; std::tuple m_values; }; template ClientParam MakeClientParam(Types&&... values) { return {std::forward(values)...}; } struct ServerCall { // FIXME: maybe call call_context.releaseParams() template decltype(auto) invoke(ServerContext& server_context, TypeList<>, Args&&... args) const { return ProxyServerMethodTraits::invoke( server_context, std::forward(args)...); } }; struct ServerDestroy { template void invoke(ServerContext& server_context, TypeList<>, Args&&... args) const { server_context.proxy_server.invokeDestroy(std::forward(args)...); } }; template struct ServerRet : Parent { ServerRet(Parent parent) : Parent(parent) {} template void invoke(ServerContext& server_context, TypeList<>, Args&&... args) const { auto&& result = Parent::invoke(server_context, TypeList<>(), std::forward(args)...); auto&& results = server_context.call_context.getResults(); InvokeContext& invoke_context = server_context; BuildField(TypeList(), invoke_context, Make(results), std::forward(result)); } }; template struct ServerExcept : Parent { ServerExcept(Parent parent) : Parent(parent) {} template void invoke(ServerContext& server_context, TypeList<>, Args&&... args) const { try { return Parent::invoke(server_context, TypeList<>(), std::forward(args)...); } catch (const Exception& exception) { auto&& results = server_context.call_context.getResults(); BuildField(TypeList(), server_context, Make(results), exception); } } }; //! Helper for CustomPassField below. Call Accessor::get method if it has one, //! otherwise return capnp::Void. template decltype(auto) MaybeGet(Message&& message, decltype(Accessor::get(message))* enable = nullptr) { return Accessor::get(message); } template ::capnp::Void MaybeGet(...) { return {}; } template 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 auto PassField(Priority<2>, Args&&... args) -> decltype(CustomPassField(std::forward(args)...)) { return CustomPassField(std::forward(args)...); }; template struct ServerField : Parent { ServerField(Parent parent) : Parent(parent) {} const Parent& parent() const { return *this; } template decltype(auto) invoke(ServerContext& server_context, ArgTypes, Args&&... args) const { return PassField(Priority<2>(), typename Split::First(), server_context, this->parent(), typename Split::Second(), std::forward(args)...); } }; template ServerField MakeServerField(Parent parent) { return {parent}; } template struct CapRequestTraits; template 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 void clientDestroy(Client& client) { if (client.m_context.connection) { client.m_context.connection->m_loop.log() << "IPC client destroy " << typeid(client).name(); } else { KJ_LOG(INFO, "IPC interrupted client destroy", typeid(client).name()); } } template void serverDestroy(Server& server) { server.m_context.connection->m_loop.log() << "IPC server destroy " << typeid(server).name(); } //! Entry point called by generated client code that looks like: //! //! ProxyClient::M0::Result ProxyClient::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 void clientInvoke(ProxyClient& proxy_client, const GetRequest& get_request, FieldObjs&&... fields) { if (!proxy_client.m_context.connection) { throw std::logic_error("clientInvoke call made after disconnect"); } if (!g_thread_context.waiter) { assert(g_thread_context.thread_name.empty()); g_thread_context.thread_name = ThreadName(proxy_client.m_context.connection->m_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(); proxy_client.m_context.connection->m_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; proxy_client.m_context.connection->m_loop.sync([&]() { auto request = (proxy_client.m_client.*get_request)(nullptr); using Request = CapRequestTraits; using FieldList = typename ProxyClientMethodTraits::Fields; IterateFields().handleChain(invoke_context, request, FieldList(), typename FieldObjs::BuildParams{&fields}...); proxy_client.m_context.connection->m_loop.logPlain() << "{" << invoke_context.thread_context.thread_name << "} IPC client send " << TypeName() << " " << LogEscape(request.toString()); proxy_client.m_context.connection->m_loop.m_task_set->add(request.send().then( [&](::capnp::Response&& response) { proxy_client.m_context.connection->m_loop.logPlain() << "{" << invoke_context.thread_context.thread_name << "} IPC client recv " << TypeName() << " " << LogEscape(response.toString()); try { IterateFields().handleChain( invoke_context, response, FieldList(), typename FieldObjs::ReadResults{&fields}...); } catch (...) { exception = std::current_exception(); } const std::unique_lock lock(invoke_context.thread_context.waiter->m_mutex); done = true; invoke_context.thread_context.waiter->m_cv.notify_all(); }, [&](const ::kj::Exception& e) { kj_exception = kj::str("kj::Exception: ", e).cStr(); proxy_client.m_context.connection->m_loop.logPlain() << "{" << invoke_context.thread_context.thread_name << "} IPC client exception " << kj_exception; const std::unique_lock lock(invoke_context.thread_context.waiter->m_mutex); done = true; invoke_context.thread_context.waiter->m_cv.notify_all(); })); }); std::unique_lock 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.connection->m_loop.raise() << kj_exception; } //! 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 auto ReplaceVoid(Fn&& fn, Ret&& ret) -> std::enable_if_t, decltype(ret())> { fn(); return ret(); } //! Overload of above for non-void `fn()` case. template auto ReplaceVoid(Fn&& fn, Ret&& ret) -> std::enable_if_t, decltype(fn())> { return fn(); } extern std::atomic server_reqs; //! Entry point called by generated server code that looks like: //! //! kj::Promise ProxyServer::methodName(CallContext call_context) { //! return serverInvoke(*this, call_context, MakeServerField<0, ...>(MakeServerField<1, ...>(Make(ServerCall())))); //! } //! //! Ellipses above are where generated Accessor<> type declarations are inserted. template kj::Promise 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.connection->m_loop.log() << "IPC server recv request #" << req << " " << TypeName() << " " << LogEscape(params.toString()); try { using ServerContext = ServerInvokeContext; using ArgList = typename ProxyClientMethodTraits::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(kj::mv(call_context)); }) .then([&server, req](CallContext call_context) { server.m_context.connection->m_loop.log() << "IPC server send response #" << req << " " << TypeName() << " " << LogEscape(call_context.getResults().toString()); }); } catch (const std::exception& e) { server.m_context.connection->m_loop.log() << "IPC server unhandled exception: " << e.what(); throw; } catch (...) { server.m_context.connection->m_loop.log() << "IPC server unhandled exception"; throw; } } //! Map to convert client interface pointers to ProxyContext struct references //! at runtime using typeids. struct ProxyTypeRegister { template ProxyTypeRegister(TypeList) { types().emplace(typeid(Interface), [](void* iface) -> ProxyContext& { return static_cast::Client&>(*static_cast(iface)).m_context; }); } using Types = std::map; static Types& types() { static Types types; return types; } }; } // namespace mp #endif // MP_PROXY_TYPES_H