Squashed 'src/ipc/libmultiprocess/' content from commit 35944ffd23fa

git-subtree-dir: src/ipc/libmultiprocess
git-subtree-split: 35944ffd23fa26652b82210351d50e896ce16c8f
This commit is contained in:
Ryan Ofsky
2025-04-02 21:41:16 +08:00
commit a2f28e4be9
60 changed files with 5878 additions and 0 deletions

16
include/mp/config.h.in Normal file
View File

@@ -0,0 +1,16 @@
// 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_CONFIG_H
#define MP_CONFIG_H
#cmakedefine CMAKE_INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@"
#cmakedefine capnp_PREFIX "@capnp_PREFIX@"
#cmakedefine HAVE_KJ_FILESYSTEM
#cmakedefine HAVE_PTHREAD_GETNAME_NP @HAVE_PTHREAD_GETNAME_NP@
#cmakedefine HAVE_PTHREAD_THREADID_NP @HAVE_PTHREAD_THREADID_NP@
#cmakedefine HAVE_PTHREAD_GETTHREADID_NP @HAVE_PTHREAD_GETTHREADID_NP@
#endif // MP_CONFIG_H

641
include/mp/proxy-io.h Normal file
View File

@@ -0,0 +1,641 @@
// 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_IO_H
#define MP_PROXY_IO_H
#include <mp/proxy.h>
#include <mp/util.h>
#include <mp/proxy.capnp.h>
#include <capnp/rpc-twoparty.h>
#include <assert.h>
#include <functional>
#include <optional>
#include <map>
#include <memory>
#include <sstream>
#include <string>
namespace mp {
struct ThreadContext;
struct InvokeContext
{
Connection& connection;
};
struct ClientInvokeContext : InvokeContext
{
ThreadContext& thread_context;
ClientInvokeContext(Connection& conn, ThreadContext& thread_context)
: InvokeContext{conn}, thread_context{thread_context}
{
}
};
template <typename ProxyServer, typename CallContext_>
struct ServerInvokeContext : InvokeContext
{
using CallContext = CallContext_;
ProxyServer& proxy_server;
CallContext& call_context;
int req;
ServerInvokeContext(ProxyServer& proxy_server, CallContext& call_context, int req)
: InvokeContext{*proxy_server.m_context.connection}, proxy_server{proxy_server}, call_context{call_context}, req{req}
{
}
};
template <typename Interface, typename Params, typename Results>
using ServerContext = ServerInvokeContext<ProxyServer<Interface>, ::capnp::CallContext<Params, Results>>;
template <>
struct ProxyClient<Thread> : public ProxyClientBase<Thread, ::capnp::Void>
{
using ProxyClientBase::ProxyClientBase;
// https://stackoverflow.com/questions/22357887/comparing-two-mapiterators-why-does-it-need-the-copy-constructor-of-stdpair
ProxyClient(const ProxyClient&) = delete;
~ProxyClient();
void setCleanup(const std::function<void()>& fn);
//! Cleanup function to run when the connection is closed. If the Connection
//! gets destroyed before this ProxyClient<Thread> object, this cleanup
//! callback lets it destroy this object and remove its entry in the
//! thread's request_threads or callback_threads map (after resetting
//! m_cleanup_it so the destructor does not try to access it). But if this
//! object gets destroyed before the Connection, there's no need to run the
//! cleanup function and the destructor will unregister it.
std::optional<CleanupIt> m_cleanup_it;
};
template <>
struct ProxyServer<Thread> final : public Thread::Server
{
public:
ProxyServer(ThreadContext& thread_context, std::thread&& thread);
~ProxyServer();
kj::Promise<void> getName(GetNameContext context) override;
ThreadContext& m_thread_context;
std::thread m_thread;
};
//! Handler for kj::TaskSet failed task events.
class LoggingErrorHandler : public kj::TaskSet::ErrorHandler
{
public:
LoggingErrorHandler(EventLoop& loop) : m_loop(loop) {}
void taskFailed(kj::Exception&& exception) override;
EventLoop& m_loop;
};
using LogFn = std::function<void(bool raise, std::string message)>;
class Logger
{
public:
Logger(bool raise, LogFn& fn) : m_raise(raise), m_fn(fn) {}
Logger(Logger&& logger) : m_raise(logger.m_raise), m_fn(logger.m_fn), m_buffer(std::move(logger.m_buffer)) {}
~Logger() noexcept(false)
{
if (m_fn) m_fn(m_raise, m_buffer.str());
}
template <typename T>
friend Logger& operator<<(Logger& logger, T&& value)
{
if (logger.m_fn) logger.m_buffer << std::forward<T>(value);
return logger;
}
template <typename T>
friend Logger& operator<<(Logger&& logger, T&& value)
{
return logger << std::forward<T>(value);
}
bool m_raise;
LogFn& m_fn;
std::ostringstream m_buffer;
};
std::string LongThreadName(const char* exe_name);
//! Event loop implementation.
//!
//! Based on https://groups.google.com/d/msg/capnproto/TuQFF1eH2-M/g81sHaTAAQAJ
class EventLoop
{
public:
//! Construct event loop object.
EventLoop(const char* exe_name, LogFn log_fn, void* context = nullptr);
~EventLoop();
//! Run event loop. Does not return until shutdown. This should only be
//! called once from the m_thread_id thread. This will block until
//! the m_num_clients reference count is 0.
void loop();
//! Run function on event loop thread. Does not return until function completes.
//! Must be called while the loop() function is active.
void post(const std::function<void()>& fn);
//! Wrapper around EventLoop::post that takes advantage of the
//! fact that callable will not go out of scope to avoid requirement that it
//! be copyable.
template <typename Callable>
void sync(Callable&& callable)
{
post(std::ref(callable));
}
//! Start asynchronous worker thread if necessary. This is only done if
//! there are ProxyServerBase::m_impl objects that need to be destroyed
//! asynchronously, without tying up the event loop thread. This can happen
//! when an interface does not declare a destroy() method that would allow
//! the client to wait for the destructor to finish and run it on a
//! dedicated thread. It can also happen whenever this is a broken
//! connection and the client is no longer around to call the destructors
//! and the server objects need to be garbage collected. In both cases, it
//! is important that ProxyServer::m_impl destructors do not run on the
//! eventloop thread because they may need it to do I/O if they perform
//! other IPC calls.
void startAsyncThread(std::unique_lock<std::mutex>& lock);
//! Add/remove remote client reference counts.
void addClient(std::unique_lock<std::mutex>& lock);
bool removeClient(std::unique_lock<std::mutex>& lock);
//! Check if loop should exit.
bool done(std::unique_lock<std::mutex>& lock);
Logger log()
{
Logger logger(false, m_log_fn);
logger << "{" << LongThreadName(m_exe_name) << "} ";
return logger;
}
Logger logPlain() { return {false, m_log_fn}; }
Logger raise() { return {true, m_log_fn}; }
//! Process name included in thread names so combined debug output from
//! multiple processes is easier to understand.
const char* m_exe_name;
//! ID of the event loop thread
std::thread::id m_thread_id = std::this_thread::get_id();
//! Handle of an async worker thread. Joined on destruction. Unset if async
//! method has not been called.
std::thread m_async_thread;
//! Callback function to run on event loop thread during post() or sync() call.
const std::function<void()>* m_post_fn = nullptr;
//! Callback functions to run on async thread.
CleanupList m_async_fns;
//! Pipe read handle used to wake up the event loop thread.
int m_wait_fd = -1;
//! Pipe write handle used to wake up the event loop thread.
int m_post_fd = -1;
//! Number of clients holding references to ProxyServerBase objects that
//! reference this event loop.
int m_num_clients = 0;
//! Mutex and condition variable used to post tasks to event loop and async
//! thread.
std::mutex m_mutex;
std::condition_variable m_cv;
//! Capnp IO context.
kj::AsyncIoContext m_io_context;
//! Capnp error handler. Needs to outlive m_task_set.
LoggingErrorHandler m_error_handler{*this};
//! Capnp list of pending promises.
std::unique_ptr<kj::TaskSet> m_task_set;
//! List of connections.
std::list<Connection> m_incoming_connections;
//! External logging callback.
LogFn m_log_fn;
//! External context pointer.
void* m_context;
};
//! Single element task queue used to handle recursive capnp calls. (If server
//! makes an callback into the client in the middle of a request, while client
//! thread is blocked waiting for server response, this is what allows the
//! client to run the request in the same thread, the same way code would run in
//! single process, with the callback sharing same thread stack as the original
//! call.
struct Waiter
{
Waiter() = default;
template <typename Fn>
void post(Fn&& fn)
{
const std::unique_lock<std::mutex> lock(m_mutex);
assert(!m_fn);
m_fn = std::move(fn);
m_cv.notify_all();
}
template <class Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred)
{
m_cv.wait(lock, [&] {
// Important for this to be "while (m_fn)", not "if (m_fn)" to avoid
// a lost-wakeup bug. A new m_fn and m_cv notification might be sent
// after the fn() call and before the lock.lock() call in this loop
// in the case where a capnp response is sent and a brand new
// request is immediately received.
while (m_fn) {
auto fn = std::move(m_fn);
m_fn = nullptr;
lock.unlock();
fn();
lock.lock();
}
const bool done = pred();
return done;
});
}
std::mutex m_mutex;
std::condition_variable m_cv;
std::function<void()> m_fn;
};
//! Object holding network & rpc state associated with either an incoming server
//! connection, or an outgoing client connection. It must be created and destroyed
//! on the event loop thread.
//! In addition to Cap'n Proto state, it also holds lists of callbacks to run
//! when the connection is closed.
class Connection
{
public:
Connection(EventLoop& loop, kj::Own<kj::AsyncIoStream>&& stream_)
: m_loop(loop), m_stream(kj::mv(stream_)),
m_network(*m_stream, ::capnp::rpc::twoparty::Side::CLIENT, ::capnp::ReaderOptions()),
m_rpc_system(::capnp::makeRpcClient(m_network))
{
std::unique_lock<std::mutex> lock(m_loop.m_mutex);
m_loop.addClient(lock);
}
Connection(EventLoop& loop,
kj::Own<kj::AsyncIoStream>&& stream_,
const std::function<::capnp::Capability::Client(Connection&)>& make_client)
: m_loop(loop), m_stream(kj::mv(stream_)),
m_network(*m_stream, ::capnp::rpc::twoparty::Side::SERVER, ::capnp::ReaderOptions()),
m_rpc_system(::capnp::makeRpcServer(m_network, make_client(*this)))
{
std::unique_lock<std::mutex> lock(m_loop.m_mutex);
m_loop.addClient(lock);
}
//! Run cleanup functions. Must be called from the event loop thread. First
//! calls synchronous cleanup functions while blocked (to free capnp
//! Capability::Client handles owned by ProxyClient objects), then schedules
//! asynchronous cleanup functions to run in a worker thread (to run
//! destructors of m_impl instances owned by ProxyServer objects).
~Connection();
//! Register synchronous cleanup function to run on event loop thread (with
//! access to capnp thread local variables) when disconnect() is called.
//! any new i/o.
CleanupIt addSyncCleanup(std::function<void()> fn);
void removeSyncCleanup(CleanupIt it);
//! Register asynchronous cleanup function to run on worker thread when
//! disconnect() is called.
void addAsyncCleanup(std::function<void()> fn);
//! Add disconnect handler.
template <typename F>
void onDisconnect(F&& f)
{
// Add disconnect handler to local TaskSet to ensure it is cancelled and
// will never run after connection object is destroyed. But when disconnect
// handler fires, do not call the function f right away, instead add it
// to the EventLoop TaskSet to avoid "Promise callback destroyed itself"
// error in cases where f deletes this Connection object.
m_on_disconnect.add(m_network.onDisconnect().then(
[f = std::move(f), this]() mutable { m_loop.m_task_set->add(kj::evalLater(kj::mv(f))); }));
}
EventLoop& m_loop;
kj::Own<kj::AsyncIoStream> m_stream;
LoggingErrorHandler m_error_handler{m_loop};
kj::TaskSet m_on_disconnect{m_error_handler};
::capnp::TwoPartyVatNetwork m_network;
std::optional<::capnp::RpcSystem<::capnp::rpc::twoparty::VatId>> m_rpc_system;
// ThreadMap interface client, used to create a remote server thread when an
// client IPC call is being made for the first time from a new thread.
ThreadMap::Client m_thread_map{nullptr};
//! Collection of server-side IPC worker threads (ProxyServer<Thread> objects previously returned by
//! ThreadMap.makeThread) used to service requests to clients.
::capnp::CapabilityServerSet<Thread> m_threads;
//! Cleanup functions to run if connection is broken unexpectedly.
//! Lists will be empty if all ProxyClient and ProxyServer objects are
//! destroyed cleanly before the connection is destroyed.
CleanupList m_sync_cleanup_fns;
CleanupList m_async_cleanup_fns;
};
//! Vat id for server side of connection. Required argument to RpcSystem::bootStrap()
//!
//! "Vat" is Cap'n Proto nomenclature for a host of various objects that facilitates
//! bidirectional communication with other vats; it is often but not always 1-1 with
//! processes. Cap'n Proto doesn't reference clients or servers per se; instead everything
//! is just a vat.
//!
//! See also: https://github.com/capnproto/capnproto/blob/9021f0c722b36cb11e3690b0860939255ebad39c/c%2B%2B/src/capnp/rpc.capnp#L42-L56
struct ServerVatId
{
::capnp::word scratch[4]{};
::capnp::MallocMessageBuilder message{scratch};
::capnp::rpc::twoparty::VatId::Builder vat_id{message.getRoot<::capnp::rpc::twoparty::VatId>()};
ServerVatId() { vat_id.setSide(::capnp::rpc::twoparty::Side::SERVER); }
};
template <typename Interface, typename Impl>
ProxyClientBase<Interface, Impl>::ProxyClientBase(typename Interface::Client client,
Connection* connection,
bool destroy_connection)
: m_client(std::move(client)), m_context(connection)
{
{
std::unique_lock<std::mutex> lock(m_context.connection->m_loop.m_mutex);
m_context.connection->m_loop.addClient(lock);
}
// Handler for the connection getting destroyed before this client object.
auto cleanup_it = m_context.connection->addSyncCleanup([this]() {
// Release client capability by move-assigning to temporary.
{
typename Interface::Client(std::move(m_client));
}
{
std::unique_lock<std::mutex> lock(m_context.connection->m_loop.m_mutex);
m_context.connection->m_loop.removeClient(lock);
}
m_context.connection = nullptr;
});
// Two shutdown sequences are supported:
//
// - A normal sequence where client proxy objects are deleted by external
// code that no longer needs them
//
// - A garbage collection sequence where the connection or event loop shuts
// down while external code is still holding client references.
//
// The first case is handled here when m_context.connection is not null. The
// second case is handled by the cleanup function, which sets m_context.connection to
// null so nothing happens here.
m_context.cleanup_fns.emplace_front([this, destroy_connection, cleanup_it]{
if (m_context.connection) {
// Remove cleanup callback so it doesn't run and try to access
// this object after it's already destroyed.
m_context.connection->removeSyncCleanup(cleanup_it);
// If the capnp interface defines a destroy method, call it to destroy
// the remote object, waiting for it to be deleted server side. If the
// capnp interface does not define a destroy method, this will just call
// an empty stub defined in the ProxyClientBase class and do nothing.
Sub::destroy(*this);
// FIXME: Could just invoke removed addCleanup fn here instead of duplicating code
m_context.connection->m_loop.sync([&]() {
// Release client capability by move-assigning to temporary.
{
typename Interface::Client(std::move(m_client));
}
{
std::unique_lock<std::mutex> lock(m_context.connection->m_loop.m_mutex);
m_context.connection->m_loop.removeClient(lock);
}
if (destroy_connection) {
delete m_context.connection;
m_context.connection = nullptr;
}
});
}
});
Sub::construct(*this);
}
template <typename Interface, typename Impl>
ProxyClientBase<Interface, Impl>::~ProxyClientBase() noexcept
{
CleanupRun(m_context.cleanup_fns);
}
template <typename Interface, typename Impl>
ProxyServerBase<Interface, Impl>::ProxyServerBase(std::shared_ptr<Impl> impl, Connection& connection)
: m_impl(std::move(impl)), m_context(&connection)
{
assert(m_impl);
std::unique_lock<std::mutex> lock(m_context.connection->m_loop.m_mutex);
m_context.connection->m_loop.addClient(lock);
}
//! ProxyServer destructor, called from the EventLoop thread by Cap'n Proto
//! garbage collection code after there are no more references to this object.
template <typename Interface, typename Impl>
ProxyServerBase<Interface, Impl>::~ProxyServerBase()
{
if (m_impl) {
// If impl is non-null at this point, it means no client is waiting for
// the m_impl server object to be destroyed synchronously. This can
// happen either if the interface did not define a "destroy" method (see
// invokeDestroy method below), or if a destroy method was defined, but
// the connection was broken before it could be called.
//
// In either case, be conservative and run the cleanup on an
// asynchronous thread, to avoid destructors or cleanup functions
// blocking or deadlocking the current EventLoop thread, since they
// could be making IPC calls.
//
// Technically this is a little too conservative since if the interface
// defines a "destroy" method, but the destroy method does not accept a
// Context parameter specifying a worker thread, the cleanup method
// would run on the EventLoop thread normally (when connection is
// unbroken), but will not run on the EventLoop thread now (when
// connection is broken). Probably some refactoring of the destructor
// and invokeDestroy function is possible to make this cleaner and more
// consistent.
m_context.connection->addAsyncCleanup([impl=std::move(m_impl), fns=std::move(m_context.cleanup_fns)]() mutable {
impl.reset();
CleanupRun(fns);
});
}
assert(m_context.cleanup_fns.empty());
std::unique_lock<std::mutex> lock(m_context.connection->m_loop.m_mutex);
m_context.connection->m_loop.removeClient(lock);
}
//! If the capnp interface defined a special "destroy" method, as described the
//! ProxyClientBase class, this method will be called and synchronously destroy
//! m_impl before returning to the client.
//!
//! If the capnp interface does not define a "destroy" method, this will never
//! be called and the ~ProxyServerBase destructor will be responsible for
//! deleting m_impl asynchronously, whenever the ProxyServer object gets garbage
//! collected by Cap'n Proto.
//!
//! This method is called in the same way other proxy server methods are called,
//! via the serverInvoke function. Basically serverInvoke just calls this as a
//! substitute for a non-existent m_impl->destroy() method. If the destroy
//! method has any parameters or return values they will be handled in the
//! normal way by PassField/ReadField/BuildField functions. Particularly if a
//! Context.thread parameter was passed, this method will run on the worker
//! thread specified by the client. Otherwise it will run on the EventLoop
//! thread, like other server methods without an assigned thread.
template <typename Interface, typename Impl>
void ProxyServerBase<Interface, Impl>::invokeDestroy()
{
m_impl.reset();
CleanupRun(m_context.cleanup_fns);
}
using ConnThreads = std::map<Connection*, ProxyClient<Thread>>;
using ConnThread = ConnThreads::iterator;
// Retrieve ProxyClient<Thread> object associated with this connection from a
// map, or create a new one and insert it into the map. Return map iterator and
// inserted bool.
std::tuple<ConnThread, bool> SetThread(ConnThreads& threads, std::mutex& mutex, Connection* connection, const std::function<Thread::Client()>& make_thread);
struct ThreadContext
{
//! Identifying string for debug.
std::string thread_name;
//! Waiter object used to allow client threads blocked waiting for a server
//! response to execute callbacks made from the client's corresponding
//! server thread.
std::unique_ptr<Waiter> waiter = nullptr;
//! When client is making a request to a server, this is the
//! `callbackThread` argument it passes in the request, used by the server
//! in case it needs to make callbacks into the client that need to execute
//! while the client is waiting. This will be set to a local thread object.
ConnThreads callback_threads;
//! When client is making a request to a server, this is the `thread`
//! argument it passes in the request, used to control which thread on
//! server will be responsible for executing it. If client call is being
//! made from a local thread, this will be a remote thread object returned
//! by makeThread. If a client call is being made from a thread currently
//! handling a server request, this will be set to the `callbackThread`
//! request thread argument passed in that request.
ConnThreads request_threads;
//! Whether this thread is a capnp event loop thread. Not really used except
//! to assert false if there's an attempt to execute a blocking operation
//! which could deadlock the thread.
bool loop_thread = false;
};
//! Given stream file descriptor, make a new ProxyClient object to send requests
//! over the stream. Also create a new Connection object embedded in the
//! client that is freed when the client is closed.
template <typename InitInterface>
std::unique_ptr<ProxyClient<InitInterface>> ConnectStream(EventLoop& loop, int fd)
{
typename InitInterface::Client init_client(nullptr);
std::unique_ptr<Connection> connection;
loop.sync([&] {
auto stream =
loop.m_io_context.lowLevelProvider->wrapSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP);
connection = std::make_unique<Connection>(loop, kj::mv(stream));
init_client = connection->m_rpc_system->bootstrap(ServerVatId().vat_id).castAs<InitInterface>();
Connection* connection_ptr = connection.get();
connection->onDisconnect([&loop, connection_ptr] {
loop.log() << "IPC client: unexpected network disconnect.";
delete connection_ptr;
});
});
return std::make_unique<ProxyClient<InitInterface>>(
kj::mv(init_client), connection.release(), /* destroy_connection= */ true);
}
//! Given stream and init objects, construct a new ProxyServer object that
//! handles requests from the stream by calling the init object. Embed the
//! ProxyServer in a Connection object that is stored and erased if
//! disconnected. This should be called from the event loop thread.
template <typename InitInterface, typename InitImpl>
void _Serve(EventLoop& loop, kj::Own<kj::AsyncIoStream>&& stream, InitImpl& init)
{
loop.m_incoming_connections.emplace_front(loop, kj::mv(stream), [&](Connection& connection) {
// Disable deleter so proxy server object doesn't attempt to delete the
// init implementation when the proxy client is destroyed or
// disconnected.
return kj::heap<ProxyServer<InitInterface>>(std::shared_ptr<InitImpl>(&init, [](InitImpl*){}), connection);
});
auto it = loop.m_incoming_connections.begin();
it->onDisconnect([&loop, it] {
loop.log() << "IPC server: socket disconnected.";
loop.m_incoming_connections.erase(it);
});
}
//! Given connection receiver and an init object, handle incoming connections by
//! calling _Serve, to create ProxyServer objects and forward requests to the
//! init object.
template <typename InitInterface, typename InitImpl>
void _Listen(EventLoop& loop, kj::Own<kj::ConnectionReceiver>&& listener, InitImpl& init)
{
auto* ptr = listener.get();
loop.m_task_set->add(ptr->accept().then(
[&loop, &init, listener = kj::mv(listener)](kj::Own<kj::AsyncIoStream>&& stream) mutable {
_Serve<InitInterface>(loop, kj::mv(stream), init);
_Listen<InitInterface>(loop, kj::mv(listener), init);
}));
}
//! Given stream file descriptor and an init object, handle requests on the
//! stream by calling methods on the Init object.
template <typename InitInterface, typename InitImpl>
void ServeStream(EventLoop& loop, int fd, InitImpl& init)
{
_Serve<InitInterface>(
loop, loop.m_io_context.lowLevelProvider->wrapSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP), init);
}
//! Given listening socket file descriptor and an init object, handle incoming
//! connections and requests by calling methods on the Init object.
template <typename InitInterface, typename InitImpl>
void ListenConnections(EventLoop& loop, int fd, InitImpl& init)
{
loop.sync([&]() {
_Listen<InitInterface>(loop,
loop.m_io_context.lowLevelProvider->wrapListenSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP),
init);
});
}
extern thread_local ThreadContext g_thread_context;
} // namespace mp
#endif // MP_PROXY_IO_H

