11#include <mp/proxy.capnp.h>
13#include <capnp/rpc-twoparty.h>
18#include <condition_variable>
21#include <kj/function.h>
49template <
typename ProxyServer,
typename CallContext_>
77template <
typename Interface,
typename Params,
typename Results>
112 template<
typename T,
typename Fn>
113 kj::Promise<T> post(Fn&& fn);
120 kj::Promise<void> m_thread_ready{kj::READY_NOW};
128 void taskFailed(kj::Exception&& exception)
override;
184 template <
typename T>
191 template <
typename T>
213#define MP_LOGPLAIN(loop, ...) if (mp::Logger logger{(loop).m_log_opts, __VA_ARGS__}; logger) logger
215#define MP_LOG(loop, ...) MP_LOGPLAIN(loop, __VA_ARGS__) << "{" << LongThreadName((loop).m_exe_name) << "} "
272 void sync(kj::FunctionParam<
void()> fn);
276 void addAsyncCleanup(std::function<
void()> fn);
298 auto incomingConnections() {
return std::views::all(m_incoming_connections); }
305 std::thread::id m_thread_id = std::this_thread::get_id();
399 template <
typename Fn>
402 const Lock lock(m_mutex);
403 if (m_fn)
return false;
404 m_fn = std::forward<Fn>(fn);
409 template <
class Predicate>
412 m_cv.wait(lock.m_lock, [&]()
MP_REQUIRES(m_mutex) {
419 auto fn = std::move(*m_fn);
423 const bool done =
pred();
449 :
m_loop(loop), m_stream(kj::
mv(stream_)),
453 kj::Own<kj::AsyncIoStream>&& stream_,
455 :
m_loop(loop), m_stream(kj::
mv(stream_)),
474 void onDisconnect(
F&&
f)
481 m_on_disconnect.add(m_network.onDisconnect().then(
482 [
f = std::forward<F>(
f),
this]()
mutable { m_loop->m_task_set->add(kj::evalLater(kj::mv(f))); }));
491 kj::TaskSet m_on_disconnect{m_error_handler};
493 std::optional<::capnp::RpcSystem<::capnp::rpc::twoparty::VatId>>
m_rpc_system;
497 ThreadMap::Client m_thread_map{
nullptr};
531 ::capnp::word scratch[4]{};
532 ::capnp::MallocMessageBuilder message{scratch};
533 ::capnp::rpc::twoparty::VatId::Builder vat_id{message.getRoot<::capnp::rpc::twoparty::VatId>()};
534 ServerVatId() { vat_id.setSide(::capnp::rpc::twoparty::Side::SERVER); }
537template <
typename Interface,
typename Impl>
549 typename Interface::Client(std::move(
m_client));
575 if (kj::runCatchingExceptions([&]{ Sub::destroy(*
this); }) !=
nullptr) {
591 typename Interface::Client(std::move(
m_client));
604 Sub::construct(*
this);
624template <
typename Interface,
typename Impl>
632template <
typename Interface,
typename Impl>
652template <
typename Interface,
typename Impl>
676 m_context.loop->addAsyncCleanup([impl=std::move(m_impl),
fns=std::move(
m_context.cleanup_fns)]()
mutable {
702template <
typename Interface,
typename Impl>
715using ConnThreads = std::map<Connection*, std::optional<ProxyClient<Thread>>>;
756 std::unique_ptr<Waiter>
waiter =
nullptr;
794template<
typename T,
typename Fn>
797 auto ready = kj::newPromiseAndFulfiller<void>();
807 auto result = kj::newPromiseAndFulfiller<T>();
808 bool posted = m_thread_context.waiter->post([this, self = std::move(self), fn = std::forward<Fn>(fn), ready_fulfiller = kj::mv(ready_fulfiller), result_fulfiller = kj::mv(result.fulfiller), cancel_monitor_ptr = kj::mv(cancel_monitor_ptr)]() mutable {
817 m_loop->sync([ready_fulfiller = kj::mv(ready_fulfiller)]() mutable {
818 ready_fulfiller->fulfill();
819 ready_fulfiller = nullptr;
821 std::optional<T> result_value;
822 kj::Maybe<kj::Exception> exception{kj::runCatchingExceptions([&]{ result_value.emplace(fn(*cancel_monitor_ptr)); })};
823 m_loop->sync([this, &result_value, &exception, self = kj::mv(self), result_fulfiller = kj::mv(result_fulfiller), cancel_monitor_ptr = kj::mv(cancel_monitor_ptr)]() mutable {
827 cancel_monitor_ptr = nullptr;
831 KJ_IF_MAYBE(e, exception) {
832 assert(!result_value);
833 result_fulfiller->reject(kj::mv(*e));
835 assert(result_value);
836 result_fulfiller->fulfill(kj::mv(*result_value));
837 result_value.reset();
839 result_fulfiller = nullptr;
844 m_loop->m_task_set->add(kj::evalLater([self = kj::mv(self)] {}));
852 return kj::mv(result.promise);
854 m_thread_ready = kj::mv(ready.promise);
870template <
typename InitInterface>
873 typename InitInterface::Client
init_client(
nullptr);
874 std::unique_ptr<Connection> connection;
876 connection = std::make_unique<Connection>(loop, kj::mv(
stream));
893template <
typename InitInterface,
typename InitImpl,
typename OnDisconnect>
894std::pair<ProxyServer<InitInterface>*, EventLoop::Connections::iterator>
_Serve(
EventLoop& loop,
895 kj::Own<kj::AsyncIoStream>&&
stream,
896 std::shared_ptr<InitImpl>
init,
902 auto proxy_server = kj::heap<ProxyServer<InitInterface>>(std::move(
init), connection);
903 server = proxy_server;
904 return capnp::Capability::Client(kj::mv(proxy_server));
927template <
typename InitInterface,
typename InitImpl,
typename OnDisconnect>
928std::pair<ProxyServer<InitInterface>*, EventLoop::Connections::iterator>
_Serve(
EventLoop& loop,
929 kj::Own<kj::AsyncIoStream>&&
stream,
940 explicit Listener(kj::Own<kj::ConnectionReceiver>&& receiver, std::optional<size_t> max_connections)
953template <
typename InitInterface,
typename InitImpl>
958 auto* receiver =
listener->m_receiver.get();
961 ++listener->m_active_connections;
962 _Serve<InitInterface>(loop, kj::mv(stream), init, [&loop, &init, listener] {
963 const bool resume_accept{listener->atCapacity()};
964 assert(listener->m_active_connections > 0);
965 --listener->m_active_connections;
966 if (resume_accept) _Listen<InitInterface>(listener, loop, init);
975template <
typename InitInterface,
typename InitImpl>
976std::pair<ProxyServer<InitInterface>*, EventLoop::Connections::iterator>
ServeStream(
984template <
typename InitInterface,
typename InitImpl>
988 auto listener{std::make_shared<Listener>(
989 loop.
m_io_context.lowLevelProvider->wrapListenSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP),
Helper class that detects when a promise is canceled.
Object holding network & rpc state associated with either an incoming server connection,...
CleanupIt addSyncCleanup(std::function< void()> fn)
Register synchronous cleanup function to run on event loop thread (with access to capnp thread local ...
::capnp::TwoPartyVatNetwork m_network
kj::Own< kj::AsyncIoStream > m_stream
Connection(EventLoop &loop, kj::Own< kj::AsyncIoStream > &&stream_, const std::function<::capnp::Capability::Client(Connection &)> &make_client)
Connection(EventLoop &loop, kj::Own< kj::AsyncIoStream > &&stream_)
void onDisconnect(F &&f)
Add disconnect handler.
std::vector< PoolSlot > m_thread_pool
kj::Canceler m_canceler
Canceler for canceling promises that we want to discard when the connection is destroyed.
::capnp::CapabilityServerSet< Thread > m_threads
Collection of server-side IPC worker threads (ProxyServer<Thread> objects previously returned by Thre...
CleanupList m_sync_cleanup_fns
Cleanup functions to run if connection is broken unexpectedly.
std::optional<::capnp::RpcSystem<::capnp::rpc::twoparty::VatId > > m_rpc_system
void removeSyncCleanup(CleanupIt it)
Event loop implementation.
const char * m_exe_name
Process name included in thread names so combined debug output from multiple processes is easier to u...
kj::AsyncIoContext m_io_context
Capnp IO context.
Connections m_incoming_connections
List of connections.
std::condition_variable m_cv
EventLoop(const char *exe_name, LogFn log_fn, void *context=nullptr)
Construct event loop object with default logging options.
kj::FunctionParam< void()> *m_sync_fn MP_GUARDED_BY(m_mutex)
Callback function to run on event loop thread during sync() call.
std::function< void()> testing_hook_connected
Hook called on the event loop thread when a client has connected.
void sync(kj::FunctionParam< void()> fn)
Run function on event loop thread.
Mutex m_mutex
Mutex and condition variable used to post tasks to event loop and async thread.
int m_num_refs MP_GUARDED_BY(m_mutex)=0
Number of EventLoopRef instances referencing this event loop.
kj::Own< kj::AsyncIoStream > m_post_stream
std::list< Connection > Connections
Type of m_incoming_connections list.
std::function< void()> testing_hook_disconnected
Hook called on the event loop thread when a client has disconnected.
LogOptions m_log_opts
Logging options.
std::unique_ptr< kj::TaskSet > m_task_set
Capnp list of pending promises.
void * m_context
External context pointer.
std::function< void()> testing_hook_makethread
Hook called when ProxyServer<ThreadMap>::makeThread() is called.
std::optional< CleanupList > m_async_fns MP_GUARDED_BY(m_mutex)
Callback functions to run on async thread.
kj::Own< kj::OutputStream > m_post_writer
Synchronous writer used to write to m_post_stream.
std::thread m_async_thread
Handle of an async worker thread.
std::function< void()> testing_hook_makethread_created
Hook called on the worker thread inside makeThread(), after the thread context is set up and thread_c...
EventLoop(const char *exe_name, std::function< void(bool, std::string)> old_callback, void *context=nullptr)
Backwards-compatible constructor for previous (deprecated) logging callback signature.
kj::Own< kj::AsyncIoStream > m_wait_stream
Socket pair used to post and wait for wakeups to the event loop thread.
std::function< void()> testing_hook_async_request_done
Hook called on the worker thread just before returning results.
std::function< void()> testing_hook_async_request_start
Hook called on the worker thread when it starts to execute an async request.
std::function< void(std::any)> testing_hook_misc
Miscellaneous testing hook.
Event loop smart pointer automatically managing m_num_refs.
friend Logger & operator<<(Logger &logger, T &&value)
Logger(const Logger &)=delete
~Logger() noexcept(false)
Logger & operator=(Logger &&)=delete
Logger(const LogOptions &options, Log log_level)
const LogOptions & m_options
friend Logger & operator<<(Logger &&logger, T &&value)
std::ostringstream m_buffer
Logger & operator=(const Logger &)=delete
Handler for kj::TaskSet failed task events.
LoggingErrorHandler(EventLoop &loop)
void taskFailed(kj::Exception &&exception) override
Base class for generated ProxyClient classes that implement a C++ interface and forward calls to a ca...
Interface::Client m_client
~ProxyClientBase() noexcept
ProxyClientBase(typename Interface::Client client, Connection *connection, bool destroy_connection)
Construct libmultiprocess client object wrapping Cap'n Proto client object with a reference to the as...
#define T(expected, seed, data)
std::optional< mp::EventLoop > m_loop
EventLoop object which manages I/O events for all connections.
std::unique_ptr< ProxyClient< messages::FooInterface > > client
std::thread thread
Thread variable should be after other struct members so the thread does not start until the other mem...
Functions to serialize / deserialize common bitcoin types.
void Unlock(Lock &lock, Callback &&callback)
std::string CxxTypeName(const T &)
Stream MakeStream(EventLoop &loop, SocketId socket)
Wrap a socket file descriptor as an async stream, taking ownership of the fd.
std::pair< ProxyServer< InitInterface > *, EventLoop::Connections::iterator > _Serve(EventLoop &loop, kj::Own< kj::AsyncIoStream > &&stream, std::shared_ptr< InitImpl > init, OnDisconnect &&on_disconnect, bool destroy_connection=true)
Given stream and init objects, construct a new ProxyServer object that handles requests from the stre...
kj::StringPtr KJ_STRINGIFY(Log flags)
kj::Own< kj::AsyncIoStream > Stream
ThreadContext & CurrentThread()
Return the current thread's ThreadContext.
std::list< std::function< void()> > CleanupList
std::function< void(LogMessage)> LogFn
std::tuple< ConnThread, bool > SetThread(GuardedRef< ConnThreads > threads, Connection *connection, const std::function< Thread::Client()> &make_thread)
thread_local ThreadContext g_thread_context
std::pair< ProxyServer< InitInterface > *, EventLoop::Connections::iterator > ServeStream(EventLoop &loop, Stream stream, InitImpl &&init, bool destroy_connection=true)
Given a stream and an init object, handle requests on the stream by calling methods on the Init objec...
void ListenConnections(EventLoop &loop, SocketId fd, InitImpl &init, std::optional< size_t > max_connections=std::nullopt)
Given listening socket identifier and an init object, handle incoming connections and requests by cal...
Log
Log flags. Update stringify function if changed!
std::string LongThreadName(const char *exe_name)
ConnThreads::iterator ConnThread
typename CleanupList::iterator CleanupIt
void CleanupRun(CleanupList &fns)
std::unique_ptr< ProxyClient< InitInterface > > ConnectStream(EventLoop &loop, Stream stream, bool destroy_connection=true)
Given a stream, make a new ProxyClient object to send requests over it.
void _Listen(const std::shared_ptr< Listener > &listener, EventLoop &loop, InitImpl &init)
std::map< Connection *, std::optional< ProxyClient< Thread > > > ConnThreads
Map from Connection to local or remote thread handle which will be used over that connection.
ThreadContext & thread_context
ClientInvokeContext(Connection &conn, ThreadContext &thread_context)
A thread created by makePool with associated pending work queue. Vector is filled once by makePool() ...
std::optional< size_t > m_max_connections
size_t m_active_connections
kj::Own< kj::ConnectionReceiver > m_receiver
Listener(kj::Own< kj::ConnectionReceiver > &&receiver, std::optional< size_t > max_connections)
Log level
The severity level of this message.
std::string message
Message to be logged.
LogFn log_fn
External logging callback.
size_t max_chars
Maximum number of characters to use when representing request and response structs as strings.
Log log_level
Messages with a severity level less than log_level will not be reported.
ProxyClient(const ProxyClient &)=delete
std::optional< CleanupIt > m_disconnect_cb
Reference to callback function that is run if there is a sudden disconnect and the Connection object ...
Mapping from capnp interface type to proxy client implementation (specializations are generated by pr...
ThreadContext & m_thread_context
ProxyServerBase(std::shared_ptr< Impl > impl, Connection &connection)
void invokeDestroy()
If the capnp interface defined a special "destroy" method, as described the ProxyClientBase class,...
virtual ~ProxyServerBase()
ProxyServer destructor, called from the EventLoop thread by Cap'n Proto garbage collection code after...
std::shared_ptr< Impl > m_impl
Implementation pointer that may or may not be owned and deleted when this capnp server goes out of sc...
Mapping from capnp interface type to proxy server implementation (specializations are generated by pr...
ServerInvokeContext(ProxyServer &proxy_server, CallContext &call_context, int req)
CallContext & call_context
ProxyServer & proxy_server
bool request_canceled
For IPC methods that execute asynchronously, not on the event-loop thread, this is set to true if the...
Lock * cancel_lock
For IPC methods that execute asynchronously, not on the event-loop thread: lock preventing the event-...
Vat id for server side of connection.
The thread_local ThreadContext struct (see CurrentThread()) provides information about individual thr...
ConnThreads callback_threads MP_GUARDED_BY(waiter->m_mutex)
When client is making a request to a server, this is the callbackThread argument it passes in the req...
std::unique_ptr< Waiter > waiter
Waiter object used to allow remote clients to execute code on this thread.
ConnThreads request_threads MP_GUARDED_BY(waiter->m_mutex)
When client is making a request to a server, this is the thread argument it passes in the request,...
bool loop_thread
Whether this thread is a capnp event loop thread.
std::string thread_name
Identifying string for debug.
Single element task queue used to handle recursive capnp calls.
void wait(Lock &lock, Predicate pred) MP_REQUIRES(m_mutex)
Mutex m_mutex
Mutex mainly used internally by waiter class, but also used externally to guard access to related sta...
std::optional< kj::Function< void()> > m_fn MP_GUARDED_BY(m_mutex)
std::condition_variable m_cv MP_GUARDED_BY(m_mutex)
constexpr auto Ticks(Dur2 d)
Helper to count the seconds of a duration/time_point.