mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-01 00:34:01 +02:00
Squashed 'src/ipc/libmultiprocess/' content from commit 35944ffd23fa
git-subtree-dir: src/ipc/libmultiprocess git-subtree-split: 35944ffd23fa26652b82210351d50e896ce16c8f
This commit is contained in:
16
include/mp/config.h.in
Normal file
16
include/mp/config.h.in
Normal 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
641
include/mp/proxy-io.h
Normal 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
727
include/mp/proxy-types.h
Normal 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
65
include/mp/proxy.capnp
Normal 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
299
include/mp/proxy.h
Normal 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
36
include/mp/type-char.h
Normal 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
34
include/mp/type-chrono.h
Normal 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
173
include/mp/type-context.h
Normal 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
46
include/mp/type-data.h
Normal 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
38
include/mp/type-decay.h
Normal 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
|
||||
22
include/mp/type-exception.h
Normal file
22
include/mp/type-exception.h
Normal 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
|
||||
67
include/mp/type-function.h
Normal file
67
include/mp/type-function.h
Normal 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
112
include/mp/type-interface.h
Normal 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
52
include/mp/type-map.h
Normal 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
64
include/mp/type-message.h
Normal 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
87
include/mp/type-number.h
Normal 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
|
||||
48
include/mp/type-optional.h
Normal file
48
include/mp/type-optional.h
Normal 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
50
include/mp/type-pair.h
Normal 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
113
include/mp/type-pointer.h
Normal 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)..., ¶m);
|
||||
|
||||
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
48
include/mp/type-set.h
Normal 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
34
include/mp/type-string.h
Normal 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
85
include/mp/type-struct.h
Normal 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
|
||||
41
include/mp/type-threadmap.h
Normal file
41
include/mp/type-threadmap.h
Normal 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
45
include/mp/type-tuple.h
Normal 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
71
include/mp/type-vector.h
Normal 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
23
include/mp/type-void.h
Normal 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
220
include/mp/util.h
Normal 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
|
||||
Reference in New Issue
Block a user