727
include/mp/proxy-types.h Normal file
View File

@@ -0,0 +1,727 @@
// 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.connection->m_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.connection->m_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 (!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<Waiter>();
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<decltype(request)>;
using FieldList = typename ProxyClientMethodTraits<typename Request::Params>::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<typename Request::Params>() << " " << LogEscape(request.toString());
proxy_client.m_context.connection->m_loop.m_task_set->add(request.send().then(
[&](::capnp::Response<typename Request::Results>&& response) {
proxy_client.m_context.connection->m_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) {
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<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.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 <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.connection->m_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.connection->m_loop.log() << "IPC server send response #" << req << " " << TypeName<Results>()
<< " " << 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<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

65
include/mp/proxy.capnp Normal file
View File

@@ -0,0 +1,65 @@
# 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.
@0xcc316e3f71a040fb;
using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("mp");
annotation include(file): Text;
annotation includeTypes(file): Text;
# Extra include paths to add to generated files.
annotation wrap(interface, struct): Text;
# Wrap capnp interface generating ProxyClient / ProxyServer C++ classes that
# forward calls to a C++ interface with same methods and parameters. Text
# string should be the name of the C++ interface.
# If applied to struct rather than an interface, this will generate a ProxyType
# struct with get methods for introspection and copying fields between C++ and
# capnp structs.
annotation count(param, struct, interface): Int32;
# Indicate how many C++ method parameters there are corresponding to one capnp
# parameter (default is 1). If not 1, multiple C++ method arguments will be
# condensed into a single capnp parameter by the client and then expanded by
# the server by CustomReadField/CustomBuildField overloads which need to be
# provided separately. An example would be a capnp Text parameter initialized
# from C++ char* and size arguments. Can be 0 to fill an implicit capnp
# parameter from client or server side context. If annotation is applied to an
# interface or struct type it will apply to all parameters of that type.
annotation exception(param): Text;
# Indicate that a result parameter corresponds to a C++ exception. Text string
# should be the name of a C++ exception type that the generated server class
# will catch and the client class will rethrow.
annotation name(field, method): Text;
# Name of the C++ method or field corresponding to a capnp method or field.
annotation skip(field): Void;
# Synonym for count(0).
interface ThreadMap $count(0) {
# Interface letting clients control which thread a method call should
# execute on. Clients create and name threads and pass the thread handle as
# a call parameter.
makeThread @0 (name :Text) -> (result :Thread);
}
interface Thread {
# Thread handle returned by makeThread corresponding to one server thread.
getName @0 () -> (result: Text);
}
struct Context $count(0) {
# Execution context passed as a parameter from the client class to the server class.
thread @0 : Thread;
# Handle of the server thread the current method call should execute on.
callbackThread @1 : Thread;
# Handle of the client thread that is calling the current method, and that
# any callbacks made by the server thread should be made on.
}

299
include/mp/proxy.h Normal file
View File

@@ -0,0 +1,299 @@
// 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_H
#define MP_PROXY_H
#include <mp/util.h>
#include <array>
#include <functional>
#include <list>
#include <stddef.h>
#include <tuple>
#include <type_traits>
#include <utility>
namespace mp {
class Connection;
class EventLoop;
//! Mapping from capnp interface type to proxy client implementation (specializations are generated by
//! proxy-codegen.cpp).
template <typename Interface>
struct ProxyClient;
//! Mapping from capnp interface type to proxy server implementation (specializations are generated by
//! proxy-codegen.cpp).
template <typename Interface>
struct ProxyServer;
//! Mapping from capnp method params type to method traits (specializations are generated by proxy-codegen.cpp).
template <typename Params>
struct ProxyMethod;
//! Mapping from capnp struct type to struct traits (specializations are generated by proxy-codegen.cpp).
template <typename Struct>
struct ProxyStruct;
//! Mapping from local c++ type to capnp type and traits (specializations are generated by proxy-codegen.cpp).
template <typename Type>
struct ProxyType;
using CleanupList = std::list<std::function<void()>>;
using CleanupIt = typename CleanupList::iterator;
inline void CleanupRun(CleanupList& fns) {
while (!fns.empty()) {
auto fn = std::move(fns.front());
fns.pop_front();
fn();
}
}
//! Context data associated with proxy client and server classes.
struct ProxyContext
{
Connection* connection;
CleanupList cleanup_fns;
ProxyContext(Connection* connection) : connection(connection) {}
};
//! Base class for generated ProxyClient classes that implement a C++ interface
//! and forward calls to a capnp interface.
template <typename Interface_, typename Impl_>
class ProxyClientBase : public Impl_
{
public:
using Interface = Interface_;
using Impl = Impl_;
using Sub = ProxyClient<Interface>;
using Super = ProxyClientBase<Interface, Impl>;
ProxyClientBase(typename Interface::Client client, Connection* connection, bool destroy_connection);
~ProxyClientBase() noexcept;
// construct/destroy methods called during client construction/destruction
// that can optionally be defined in capnp interfaces to invoke code on the
// server when proxy client objects are created and destroyed.
//
// The construct() method is not generally very useful, but can be used to
// run custom code on the server automatically when a ProxyClient client is
// constructed. The only current use is adding a construct method to Init
// interfaces that is called automatically on construction, so client and
// server exchange ThreadMap references and set Connection::m_thread_map
// values as soon as the Init client is created.
//
// construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap: Proxy.ThreadMap);
//
// But construct() is not necessary for this, thread maps could be passed
// through a normal method that is just called explicitly rather than
// implicitly.
//
// The destroy() method is more generally useful than construct(), because
// it ensures that the server object will be destroyed synchronously before
// the client destructor returns, instead of asynchronously at some
// unpredictable time after the client object is already destroyed and
// client code has moved on. If the destroy method accepts a Context
// parameter like:
//
// destroy @0 (context: Proxy.Context) -> ();
//
// then it will also ensure that the destructor runs on the same thread the
// client used to make other RPC calls, instead of running on the server
// EventLoop thread and possibly blocking it.
static void construct(Super&) {}
static void destroy(Super&) {}
typename Interface::Client m_client;
ProxyContext m_context;
};
//! Customizable (through template specialization) base class used in generated ProxyClient implementations from
//! proxy-codegen.cpp.
template <typename Interface, typename Impl>
class ProxyClientCustom : public ProxyClientBase<Interface, Impl>
{
using ProxyClientBase<Interface, Impl>::ProxyClientBase;
};
//! Base class for generated ProxyServer classes that implement capnp server
//! methods and forward calls to a wrapped c++ implementation class.
template <typename Interface_, typename Impl_>
struct ProxyServerBase : public virtual Interface_::Server
{
public:
using Interface = Interface_;
using Impl = Impl_;
ProxyServerBase(std::shared_ptr<Impl> impl, Connection& connection);
virtual ~ProxyServerBase();
void invokeDestroy();
/**
* Implementation pointer that may or may not be owned and deleted when this
* capnp server goes out of scope. It is owned for servers created to wrap
* unique_ptr<Impl> method arguments, but unowned for servers created to
* wrap Impl& method arguments.
*
* In the case of Impl& arguments, custom code is required on other side of
* the connection to delete the capnp client & server objects since native
* code on that side of the connection will just be taking a plain reference
* rather than a pointer, so won't be able to do its own cleanup. Right now
* this is implemented with addCloseHook callbacks to delete clients at
* appropriate times depending on semantics of the particular method being
* wrapped. */
std::shared_ptr<Impl> m_impl;
ProxyContext m_context;
};
//! Customizable (through template specialization) base class which ProxyServer
//! classes produced by generated code will inherit from. The default
//! specialization of this class just inherits from ProxyServerBase, but custom
//! specializations can be defined to control ProxyServer behavior.
//!
//! Specifically, it can be useful to specialize this class to add additional
//! state to ProxyServer classes, for example to cache state between IPC calls.
//! If this is done, however, care should be taken to ensure that the extra
//! state can be destroyed without blocking, because ProxyServer destructors are
//! called from the EventLoop thread, and if they block, it could deadlock the
//! program. One way to do avoid blocking is to clean up the state by pushing
//! cleanup callbacks to the m_context.cleanup_fns list, which run after the server
//! m_impl object is destroyed on the same thread destroying it (which will
//! either be an IPC worker thread if the ProxyServer is being explicitly
//! destroyed by a client calling a destroy() method with a Context argument and
//! Context.thread value set, or the temporary EventLoop::m_async_thread used to
//! run destructors without blocking the event loop when no-longer used server
//! objects are garbage collected by Cap'n Proto.) Alternately, if cleanup needs
//! to run before m_impl is destroyed, the specialization can override
//! invokeDestroy and destructor methods to do that.
template <typename Interface, typename Impl>
struct ProxyServerCustom : public ProxyServerBase<Interface, Impl>
{
using ProxyServerBase<Interface, Impl>::ProxyServerBase;
};
//! Function traits class used to get method parameter and result types, used in
//! generated ProxyClient and ProxyServer classes produced by gen.cpp to get C++
//! method type information. The generated code accesses these traits via
//! intermediate ProxyClientMethodTraits and ProxyServerMethodTraits classes,
//! which it is possible to specialize to change the way method arguments and
//! return values are handled.
//!
//! Fields of the trait class are:
//!
//! Params - TypeList of C++ ClassName::methodName parameter types
//! Result - Return type of ClassName::method
//! Param<N> - helper to access individual parameters by index number.
//! Fields - helper alias that appends Result type to the Params typelist if
//! it not void.
template <class Fn>
struct FunctionTraits;
//! Specialization of above extracting result and params types assuming the
//! template argument is a pointer-to-method type,
//! decltype(&ClassName::methodName)
template <class _Class, class _Result, class... _Params>
struct FunctionTraits<_Result (_Class::*const)(_Params...)>
{
using Params = TypeList<_Params...>;
using Result = _Result;
template <size_t N>
using Param = typename std::tuple_element<N, std::tuple<_Params...>>::type;
using Fields =
std::conditional_t<std::is_same_v<void, Result>, Params, TypeList<_Params..., _Result>>;
};
//! Traits class for a proxy method, providing the same
//! Params/Result/Param/Fields described in the FunctionTraits class above, plus
//! an additional invoke() method that calls the C++ method which is being
//! proxied, forwarding any arguments.
//!
//! The template argument should be the InterfaceName::MethodNameParams class
//! (generated by Cap'n Proto) associated with the method.
//!
//! Note: The class definition here is just the fallback definition used when
//! the other specialization below doesn't match. The fallback is only used for
//! capnp methods which do not have corresponding C++ methods, which in practice
//! is just the two special construct() and destroy() methods described in \ref
//! ProxyClientBase. These methods don't have any C++ parameters or return
//! types, so the trait information below reflects that.
template <typename MethodParams, typename Enable = void>
struct ProxyMethodTraits
{
using Params = TypeList<>;
using Result = void;
using Fields = Params;
template <typename ServerContext>
static void invoke(ServerContext&)
{
}
};
//! Specialization of above for proxy methods that have a
//! ProxyMethod<InterfaceName::MethodNameParams>::impl pointer-to-method
//! constant defined by generated code. This includes all functions defined in
//! the capnp interface except any construct() or destroy() methods, that are
//! assumed not to correspond to real member functions in the C++ class, and
//! will use the fallback traits definition above. The generated code this
//! specialization relies on looks like:
//!
//! struct ProxyMethod<InterfaceName::MethodNameParams>
//! {
//! static constexpr auto impl = &ClassName::methodName;
//! };
template <typename MethodParams>
struct ProxyMethodTraits<MethodParams, Require<decltype(ProxyMethod<MethodParams>::impl)>>
: public FunctionTraits<decltype(ProxyMethod<MethodParams>::impl)>
{
template <typename ServerContext, typename... Args>
static decltype(auto) invoke(ServerContext& server_context, Args&&... args)
{
return (server_context.proxy_server.m_impl.get()->*ProxyMethod<MethodParams>::impl)(std::forward<Args>(args)...);
}
};
//! Customizable (through template specialization) traits class used in generated ProxyClient implementations from
//! proxy-codegen.cpp.
template <typename MethodParams>
struct ProxyClientMethodTraits : public ProxyMethodTraits<MethodParams>
{
};
//! Customizable (through template specialization) traits class used in generated ProxyServer implementations from
//! proxy-codegen.cpp.
template <typename MethodParams>
struct ProxyServerMethodTraits : public ProxyMethodTraits<MethodParams>
{
};
static constexpr int FIELD_IN = 1;
static constexpr int FIELD_OUT = 2;
static constexpr int FIELD_OPTIONAL = 4;
static constexpr int FIELD_REQUESTED = 8;
static constexpr int FIELD_BOXED = 16;
//! Accessor type holding flags that determine how to access a message field.
template <typename Field, int flags>
struct Accessor : public Field
{
static const bool in = flags & FIELD_IN;
static const bool out = flags & FIELD_OUT;
static const bool optional = flags & FIELD_OPTIONAL;
static const bool requested = flags & FIELD_REQUESTED;
static const bool boxed = flags & FIELD_BOXED;
};
//! Wrapper around std::function for passing std::function objects between client and servers.
template <typename Fn>
class ProxyCallback;
//! Specialization of above to separate Result and Arg types.
template <typename Result, typename... Args>
class ProxyCallback<std::function<Result(Args...)>>
{
public:
virtual Result call(Args&&... args) = 0;
};
} // namespace mp
#endif // MP_PROXY_H

36
include/mp/type-char.h Normal file
View File

@@ -0,0 +1,36 @@
// Copyright (c) 2025 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_TYPE_CHAR_H
#define MP_PROXY_TYPE_CHAR_H
#include <mp/util.h>
namespace mp {
template <typename Output, size_t size>
void CustomBuildField(TypeList<const unsigned char*>,
Priority<3>,
InvokeContext& invoke_context,
const unsigned char (&value)[size],
Output&& output)
{
auto result = output.init(size);
memcpy(result.begin(), value, size);
}
template <size_t size, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<unsigned char[size]>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
auto data = input.get();
memcpy(value, data.begin(), size);
});
}
} // namespace mp
#endif // MP_PROXY_TYPE_CHAR_H

34
include/mp/type-chrono.h Normal file
View File

@@ -0,0 +1,34 @@
// Copyright (c) 2025 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_TYPE_CHRONO_H
#define MP_PROXY_TYPE_CHRONO_H
#include <mp/util.h>
#include <chrono>
namespace mp {
//! Overload CustomBuildField and CustomReadField to serialize std::chrono
//! parameters and return values as numbers.
template <class Rep, class Period, typename Value, typename Output>
void CustomBuildField(TypeList<std::chrono::duration<Rep, Period>>, Priority<1>, InvokeContext& invoke_context, Value&& value,
Output&& output)
{
static_assert(std::numeric_limits<decltype(output.get())>::lowest() <= std::numeric_limits<Rep>::lowest(),
"capnp type does not have enough range to hold lowest std::chrono::duration value");
static_assert(std::numeric_limits<decltype(output.get())>::max() >= std::numeric_limits<Rep>::max(),
"capnp type does not have enough range to hold highest std::chrono::duration value");
output.set(value.count());
}
template <class Rep, class Period, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::chrono::duration<Rep, Period>>, Priority<1>, InvokeContext& invoke_context,
Input&& input, ReadDest&& read_dest)
{
return read_dest.construct(input.get());
}
} // namespace mp
#endif // MP_PROXY_TYPE_CHRONO_H

173
include/mp/type-context.h Normal file
View File

@@ -0,0 +1,173 @@
// Copyright (c) 2025 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_TYPE_CONTEXT_H
#define MP_PROXY_TYPE_CONTEXT_H
#include <mp/proxy-io.h>
#include <mp/util.h>
namespace mp {
template <typename Output>
void CustomBuildField(TypeList<>,
Priority<1>,
ClientInvokeContext& invoke_context,
Output&& output,
typename std::enable_if<std::is_same<decltype(output.get()), Context::Builder>::value>::type* enable = nullptr)
{
auto& connection = invoke_context.connection;
auto& thread_context = invoke_context.thread_context;
// Create local Thread::Server object corresponding to the current thread
// and pass a Thread::Client reference to it in the Context.callbackThread
// field so the function being called can make callbacks to this thread.
// Also store the Thread::Client reference in the callback_threads map so
// future calls over this connection can reuse it.
auto [callback_thread, _]{SetThread(
thread_context.callback_threads, thread_context.waiter->m_mutex, &connection,
[&] { return connection.m_threads.add(kj::heap<ProxyServer<Thread>>(thread_context, std::thread{})); })};
// Call remote ThreadMap.makeThread function so server will create a
// dedicated worker thread to run function calls from this thread. Store the
// Thread::Client reference it returns in the request_threads map.
auto make_request_thread{[&]{
// This code will only run if an IPC client call is being made for the
// first time on this thread. After the first call, subsequent calls
// will use the existing request thread. This code will also never run at
// all if the current thread is a request thread created for a different
// IPC client, because in that case PassField code (below) will have set
// request_thread to point to the calling thread.
auto request = connection.m_thread_map.makeThreadRequest();
request.setName(thread_context.thread_name);
return request.send().getResult(); // Nonblocking due to capnp request pipelining.
}};
auto [request_thread, _1]{SetThread(
thread_context.request_threads, thread_context.waiter->m_mutex,
&connection, make_request_thread)};
auto context = output.init();
context.setThread(request_thread->second.m_client);
context.setCallbackThread(callback_thread->second.m_client);
}
//! PassField override for mp.Context arguments. Return asynchronously and call
//! function on other thread found in context.
template <typename Accessor, typename ServerContext, typename Fn, typename... Args>
auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn& fn, Args&&... args) ->
typename std::enable_if<
std::is_same<decltype(Accessor::get(server_context.call_context.getParams())), Context::Reader>::value,
kj::Promise<typename ServerContext::CallContext>>::type
{
const auto& params = server_context.call_context.getParams();
Context::Reader context_arg = Accessor::get(params);
auto future = kj::newPromiseAndFulfiller<typename ServerContext::CallContext>();
auto& server = server_context.proxy_server;
int req = server_context.req;
auto invoke = MakeAsyncCallable(
[fulfiller = kj::mv(future.fulfiller),
call_context = kj::mv(server_context.call_context), &server, req, fn, args...]() mutable {
const auto& params = call_context.getParams();
Context::Reader context_arg = Accessor::get(params);
ServerContext server_context{server, call_context, req};
bool disconnected{false};
{
// Before invoking the function, store a reference to the
// callbackThread provided by the client in the
// thread_local.request_threads map. This way, if this
// server thread needs to execute any RPCs that call back to
// the client, they will happen on the same client thread
// that is waiting for this function, just like what would
// happen if this were a normal function call made on the
// local stack.
//
// If the request_threads map already has an entry for this
// connection, it will be left unchanged, and it indicates
// that the current thread is an RPC client thread which is
// in the middle of an RPC call, and the current RPC call is
// a nested call from the remote thread handling that RPC
// call. In this case, the callbackThread value should point
// to the same thread already in the map, so there is no
// need to update the map.
auto& thread_context = g_thread_context;
auto& request_threads = thread_context.request_threads;
auto [request_thread, inserted]{SetThread(
request_threads, thread_context.waiter->m_mutex,
server.m_context.connection,
[&] { return context_arg.getCallbackThread(); })};
// If an entry was inserted into the requests_threads map,
// remove it after calling fn.invoke. If an entry was not
// inserted, one already existed, meaning this must be a
// recursive call (IPC call calling back to the caller which
// makes another IPC call), so avoid modifying the map.
const bool erase_thread{inserted};
KJ_DEFER({
std::unique_lock<std::mutex> lock(thread_context.waiter->m_mutex);
// Call erase here with a Connection* argument instead
// of an iterator argument, because the `request_thread`
// iterator may be invalid if the connection is closed
// during this function call. More specifically, the
// iterator may be invalid because SetThread adds a
// cleanup callback to the Connection destructor that
// erases the thread from the map, and also because the
// ProxyServer<Thread> destructor calls
// request_threads.clear().
if (erase_thread) {
disconnected = !request_threads.erase(server.m_context.connection);
} else {
disconnected = !request_threads.count(server.m_context.connection);
}
});
fn.invoke(server_context, args...);
}
if (disconnected) {
// If disconnected is true, the Connection object was
// destroyed during the method call. Deal with this by
// returning without ever fulfilling the promise, which will
// cause the ProxyServer object to leak. This is not ideal,
// but fixing the leak will require nontrivial code changes
// because there is a lot of code assuming ProxyServer
// objects are destroyed before Connection objects.
return;
}
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() {
server.m_context.connection->m_loop.sync([&] {
auto fulfiller_dispose = kj::mv(fulfiller);
fulfiller_dispose->fulfill(kj::mv(call_context));
});
}))
{
server.m_context.connection->m_loop.sync([&]() {
auto fulfiller_dispose = kj::mv(fulfiller);
fulfiller_dispose->reject(kj::mv(*exception));
});
}
});
// Lookup Thread object specified by the client. The specified thread should
// be a local Thread::Server object, but it needs to be looked up
// asynchronously with getLocalServer().
auto thread_client = context_arg.getThread();
return server.m_context.connection->m_threads.getLocalServer(thread_client)
.then([&server, invoke, req](const kj::Maybe<Thread::Server&>& perhaps) {
// Assuming the thread object is found, pass it a pointer to the
// `invoke` lambda above which will invoke the function on that
// thread.
KJ_IF_MAYBE (thread_server, perhaps) {
const auto& thread = static_cast<ProxyServer<Thread>&>(*thread_server);
server.m_context.connection->m_loop.log()
<< "IPC server post request #" << req << " {" << thread.m_thread_context.thread_name << "}";
thread.m_thread_context.waiter->post(std::move(invoke));
} else {
server.m_context.connection->m_loop.log()
<< "IPC server error request #" << req << ", missing thread to execute request";
throw std::runtime_error("invalid thread handle");
}
})
// Wait for the invocation to finish before returning to the caller.
.then([invoke_wait = kj::mv(future.promise)]() mutable { return kj::mv(invoke_wait); });
}
} // namespace mp
#endif // MP_PROXY_TYPE_CONTEXT_H

46
include/mp/type-data.h Normal file
View File

@@ -0,0 +1,46 @@
// Copyright (c) 2025 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_TYPE_DATA_H
#define MP_PROXY_TYPE_DATA_H
#include <mp/util.h>
namespace mp {
template <typename T, typename U>
concept IsSpanOf =
std::convertible_to<T, std::span<const U>> &&
std::constructible_from<T, const U*, const U*>;
template <typename T>
concept IsByteSpan =
IsSpanOf<T, std::byte> ||
IsSpanOf<T, char> ||
IsSpanOf<T, unsigned char> ||
IsSpanOf<T, signed char>;
//! Generic ::capnp::Data field builder for any C++ type that can be converted
//! to a span of bytes, like std::vector<char> or std::array<uint8_t>, or custom
//! blob types like uint256 or PKHash with data() and size() methods pointing to
//! bytes.
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<LocalType>, Priority<2>, InvokeContext& invoke_context, Value&& value, Output&& output)
requires (std::is_same_v<decltype(output.get()), ::capnp::Data::Builder> && IsByteSpan<LocalType>)
{
auto data = std::span{value};
auto result = output.init(data.size());
memcpy(result.begin(), data.data(), data.size());
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<LocalType>, Priority<2>, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest)
requires (std::is_same_v<decltype(input.get()), ::capnp::Data::Reader> && IsByteSpan<LocalType>)
{
using ByteType = decltype(std::span{std::declval<LocalType>().begin(), std::declval<LocalType>().end()})::element_type;
const kj::byte *begin{input.get().begin()}, *end{input.get().end()};
return read_dest.construct(reinterpret_cast<const ByteType*>(begin), reinterpret_cast<const ByteType*>(end));
}
} // namespace mp
#endif // MP_PROXY_TYPE_DATA_H

38
include/mp/type-decay.h Normal file
View File

@@ -0,0 +1,38 @@
// Copyright (c) 2025 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_TYPE_DECAY_H
#define MP_PROXY_TYPE_DECAY_H
#include <mp/util.h>
namespace mp {
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<const LocalType>,
Priority<0>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
BuildField(TypeList<LocalType>(), invoke_context, output, std::forward<Value>(value));
}
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<LocalType&>, Priority<0>, InvokeContext& invoke_context, Value&& value, Output&& output)
{
BuildField(TypeList<LocalType>(), invoke_context, output, std::forward<Value>(value));
}
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<LocalType&&>,
Priority<0>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
BuildField(TypeList<LocalType>(), invoke_context, output, std::forward<Value>(value));
}
} // namespace mp
#endif // MP_PROXY_TYPE_DECAY_H

View File

@@ -0,0 +1,22 @@
// Copyright (c) 2025 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_TYPE_EXCEPTION_H
#define MP_PROXY_TYPE_EXCEPTION_H
#include <mp/util.h>
namespace mp {
template <typename Output>
void CustomBuildField(TypeList<std::exception>,
Priority<1>,
InvokeContext& invoke_context,
const std::exception& value,
Output&& output)
{
BuildField(TypeList<std::string>(), invoke_context, output, std::string(value.what()));
}
} // namespace mp
#endif // MP_PROXY_TYPE_EXCEPTION_H

View File

@@ -0,0 +1,67 @@
// Copyright (c) 2025 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_TYPE_FUNCTION_H
#define MP_PROXY_TYPE_FUNCTION_H
#include <mp/util.h>
namespace mp {
//! Adapter to convert ProxyCallback object call to function object call.
template <typename Result, typename... Args>
class ProxyCallbackImpl final : public ProxyCallback<std::function<Result(Args...)>>
{
using Fn = std::function<Result(Args...)>;
Fn m_fn;
public:
ProxyCallbackImpl(Fn fn) : m_fn(std::move(fn)) {}
Result call(Args&&... args) override { return m_fn(std::forward<Args>(args)...); }
};
template <typename Value, typename FnR, typename... FnParams, typename Output>
void CustomBuildField(TypeList<std::function<FnR(FnParams...)>>,
Priority<1>,
InvokeContext& invoke_context,
Value& value,
Output&& output)
{
if (value) {
using Interface = typename decltype(output.get())::Calls;
using Callback = ProxyCallbackImpl<FnR, FnParams...>;
output.set(kj::heap<ProxyServer<Interface>>(
std::make_shared<Callback>(std::forward<Value>(value)), invoke_context.connection));
}
}
// ProxyCallFn class is needed because c++11 doesn't support auto lambda parameters.
// It's equivalent c++14: [invoke_context](auto&& params) {
// invoke_context->call(std::forward<decltype(params)>(params)...)
template <typename InvokeContext>
struct ProxyCallFn
{
InvokeContext m_proxy;
template <typename... CallParams>
decltype(auto) operator()(CallParams&&... params) { return this->m_proxy->call(std::forward<CallParams>(params)...); }
};
template <typename FnR, typename... FnParams, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::function<FnR(FnParams...)>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
if (input.has()) {
using Interface = typename Decay<decltype(input.get())>::Calls;
auto client = std::make_shared<ProxyClient<Interface>>(
input.get(), &invoke_context.connection, /* destroy_connection= */ false);
return read_dest.construct(ProxyCallFn<decltype(client)>{std::move(client)});
}
return read_dest.construct();
};
} // namespace mp
#endif // MP_PROXY_TYPE_FUNCTION_H

112
include/mp/type-interface.h Normal file
View File

@@ -0,0 +1,112 @@
// Copyright (c) 2025 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_TYPE_INTERFACE_H
#define MP_PROXY_TYPE_INTERFACE_H
#include <mp/util.h>
namespace mp {
template <typename Interface, typename Impl>
kj::Own<typename Interface::Server> MakeProxyServer(InvokeContext& context, std::shared_ptr<Impl> impl)
{
return kj::heap<ProxyServer<Interface>>(std::move(impl), context.connection);
}
template <typename Interface, typename Impl>
kj::Own<typename Interface::Server> CustomMakeProxyServer(InvokeContext& context, std::shared_ptr<Impl>&& impl)
{
return MakeProxyServer<Interface, Impl>(context, std::move(impl));
}
template <typename Impl, typename Value, typename Output>
void CustomBuildField(TypeList<std::unique_ptr<Impl>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output,
typename Decay<decltype(output.get())>::Calls* enable = nullptr)
{
if (value) {
using Interface = typename decltype(output.get())::Calls;
output.set(CustomMakeProxyServer<Interface, Impl>(invoke_context, std::shared_ptr<Impl>(value.release())));
}
}
template <typename Impl, typename Value, typename Output>
void CustomBuildField(TypeList<std::shared_ptr<Impl>>,
Priority<2>,
InvokeContext& invoke_context,
Value&& value,
Output&& output,
typename Decay<decltype(output.get())>::Calls* enable = nullptr)
{
if (value) {
using Interface = typename decltype(output.get())::Calls;
output.set(CustomMakeProxyServer<Interface, Impl>(invoke_context, std::move(value)));
}
}
template <typename Impl, typename Output>
void CustomBuildField(TypeList<Impl&>,
Priority<1>,
InvokeContext& invoke_context,
Impl& value,
Output&& output,
typename decltype(output.get())::Calls* enable = nullptr)
{
// Disable deleter so proxy server object doesn't attempt to delete the
// wrapped implementation when the proxy client is destroyed or
// disconnected.
using Interface = typename decltype(output.get())::Calls;
output.set(CustomMakeProxyServer<Interface, Impl>(invoke_context, std::shared_ptr<Impl>(&value, [](Impl*){})));
}
template <typename Interface, typename Impl>
std::unique_ptr<Impl> MakeProxyClient(InvokeContext& context, typename Interface::Client&& client)
{
return std::make_unique<ProxyClient<Interface>>(
std::move(client), &context.connection, /* destroy_connection= */ false);
}
template <typename Interface, typename Impl>
std::unique_ptr<Impl> CustomMakeProxyClient(InvokeContext& context, typename Interface::Client&& client)
{
return MakeProxyClient<Interface, Impl>(context, kj::mv(client));
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::unique_ptr<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest,
typename Decay<decltype(input.get())>::Calls* enable = nullptr)
{
using Interface = typename Decay<decltype(input.get())>::Calls;
if (input.has()) {
return read_dest.construct(
CustomMakeProxyClient<Interface, LocalType>(invoke_context, std::move(input.get())));
}
return read_dest.construct();
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::shared_ptr<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest,
typename Decay<decltype(input.get())>::Calls* enable = nullptr)
{
using Interface = typename Decay<decltype(input.get())>::Calls;
if (input.has()) {
return read_dest.construct(
CustomMakeProxyClient<Interface, LocalType>(invoke_context, std::move(input.get())));
}
return read_dest.construct();
}
} // namespace mp
#endif // MP_PROXY_TYPE_INTERFACE_H

52
include/mp/type-map.h Normal file
View File

@@ -0,0 +1,52 @@
// Copyright (c) 2025 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_TYPE_MAP_H
#define MP_PROXY_TYPE_MAP_H
#include <mp/proxy-types.h>
#include <mp/type-pair.h>
#include <mp/util.h>
namespace mp {
template <typename KeyLocalType, typename ValueLocalType, typename Value, typename Output>
void CustomBuildField(TypeList<std::map<KeyLocalType, ValueLocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
// FIXME dededup with vector handler above
auto list = output.init(value.size());
size_t i = 0;
for (const auto& elem : value) {
BuildField(TypeList<std::pair<KeyLocalType, ValueLocalType>>(), invoke_context,
ListOutput<typename decltype(list)::Builds>(list, i), elem);
++i;
}
}
template <typename KeyLocalType, typename ValueLocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::map<KeyLocalType, ValueLocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
auto data = input.get();
value.clear();
for (auto item : data) {
ReadField(TypeList<std::pair<const KeyLocalType, ValueLocalType>>(), invoke_context,
Make<ValueField>(item),
ReadDestEmplace(
TypeList<std::pair<const KeyLocalType, ValueLocalType>>(), [&](auto&&... args) -> auto& {
return *value.emplace(std::forward<decltype(args)>(args)...).first;
}));
}
});
}
} // namespace mp
#endif // MP_PROXY_TYPE_MAP_H

64
include/mp/type-message.h Normal file
View File

@@ -0,0 +1,64 @@
// Copyright (c) 2025 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_TYPE_MESSAGE_H
#define MP_PROXY_TYPE_MESSAGE_H
#include <mp/util.h>
namespace mp {
//! Overload CustomBuildField to serialize objects that have CustomBuildMessage
//! overloads. Defining a CustomBuildMessage overload is simpler than defining a
//! CustomBuildField overload because it only requires defining a normal
//! function, not a template function, but less flexible.
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<LocalType>, Priority<2>, InvokeContext& invoke_context, Value&& value, Output&& output,
decltype(CustomBuildMessage(invoke_context, value, std::move(output.get())))* enable = nullptr)
{
CustomBuildMessage(invoke_context, value, std::move(output.init()));
}
//! Overload CustomReadField to serialize objects that have CustomReadMessage
//! overloads. Defining a CustomReadMessage overload is simpler than defining a
//! CustomReadField overload because it only requires defining a normal
//! function, not a template function, but less flexible.
template <typename LocalType, typename Reader, typename ReadDest>
decltype(auto) CustomReadField(TypeList<LocalType>, Priority<2>, InvokeContext& invoke_context, Reader&& reader,
ReadDest&& read_dest,
decltype(CustomReadMessage(invoke_context, reader.get(),
std::declval<LocalType&>()))* enable = nullptr)
{
return read_dest.update([&](auto& value) { if (reader.has()) CustomReadMessage(invoke_context, reader.get(), value); });
}
//! Helper for CustomPassField below. Call Accessor::init method if it has one,
//! otherwise do nothing.
template <typename Accessor, typename Message>
decltype(auto) MaybeInit(Message&& message, decltype(Accessor::get(message))* enable = nullptr)
{
return Accessor::init(message);
}
template <typename Accessor>
::capnp::Void MaybeInit(...)
{
return {};
}
//! Overload CustomPassField to serialize objects that have CustomPassMessage
//! overloads. Defining a CustomPassMessage overload is simpler than defining a
//! CustomPassField overload because it only requires defining a normal
//! function, not a template function, but less flexible.
template <typename Accessor, typename... LocalTypes, typename ServerContext, typename Fn, typename... Args>
auto CustomPassField(TypeList<LocalTypes...>, ServerContext& server_context, Fn&& fn, Args&&... args)
-> decltype(CustomPassMessage(server_context, MaybeGet<Accessor>(server_context.call_context.getParams()),
MaybeGet<Accessor>(server_context.call_context.getResults()), nullptr))
{
CustomPassMessage(server_context, MaybeGet<Accessor>(server_context.call_context.getParams()),
MaybeInit<Accessor>(server_context.call_context.getResults()),
[&](LocalTypes... param) { fn.invoke(server_context, std::forward<Args>(args)..., param...); });
}
} // namespace mp
#endif // MP_PROXY_TYPE_MESSAGE_H

87
include/mp/type-number.h Normal file
View File

@@ -0,0 +1,87 @@
// Copyright (c) 2025 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_TYPE_NUMBER_H
#define MP_PROXY_TYPE_NUMBER_H
#include <mp/util.h>
namespace mp {
template <typename LocalType, typename Value>
LocalType BuildPrimitive(InvokeContext& invoke_context,
const Value& value,
TypeList<LocalType>,
typename std::enable_if<std::is_enum<Value>::value>::type* enable = nullptr)
{
using E = std::make_unsigned_t<std::underlying_type_t<Value>>;
using T = std::make_unsigned_t<LocalType>;
static_assert(std::numeric_limits<T>::max() >= std::numeric_limits<E>::max(), "mismatched integral/enum types");
return static_cast<LocalType>(value);
}
template <typename LocalType, typename Value>
LocalType BuildPrimitive(InvokeContext& invoke_context,
const Value& value,
TypeList<LocalType>,
typename std::enable_if<std::is_integral<Value>::value, int>::type* enable = nullptr)
{
static_assert(
std::numeric_limits<LocalType>::lowest() <= std::numeric_limits<Value>::lowest(), "mismatched integral types");
static_assert(
std::numeric_limits<LocalType>::max() >= std::numeric_limits<Value>::max(), "mismatched integral types");
return value;
}
template <typename LocalType, typename Value>
LocalType BuildPrimitive(InvokeContext& invoke_context,
const Value& value,
TypeList<LocalType>,
typename std::enable_if<std::is_floating_point<Value>::value>::type* enable = nullptr)
{
static_assert(std::is_same<Value, LocalType>::value,
"mismatched floating point types. please fix message.capnp type declaration to match wrapped interface");
return value;
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<LocalType>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest,
typename std::enable_if<std::is_enum<LocalType>::value>::type* enable = 0)
{
return read_dest.construct(static_cast<LocalType>(input.get()));
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<LocalType>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest,
typename std::enable_if<std::is_integral<LocalType>::value>::type* enable = nullptr)
{
auto value = input.get();
if (value < std::numeric_limits<LocalType>::min() || value > std::numeric_limits<LocalType>::max()) {
throw std::range_error("out of bound int received");
}
return read_dest.construct(static_cast<LocalType>(value));
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<LocalType>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest,
typename std::enable_if<std::is_floating_point<LocalType>::value>::type* enable = 0)
{
auto value = input.get();
static_assert(std::is_same<LocalType, decltype(value)>::value, "floating point type mismatch");
return read_dest.construct(value);
}
} // namespace mp
#endif // MP_PROXY_TYPE_NUMBER_H

View File

@@ -0,0 +1,48 @@
// Copyright (c) 2025 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_TYPE_OPTIONAL_H
#define MP_PROXY_TYPE_OPTIONAL_H
#include <mp/util.h>
namespace mp {
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<std::optional<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
if (value) {
output.setHas();
// FIXME: should std::move value if destvalue is rref?
BuildField(TypeList<LocalType>(), invoke_context, output, *value);
}
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::optional<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
if (!input.has()) {
value.reset();
} else if (value) {
ReadField(TypeList<LocalType>(), invoke_context, input, ReadDestUpdate(*value));
} else {
ReadField(TypeList<LocalType>(), invoke_context, input,
ReadDestEmplace(TypeList<LocalType>(), [&](auto&&... args) -> auto& {
value.emplace(std::forward<decltype(args)>(args)...);
return *value;
}));
}
});
}
} // namespace mp
#endif // MP_PROXY_TYPE_OPTIONAL_H

50
include/mp/type-pair.h Normal file
View File

@@ -0,0 +1,50 @@
// Copyright (c) 2025 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_TYPE_PAIR_H
#define MP_PROXY_TYPE_PAIR_H
#include <mp/util.h>
namespace mp {
// FIXME: Overload on output type instead of value type and switch to std::get and merge with next overload
template <typename KeyLocalType, typename ValueLocalType, typename Value, typename Output>
void CustomBuildField(TypeList<std::pair<KeyLocalType, ValueLocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
auto pair = output.init();
using Accessors = typename ProxyStruct<typename decltype(pair)::Builds>::Accessors;
BuildField(TypeList<KeyLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<0, Accessors>>(pair), value.first);
BuildField(TypeList<ValueLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<1, Accessors>>(pair), value.second);
}
template <typename KeyLocalType, typename ValueLocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::pair<KeyLocalType, ValueLocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
const auto& pair = input.get();
using Accessors = typename ProxyStruct<typename Decay<decltype(pair)>::Reads>::Accessors;
ReadField(TypeList<KeyLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<0, Accessors>>(pair),
ReadDestEmplace(TypeList<KeyLocalType>(), [&](auto&&... key_args) -> auto& {
KeyLocalType* key = nullptr;
ReadField(TypeList<ValueLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<1, Accessors>>(pair),
ReadDestEmplace(TypeList<ValueLocalType>(), [&](auto&&... value_args) -> auto& {
auto& ret = read_dest.construct(std::piecewise_construct, std::forward_as_tuple(key_args...),
std::forward_as_tuple(value_args...));
key = &ret.first;
return ret.second;
}));
return *key;
}));
}
} // namespace mp
#endif // MP_PROXY_TYPE_PAIR_H

113
include/mp/type-pointer.h Normal file
View File

@@ -0,0 +1,113 @@
// Copyright (c) 2025 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_TYPE_POINTER_H
#define MP_PROXY_TYPE_POINTER_H
#include <mp/util.h>
namespace mp {
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<LocalType*>, Priority<3>, InvokeContext& invoke_context, Value&& value, Output&& output)
{
if (value) {
BuildField(TypeList<LocalType>(), invoke_context, output, *value);
}
}
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<std::shared_ptr<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
if (value) {
BuildField(TypeList<LocalType>(), invoke_context, output, *value);
}
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<LocalType*>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
if (value) {
ReadField(TypeList<LocalType>(), invoke_context, std::forward<Input>(input), ReadDestUpdate(*value));
}
});
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::shared_ptr<LocalType>>,
Priority<0>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
if (!input.has()) {
value.reset();
} else if (value) {
ReadField(TypeList<LocalType>(), invoke_context, input, ReadDestUpdate(*value));
} else {
ReadField(TypeList<LocalType>(), invoke_context, input,
ReadDestEmplace(TypeList<LocalType>(), [&](auto&&... args) -> auto& {
value = std::make_shared<LocalType>(std::forward<decltype(args)>(args)...);
return *value;
}));
}
});
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::shared_ptr<const LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
if (!input.has()) {
value.reset();
return;
}
ReadField(TypeList<LocalType>(), invoke_context, std::forward<Input>(input),
ReadDestEmplace(TypeList<LocalType>(), [&](auto&&... args) -> auto& {
value = std::make_shared<LocalType>(std::forward<decltype(args)>(args)...);
return *value;
}));
});
}
//! PassField override for C++ pointer arguments.
template <typename Accessor, typename LocalType, typename ServerContext, typename Fn, typename... Args>
void PassField(Priority<1>, TypeList<LocalType*>, ServerContext& server_context, const Fn& fn, Args&&... args)
{
const auto& params = server_context.call_context.getParams();
const auto& input = Make<StructField, Accessor>(params);
if (!input.want()) {
fn.invoke(server_context, std::forward<Args>(args)..., nullptr);
return;
}
InvokeContext& invoke_context = server_context;
Decay<LocalType> param;
MaybeReadField(std::integral_constant<bool, Accessor::in>(), TypeList<LocalType>(), invoke_context, input,
ReadDestUpdate(param));
fn.invoke(server_context, std::forward<Args>(args)..., &param);
auto&& results = server_context.call_context.getResults();
MaybeBuildField(std::integral_constant<bool, Accessor::out>(), TypeList<LocalType>(), invoke_context,
Make<StructField, Accessor>(results), param);
}
} // namespace mp
#endif // MP_PROXY_TYPE_POINTER_H

48
include/mp/type-set.h Normal file
View File

@@ -0,0 +1,48 @@
// Copyright (c) 2025 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_TYPE_SET_H
#define MP_PROXY_TYPE_SET_H
#include <mp/proxy-types.h>
#include <mp/util.h>
namespace mp {
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<std::set<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
// FIXME dededup with vector handler above
auto list = output.init(value.size());
size_t i = 0;
for (const auto& elem : value) {
BuildField(TypeList<LocalType>(), invoke_context, ListOutput<typename decltype(list)::Builds>(list, i), elem);
++i;
}
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::set<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
auto data = input.get();
value.clear();
for (auto item : data) {
ReadField(TypeList<LocalType>(), invoke_context, Make<ValueField>(item),
ReadDestEmplace(TypeList<const LocalType>(), [&](auto&&... args) -> auto& {
return *value.emplace(std::forward<decltype(args)>(args)...).first;
}));
}
});
}
} // namespace mp
#endif // MP_PROXY_TYPE_SET_H

34
include/mp/type-string.h Normal file
View File

@@ -0,0 +1,34 @@
// Copyright (c) 2025 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_TYPE_STRING_H
#define MP_PROXY_TYPE_STRING_H
#include <mp/util.h>
namespace mp {
template <typename Value, typename Output>
void CustomBuildField(TypeList<std::string>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
auto result = output.init(value.size());
memcpy(result.begin(), value.data(), value.size());
}
template <typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::string>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
auto data = input.get();
return read_dest.construct(CharCast(data.begin()), data.size());
}
} // namespace mp
#endif // MP_PROXY_TYPE_STRING_H

85
include/mp/type-struct.h Normal file
View File

@@ -0,0 +1,85 @@
// Copyright (c) 2025 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_TYPE_STRUCT_H
#define MP_PROXY_TYPE_STRUCT_H
#include <mp/util.h>
namespace mp {
template <size_t index, typename LocalType, typename Value, typename Output>
void BuildOne(TypeList<LocalType> param,
InvokeContext& invoke_context,
Output&& output,
Value&& value,
typename std::enable_if < index<ProxyType<LocalType>::fields>::type * enable = nullptr)
{
using Index = std::integral_constant<size_t, index>;
using Struct = typename ProxyType<LocalType>::Struct;
using Accessor = typename std::tuple_element<index, typename ProxyStruct<Struct>::Accessors>::type;
auto&& field_output = Make<StructField, Accessor>(output);
auto&& field_value = value.*ProxyType<LocalType>::get(Index());
BuildField(TypeList<Decay<decltype(field_value)>>(), invoke_context, field_output, field_value);
BuildOne<index + 1>(param, invoke_context, output, value);
}
template <size_t index, typename LocalType, typename Value, typename Output>
void BuildOne(TypeList<LocalType> param,
InvokeContext& invoke_context,
Output&& output,
Value&& value,
typename std::enable_if<index == ProxyType<LocalType>::fields>::type* enable = nullptr)
{
}
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<LocalType> local_type,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output,
typename ProxyType<LocalType>::Struct* enable = nullptr)
{
BuildOne<0>(local_type, invoke_context, output.init(), value);
}
template <size_t index, typename LocalType, typename Input, typename Value>
void ReadOne(TypeList<LocalType> param,
InvokeContext& invoke_context,
Input&& input,
Value&& value,
typename std::enable_if<index != ProxyType<LocalType>::fields>::type* enable = nullptr)
{
using Index = std::integral_constant<size_t, index>;
using Struct = typename ProxyType<LocalType>::Struct;
using Accessor = typename std::tuple_element<index, typename ProxyStruct<Struct>::Accessors>::type;
const auto& struc = input.get();
auto&& field_value = value.*ProxyType<LocalType>::get(Index());
ReadField(TypeList<RemoveCvRef<decltype(field_value)>>(), invoke_context, Make<StructField, Accessor>(struc),
ReadDestUpdate(field_value));
ReadOne<index + 1>(param, invoke_context, input, value);
}
template <size_t index, typename LocalType, typename Input, typename Value>
void ReadOne(TypeList<LocalType> param,
InvokeContext& invoke_context,
Input& input,
Value& value,
typename std::enable_if<index == ProxyType<LocalType>::fields>::type* enable = nullptr)
{
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<LocalType> param,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest,
typename ProxyType<LocalType>::Struct* enable = nullptr)
{
return read_dest.update([&](auto& value) { ReadOne<0>(param, invoke_context, input, value); });
}
} // namespace mp
#endif // MP_PROXY_TYPE_STRUCT_H

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2025 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_TYPE_THREADMAP_H
#define MP_PROXY_TYPE_THREADMAP_H
#include <mp/util.h>
namespace mp {
template <>
struct ProxyServer<ThreadMap> final : public virtual ThreadMap::Server
{
public:
ProxyServer(Connection& connection);
kj::Promise<void> makeThread(MakeThreadContext context) override;
Connection& m_connection;
};
template <typename Output>
void CustomBuildField(TypeList<>,
Priority<1>,
InvokeContext& invoke_context,
Output&& output,
typename std::enable_if<std::is_same<decltype(output.get()), ThreadMap::Client>::value>::type* enable = nullptr)
{
output.set(kj::heap<ProxyServer<ThreadMap>>(invoke_context.connection));
}
template <typename Input>
decltype(auto) CustomReadField(TypeList<>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
typename std::enable_if<std::is_same<decltype(input.get()), ThreadMap::Client>::value>::type* enable = nullptr)
{
invoke_context.connection.m_thread_map = input.get();
}
} // namespace mp
#endif // MP_PROXY_TYPE_THREADMAP_H

45
include/mp/type-tuple.h Normal file
View File

@@ -0,0 +1,45 @@
// Copyright (c) 2025 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_TYPE_TUPLE_H
#define MP_PROXY_TYPE_TUPLE_H
#include <mp/util.h>
namespace mp {
// TODO: Should generalize this to work with arbitrary length tuples, not just length 2-tuples.
template <typename KeyLocalType, typename ValueLocalType, typename Value, typename Output>
void CustomBuildField(TypeList<std::tuple<KeyLocalType, ValueLocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
auto pair = output.init();
using Accessors = typename ProxyStruct<typename decltype(pair)::Builds>::Accessors;
BuildField(TypeList<KeyLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<0, Accessors>>(pair), std::get<0>(value));
BuildField(TypeList<ValueLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<1, Accessors>>(pair), std::get<1>(value));
}
// TODO: Should generalize this to work with arbitrary length tuples, not just length 2-tuples.
template <typename KeyLocalType, typename ValueLocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::tuple<KeyLocalType, ValueLocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
const auto& pair = input.get();
using Struct = ProxyStruct<typename Decay<decltype(pair)>::Reads>;
using Accessors = typename Struct::Accessors;
ReadField(TypeList<KeyLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<0, Accessors>>(pair),
ReadDestUpdate(std::get<0>(value)));
ReadField(TypeList<ValueLocalType>(), invoke_context, Make<StructField, std::tuple_element_t<1, Accessors>>(pair),
ReadDestUpdate(std::get<1>(value)));
});
}
} // namespace mp
#endif // MP_PROXY_TYPE_TUPLE_H

71
include/mp/type-vector.h Normal file
View File

@@ -0,0 +1,71 @@
// Copyright (c) 2025 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_TYPE_VECTOR_H
#define MP_PROXY_TYPE_VECTOR_H
#include <mp/proxy-types.h>
#include <mp/util.h>
namespace mp {
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<std::vector<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Value&& value,
Output&& output)
{
// FIXME dedup with set handler below
auto list = output.init(value.size());
size_t i = 0;
for (auto it = value.begin(); it != value.end(); ++it, ++i) {
BuildField(TypeList<LocalType>(), invoke_context, ListOutput<typename decltype(list)::Builds>(list, i), *it);
}
}
inline static bool BuildPrimitive(InvokeContext& invoke_context, std::vector<bool>::const_reference value, TypeList<bool>)
{
return value;
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::vector<LocalType>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
auto data = input.get();
value.clear();
value.reserve(data.size());
for (auto item : data) {
ReadField(TypeList<LocalType>(), invoke_context, Make<ValueField>(item),
ReadDestEmplace(TypeList<LocalType>(), [&](auto&&... args) -> auto& {
value.emplace_back(std::forward<decltype(args)>(args)...);
return value.back();
}));
}
});
}
template <typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<std::vector<bool>>,
Priority<1>,
InvokeContext& invoke_context,
Input&& input,
ReadDest&& read_dest)
{
return read_dest.update([&](auto& value) {
auto data = input.get();
value.clear();
value.reserve(data.size());
for (auto item : data) {
value.push_back(ReadField(TypeList<bool>(), invoke_context, Make<ValueField>(item), ReadDestTemp<bool>()));
}
});
}
} // namespace mp
#endif // MP_PROXY_TYPE_VECTOR_H

23
include/mp/type-void.h Normal file
View File

@@ -0,0 +1,23 @@
// Copyright (c) 2025 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_TYPE_VOID_H
#define MP_PROXY_TYPE_VOID_H
#include <mp/util.h>
namespace mp {
template <typename Value>
::capnp::Void BuildPrimitive(InvokeContext& invoke_context, Value&&, TypeList<::capnp::Void>)
{
return {};
}
template <typename LocalType, typename Output>
void CustomBuildField(TypeList<LocalType>, Priority<1>, InvokeContext& invoke_context, ::capnp::Void, Output&& output)
{
}
} // namespace mp
#endif // MP_PROXY_TYPE_VOID_H

220
include/mp/util.h Normal file
View File

@@ -0,0 +1,220 @@
// 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_UTIL_H
#define MP_UTIL_H
#include <capnp/schema.h>
#include <cstddef>
#include <functional>
#include <future>
#include <kj/common.h>
#include <kj/exception.h>
#include <kj/string-tree.h>
#include <memory>
#include <string.h>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
namespace mp {
//! Generic utility functions used by capnp code.
//! Type holding a list of types.
//!
//! Example:
//! TypeList<int, bool, void>
template <typename... Types>
struct TypeList
{
static constexpr size_t size = sizeof...(Types);
};
//! Construct a template class value by deducing template arguments from the
//! types of constructor arguments, so they don't need to be specified manually.
//!
//! Uses of this can go away with class template deduction in C++17
//! (https://en.cppreference.com/w/cpp/language/class_template_argument_deduction)
//!
//! Example:
//! Make<std::pair>(5, true) // Constructs std::pair<int, bool>(5, true);
template <template <typename...> class Class, typename... Types, typename... Args>
Class<Types..., std::remove_reference_t<Args>...> Make(Args&&... args)
{
return Class<Types..., std::remove_reference_t<Args>...>{std::forward<Args>(args)...};
}
//! Type helper splitting a TypeList into two halves at position index.
//!
//! Example:
//! is_same<TypeList<int, double>, Split<2, TypeList<int, double, float, bool>>::First>
//! is_same<TypeList<float, bool>, Split<2, TypeList<int, double, float, bool>>::Second>
template <std::size_t index, typename List, typename _First = TypeList<>, bool done = index == 0>
struct Split;
//! Specialization of above (base case)
template <typename _Second, typename _First>
struct Split<0, _Second, _First, true>
{
using First = _First;
using Second = _Second;
};
//! Specialization of above (recursive case)
template <std::size_t index, typename Type, typename... _Second, typename... _First>
struct Split<index, TypeList<Type, _Second...>, TypeList<_First...>, false>
{
using _Next = Split<index - 1, TypeList<_Second...>, TypeList<_First..., Type>>;
using First = typename _Next::First;
using Second = typename _Next::Second;
};
//! Type helper giving return type of a callable type.
template <typename Callable>
using ResultOf = decltype(std::declval<Callable>()());
//! Substitutue for std::remove_cvref_t
template <typename T>
using RemoveCvRef = std::remove_cv_t<std::remove_reference_t<T>>;
//! Type helper abbreviating std::decay.
template <typename T>
using Decay = std::decay_t<T>;
//! SFINAE helper, see using Require below.
template <typename SfinaeExpr, typename Result_>
struct _Require
{
using Result = Result_;
};
//! SFINAE helper, basically the same as to C++17's void_t, but allowing types other than void to be returned.
template <typename SfinaeExpr, typename Result = void>
using Require = typename _Require<SfinaeExpr, Result>::Result;
//! Function parameter type for prioritizing overloaded function calls that
//! would otherwise be ambiguous.
//!
//! Example:
//! auto foo(Priority<1>) -> std::enable_if<>;
//! auto foo(Priority<0>) -> void;
//!
//! foo(Priority<1>()); // Calls higher priority overload if enabled.
template <int priority>
struct Priority : Priority<priority - 1>
{
};
//! Specialization of above (base case)
template <>
struct Priority<0>
{
};
//! Return capnp type name with filename prefix removed.
template <typename T>
const char* TypeName()
{
// DisplayName string looks like
// "interfaces/capnp/common.capnp:ChainNotifications.resendWalletTransactions$Results"
// This discards the part of the string before the first ':' character.
// Another alternative would be to use the displayNamePrefixLength field,
// but this discards everything before the last '.' character, throwing away
// the object name, which is useful.
const char* display_name = ::capnp::Schema::from<T>().getProto().getDisplayName().cStr();
const char* short_name = strchr(display_name, ':');
return short_name ? short_name + 1 : display_name;
}
//! Analog to std::lock_guard that unlocks instead of locks.
template <typename Lock>
struct UnlockGuard
{
UnlockGuard(Lock& lock) : m_lock(lock) { m_lock.unlock(); }
~UnlockGuard() { m_lock.lock(); }
Lock& m_lock;
};
template <typename Lock, typename Callback>
void Unlock(Lock& lock, Callback&& callback)
{
const UnlockGuard<Lock> unlock(lock);
callback();
}
//! Needed for libc++/macOS compatibility. Lets code work with shared_ptr nothrow declaration
//! https://github.com/capnproto/capnproto/issues/553#issuecomment-328554603
template <typename T>
struct DestructorCatcher
{
T value;
template <typename... Params>
DestructorCatcher(Params&&... params) : value(kj::fwd<Params>(params)...)
{
}
~DestructorCatcher() noexcept try {
} catch (const kj::Exception& e) { // NOLINT(bugprone-empty-catch)
}
};
//! Wrapper around callback function for compatibility with std::async.
//!
//! std::async requires callbacks to be copyable and requires noexcept
//! destructors, but this doesn't work well with kj types which are generally
//! move-only and not noexcept.
template <typename Callable>
struct AsyncCallable
{
AsyncCallable(Callable&& callable) : m_callable(std::make_shared<DestructorCatcher<Callable>>(std::move(callable)))
{
}
AsyncCallable(const AsyncCallable&) = default;
AsyncCallable(AsyncCallable&&) = default;
~AsyncCallable() noexcept = default;
ResultOf<Callable> operator()() const { return (m_callable->value)(); }
mutable std::shared_ptr<DestructorCatcher<Callable>> m_callable;
};
//! Construct AsyncCallable object.
template <typename Callable>
AsyncCallable<std::remove_reference_t<Callable>> MakeAsyncCallable(Callable&& callable)
{
return std::move(callable);
}
//! Format current thread name as "{exe_name}-{$pid}/{thread_name}-{$tid}".
std::string ThreadName(const char* exe_name);
//! Escape binary string for use in log so it doesn't trigger unicode decode
//! errors in python unit tests.
std::string LogEscape(const kj::StringTree& string);
//! Callback type used by SpawnProcess below.
using FdToArgsFn = std::function<std::vector<std::string>(int fd)>;
//! Spawn a new process that communicates with the current process over a socket
//! pair. Returns pid through an output argument, and file descriptor for the
//! local side of the socket. Invokes fd_to_args callback with the remote file
//! descriptor number which returns the command line arguments that should be
//! used to execute the process, and which should have the remote file
//! descriptor embedded in whatever format the child process expects.
int SpawnProcess(int& pid, FdToArgsFn&& fd_to_args);
//! Call execvp with vector args.
void ExecProcess(const std::vector<std::string>& args);
//! Wait for a process to exit and return its exit code.
int WaitProcess(int pid);
inline char* CharCast(char* c) { return c; }
inline char* CharCast(unsigned char* c) { return (char*)c; }
inline const char* CharCast(const char* c) { return c; }
inline const char* CharCast(const unsigned char* c) { return (const char*)c; }
} // namespace mp
#endif // MP_UTIL_H