Coverage Report

Created: 2026-07-30 14:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/bitcoin/src/ipc/libmultiprocess/include/mp/proxy-io.h
Line
Count
Source
1
// Copyright (c) The Bitcoin Core developers
2
// Distributed under the MIT software license, see the accompanying
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5
#ifndef MP_PROXY_IO_H
6
#define MP_PROXY_IO_H
7
8
#include <mp/proxy.h>
9
#include <mp/util.h>
10
11
#include <mp/proxy.capnp.h>
12
13
#include <capnp/rpc-twoparty.h>
14
15
#include <assert.h>
16
#include <algorithm>
17
#include <condition_variable>
18
#include <cstdlib>
19
#include <functional>
20
#include <kj/function.h>
21
#include <map>
22
#include <memory>
23
#include <optional>
24
#include <sstream>
25
#include <string>
26
#include <thread>
27
28
namespace mp {
29
struct ThreadContext;
30
struct Listener;
31
32
struct InvokeContext
33
{
34
    Connection& connection;
35
};
36
37
struct ClientInvokeContext : InvokeContext
38
{
39
    ThreadContext& thread_context;
40
    ClientInvokeContext(Connection& conn, ThreadContext& thread_context)
41
0
        : InvokeContext{conn}, thread_context{thread_context}
42
0
    {
43
0
    }
44
};
45
46
template <typename ProxyServer, typename CallContext_>
47
struct ServerInvokeContext : InvokeContext
48
{
49
    using CallContext = CallContext_;
50
51
    ProxyServer& proxy_server;
52
    CallContext& call_context;
53
    int req;
54
    //! For IPC methods that execute asynchronously, not on the event-loop
55
    //! thread: lock preventing the event-loop thread from freeing the params or
56
    //! results structs if the request is canceled while the worker thread is
57
    //! reading params (`call_context.getParams()`) or writing results
58
    //! (`call_context.getResults()`).
59
    Lock* cancel_lock{nullptr};
60
    //! For IPC methods that execute asynchronously, not on the event-loop
61
    //! thread, this is set to true if the IPC call was canceled by the client
62
    //! or canceled by a disconnection. If the call runs on the event-loop
63
    //! thread, it can't be canceled. This should be accessed with cancel_lock
64
    //! held if it is not null, since in the asynchronous case it is accessed
65
    //! from multiple threads.
66
    bool request_canceled{false};
67
68
    ServerInvokeContext(ProxyServer& proxy_server, CallContext& call_context, int req)
69
0
        : InvokeContext{*proxy_server.m_context.connection}, proxy_server{proxy_server}, call_context{call_context}, req{req}
70
0
    {
71
0
    }
Unexecuted instantiation: _ZN2mp19ServerInvokeContextINS_11ProxyServerIN4test4fuzz8messages16IpcFuzzInterfaceEEEN5capnp11CallContextINS5_9AddParamsENS5_10AddResultsEEEEC2ERS6_RSB_i
Unexecuted instantiation: _ZN2mp19ServerInvokeContextINS_11ProxyServerIN4test4fuzz8messages16IpcFuzzInterfaceEEEN5capnp11CallContextINS5_18PassOutPointParamsENS5_19PassOutPointResultsEEEEC2ERS6_RSB_i
Unexecuted instantiation: _ZN2mp19ServerInvokeContextINS_11ProxyServerIN4test4fuzz8messages16IpcFuzzInterfaceEEEN5capnp11CallContextINS5_21PassVectorUint8ParamsENS5_22PassVectorUint8ResultsEEEEC2ERS6_RSB_i
Unexecuted instantiation: _ZN2mp19ServerInvokeContextINS_11ProxyServerIN4test4fuzz8messages16IpcFuzzInterfaceEEEN5capnp11CallContextINS5_16PassScriptParamsENS5_17PassScriptResultsEEEEC2ERS6_RSB_i
Unexecuted instantiation: _ZN2mp19ServerInvokeContextINS_11ProxyServerIN4test4fuzz8messages16IpcFuzzInterfaceEEEN5capnp11CallContextINS5_18PassUniValueParamsENS5_19PassUniValueResultsEEEEC2ERS6_RSB_i
Unexecuted instantiation: _ZN2mp19ServerInvokeContextINS_11ProxyServerIN4test4fuzz8messages16IpcFuzzInterfaceEEEN5capnp11CallContextINS5_21PassTransactionParamsENS5_22PassTransactionResultsEEEEC2ERS6_RSB_i
72
};
73
74
template <typename Interface, typename Params, typename Results>
75
using ServerContext = ServerInvokeContext<ProxyServer<Interface>, ::capnp::CallContext<Params, Results>>;
76
77
template <>
78
struct ProxyClient<Thread> : public ProxyClientBase<Thread, ::capnp::Void>
79
{
80
    using ProxyClientBase::ProxyClientBase;
81
    // https://stackoverflow.com/questions/22357887/comparing-two-mapiterators-why-does-it-need-the-copy-constructor-of-stdpair
82
    ProxyClient(const ProxyClient&) = delete;
83
    ~ProxyClient();
84
85
    //! Reference to callback function that is run if there is a sudden
86
    //! disconnect and the Connection object is destroyed before this
87
    //! ProxyClient<Thread> object. The callback will destroy this object and
88
    //! remove its entry from the thread's request_threads or callback_threads
89
    //! map. It will also reset m_disconnect_cb so the destructor does not
90
    //! access it. In the normal case where there is no sudden disconnect, the
91
    //! destructor will unregister m_disconnect_cb so the callback is never run.
92
    //! Since this variable is accessed from multiple threads, accesses should
93
    //! be guarded with the associated Waiter::m_mutex.
94
    std::optional<CleanupIt> m_disconnect_cb;
95
};
96
97
template <>
98
struct ProxyServer<Thread> final : public Thread::Server
99
{
100
public:
101
    ProxyServer(Connection& connection, ThreadContext& thread_context, std::thread&& thread);
102
    ~ProxyServer();
103
    kj::Promise<void> getName(GetNameContext context) override;
104
105
    //! Run a callback function fn returning T on this thread. The function will
106
    //! be queued and executed as soon as the thread is idle, and when fn
107
    //! returns, the promise returned by this method will be fulfilled with the
108
    //! value fn returned.
109
    template<typename T, typename Fn>
110
    kj::Promise<T> post(Fn&& fn);
111
112
    EventLoopRef m_loop;
113
    ThreadContext& m_thread_context;
114
    std::thread m_thread;
115
    //! Promise signaled when m_thread_context.waiter is ready and there is no
116
    //! post() callback function waiting to execute.
117
    kj::Promise<void> m_thread_ready{kj::READY_NOW};
118
};
119
120
//! Handler for kj::TaskSet failed task events.
121
class LoggingErrorHandler : public kj::TaskSet::ErrorHandler
122
{
123
public:
124
0
    LoggingErrorHandler(EventLoop& loop) : m_loop(loop) {}
125
    void taskFailed(kj::Exception&& exception) override;
126
    EventLoop& m_loop;
127
};
128
129
//! Log flags. Update stringify function if changed!
130
enum class Log {
131
    Trace = 0,
132
    Debug,
133
    Info,
134
    Warning,
135
    Error,
136
    Raise,
137
};
138
139
kj::StringPtr KJ_STRINGIFY(Log flags);
140
141
struct LogMessage {
142
143
    //! Message to be logged
144
    std::string message;
145
146
    //! The severity level of this message
147
    Log level;
148
};
149
150
using LogFn = std::function<void(LogMessage)>;
151
152
struct LogOptions {
153
154
    //! External logging callback.
155
    LogFn log_fn;
156
157
    //! Maximum number of characters to use when representing
158
    //! request and response structs as strings.
159
    size_t max_chars{200};
160
161
    //! Messages with a severity level less than log_level will not be
162
    //! reported.
163
    Log log_level{Log::Trace};
164
};
165
166
class Logger
167
{
168
public:
169
0
    Logger(const LogOptions& options, Log log_level) : m_options(options), m_log_level(log_level) {}
170
171
    Logger(Logger&&) = delete;
172
    Logger& operator=(Logger&&) = delete;
173
    Logger(const Logger&) = delete;
174
    Logger& operator=(const Logger&) = delete;
175
176
    ~Logger() noexcept(false)
177
0
    {
178
0
        if (enabled()) m_options.log_fn({std::move(m_buffer).str(), m_log_level});
  Branch (178:13): [True: 0, False: 0]
179
0
    }
180
181
    template <typename T>
182
    friend Logger& operator<<(Logger& logger, T&& value)
183
0
    {
184
0
        if (logger.enabled()) logger.m_buffer << std::forward<T>(value);
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
  Branch (184:13): [True: 0, False: 0]
185
0
        return logger;
186
0
    }
Unexecuted instantiation: _ZN2mplsIPNS_15ProxyServerBaseIN4test4fuzz8messages16IpcFuzzInterfaceE21IpcFuzzImplementationEEEERNS_6LoggerESA_OT_
Unexecuted instantiation: _ZN2mplsIRA2_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEERNS_6LoggerES8_OT_
Unexecuted instantiation: _ZN2mplsIRA10_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRA3_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIPNS_15ProxyClientBaseIN4test4fuzz8messages16IpcFuzzInterfaceE21IpcFuzzImplementationEEEERNS_6LoggerESA_OT_
Unexecuted instantiation: _ZN2mplsIRA55_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRA13_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRA12_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRA68_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEERNS_6LoggerES9_OT_
Unexecuted instantiation: _ZN2mplsIPKcEERNS_6LoggerES4_OT_
Unexecuted instantiation: _ZN2mplsIRA19_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRA24_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRPKcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRA20_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRiEERNS_6LoggerES3_OT_
Unexecuted instantiation: _ZN2mplsIRA27_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRA15_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRKiEERNS_6LoggerES4_OT_
Unexecuted instantiation: _ZN2mplsIRA16_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRA31_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRA33_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIPNS_15ProxyClientBaseINS_6ThreadEN5capnp4VoidEEEEERNS_6LoggerES8_OT_
Unexecuted instantiation: _ZN2mplsIRA39_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRA29_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRA50_KcEERNS_6LoggerES5_OT_
Unexecuted instantiation: _ZN2mplsIRA21_KcEERNS_6LoggerES5_OT_
187
188
    template <typename T>
189
    friend Logger& operator<<(Logger&& logger, T&& value)
190
    {
191
        return logger << std::forward<T>(value);
192
    }
193
194
    explicit operator bool() const
195
0
    {
196
0
        return enabled();
197
0
    }
198
199
private:
200
    bool enabled() const
201
0
    {
202
0
        return m_options.log_fn && m_log_level >= m_options.log_level;
  Branch (202:16): [True: 0, False: 0]
  Branch (202:36): [True: 0, False: 0]
203
0
    }
204
205
    const LogOptions& m_options;
206
    Log m_log_level;
207
    std::ostringstream m_buffer;
208
};
209
210
0
#define MP_LOGPLAIN(loop, ...) if (mp::Logger logger{(loop).m_log_opts, __VA_ARGS__}; logger) logger
211
212
0
#define MP_LOG(loop, ...) MP_LOGPLAIN(loop, __VA_ARGS__) << "{" << LongThreadName((loop).m_exe_name) << "} "
213
214
std::string LongThreadName(const char* exe_name);
215
216
//! Event loop implementation.
217
//!
218
//! Cap'n Proto threading model is very simple: all I/O operations are
219
//! asynchronous and must be performed on a single thread. This includes:
220
//!
221
//! - Code starting an asynchronous operation (calling a function that returns a
222
//!   promise object)
223
//! - Code notifying that an asynchronous operation is complete (code using a
224
//!   fulfiller object)
225
//! - Code handling a completed operation (code chaining or waiting for a promise)
226
//!
227
//! All of this code needs to access shared state, and there is no mutex that
228
//! can be acquired to lock this state because Cap'n Proto
229
//! assumes it will only be accessed from one thread. So all this code needs to
230
//! actually run on one thread, and the EventLoop::loop() method is the entry point for
231
//! this thread. ProxyClient and ProxyServer objects that use other threads and
232
//! need to perform I/O operations post to this thread using EventLoop::post()
233
//! and EventLoop::sync() methods.
234
//!
235
//! Specifically, because ProxyClient methods can be called from arbitrary
236
//! threads, and ProxyServer methods can run on arbitrary threads, ProxyClient
237
//! methods use the EventLoop thread to send requests, and ProxyServer methods
238
//! use the thread to return results.
239
//!
240
//! Based on https://groups.google.com/d/msg/capnproto/TuQFF1eH2-M/g81sHaTAAQAJ
241
class EventLoop
242
{
243
public:
244
    //! Construct event loop object with default logging options.
245
    EventLoop(const char* exe_name, LogFn log_fn, void* context = nullptr)
246
0
        : EventLoop(exe_name, LogOptions{std::move(log_fn)}, context){}
247
248
    //! Construct event loop object with specified logging options.
249
    EventLoop(const char* exe_name, LogOptions log_opts, void* context = nullptr);
250
251
    //! Backwards-compatible constructor for previous (deprecated) logging callback signature
252
    EventLoop(const char* exe_name, std::function<void(bool, std::string)> old_callback, void* context = nullptr)
253
        : EventLoop(exe_name,
254
                LogFn{[old_callback = std::move(old_callback)](LogMessage log_data) {old_callback(log_data.level == Log::Raise, std::move(log_data.message));}},
255
0
                context){}
256
257
    ~EventLoop();
258
259
    //! Run event loop. Does not return until shutdown. This should only be
260
    //! called once from the m_thread_id thread. This will block until
261
    //! the m_num_refs reference count is 0.
262
    void loop();
263
264
    //! Run function on event loop thread. Does not return until function completes.
265
    //! Must be called while the loop() function is active.
266
    void post(kj::Function<void()> fn);
267
268
    //! Wrapper around EventLoop::post that takes advantage of the
269
    //! fact that callable will not go out of scope to avoid requirement that it
270
    //! be copyable.
271
    template <typename Callable>
272
    void sync(Callable&& callable)
273
0
    {
274
0
        post(std::forward<Callable>(callable));
275
0
    }
Unexecuted instantiation: _ZN2mp9EventLoop4syncIZZNS_15ProxyClientBaseIN4test4fuzz8messages16IpcFuzzInterfaceE21IpcFuzzImplementationEC1ENS6_6ClientEPNS_10ConnectionEbENKUlvE0_clEvEUlvE0_EEvOT_
Unexecuted instantiation: _ZN2mp9EventLoop4syncIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_9AddParamsENS7_10AddResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields1AELi1EEEJiEEENSL_INSM_INSN_1BELi1EEEJiEEENSL_INSM_INSN_6ResultELi2EEEJRiEEEEEEvRT_RKT0_DpOT1_EUlvE_EEvOSY_
Unexecuted instantiation: _ZN2mp9EventLoop4syncIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_18PassOutPointParamsENS7_19PassOutPointResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields3ArgELi17EEEJ9COutPointEEENSL_INSM_INSN_6ResultELi18EEEJRSQ_EEEEEEvRT_RKT0_DpOT1_EUlvE_EEvOSW_
Unexecuted instantiation: _ZN2mp9EventLoop4syncIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_21PassVectorUint8ParamsENS7_22PassVectorUint8ResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields3ArgELi17EEEJSt6vectorIhSaIhEEEEENSL_INSM_INSN_6ResultELi18EEEJRSS_EEEEEEvRT_RKT0_DpOT1_EUlvE_EEvOSY_
Unexecuted instantiation: _ZN2mp9EventLoop4syncIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_16PassScriptParamsENS7_17PassScriptResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields3ArgELi17EEEJ7CScriptEEENSL_INSM_INSN_6ResultELi18EEEJRSQ_EEEEEEvRT_RKT0_DpOT1_EUlvE_EEvOSW_
Unexecuted instantiation: _ZN2mp9EventLoop4syncIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_18PassUniValueParamsENS7_19PassUniValueResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields3ArgELi17EEEJ8UniValueEEENSL_INSM_INSN_6ResultELi18EEEJRSQ_EEEEEEvRT_RKT0_DpOT1_EUlvE_EEvOSW_
Unexecuted instantiation: _ZN2mp9EventLoop4syncIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_21PassTransactionParamsENS7_22PassTransactionResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields3ArgELi17EEEJSt10shared_ptrIK12CTransactionEEEENSL_INSM_INSN_6ResultELi18EEEJRST_EEEEEEvRT_RKT0_DpOT1_EUlvE_EEvOSZ_
Unexecuted instantiation: _ZN2mp9EventLoop4syncIZZNS_15ProxyClientBaseINS_6ThreadEN5capnp4VoidEEC1ENS3_6ClientEPNS_10ConnectionEbENKUlvE0_clEvEUlvE0_EEvOT_
Unexecuted instantiation: proxy.cpp:_ZN2mp9EventLoop4syncIZNS_11ProxyClientINS_6ThreadEED1EvE3$_0EEvOT_
276
277
    //! Register cleanup function to run on asynchronous worker thread without
278
    //! blocking the event loop thread.
279
    void addAsyncCleanup(std::function<void()> fn);
280
281
    //! Start asynchronous worker thread if necessary. This is only done if
282
    //! there are ProxyServerBase::m_impl objects that need to be destroyed
283
    //! asynchronously, without tying up the event loop thread. This can happen
284
    //! when an interface does not declare a destroy() method that would allow
285
    //! the client to wait for the destructor to finish and run it on a
286
    //! dedicated thread. It can also happen whenever this is a broken
287
    //! connection and the client is no longer around to call the destructors
288
    //! and the server objects need to be garbage collected. In both cases, it
289
    //! is important that ProxyServer::m_impl destructors do not run on the
290
    //! eventloop thread because they may need it to do I/O if they perform
291
    //! other IPC calls.
292
    void startAsyncThread() MP_REQUIRES(m_mutex);
293
294
    //! Check if loop should exit.
295
    bool done() const MP_REQUIRES(m_mutex);
296
297
    //! Process name included in thread names so combined debug output from
298
    //! multiple processes is easier to understand.
299
    const char* m_exe_name;
300
301
    //! ID of the event loop thread
302
    std::thread::id m_thread_id = std::this_thread::get_id();
303
304
    //! Handle of an async worker thread. Joined on destruction. Unset if async
305
    //! method has not been called.
306
    std::thread m_async_thread;
307
308
    //! Callback function to run on event loop thread during post() or sync() call.
309
    kj::Function<void()>* m_post_fn MP_GUARDED_BY(m_mutex) = nullptr;
310
311
    //! Callback functions to run on async thread.
312
    std::optional<CleanupList> m_async_fns MP_GUARDED_BY(m_mutex);
313
314
    //! Pipe read handle used to wake up the event loop thread.
315
    int m_wait_fd = -1;
316
317
    //! Pipe write handle used to wake up the event loop thread.
318
    int m_post_fd = -1;
319
320
    //! Number of EventLoopRef instances referencing this event loop. This is a
321
    //! sum of the number of client and server objects (Connection, ProxyClient,
322
    //! ProxyServer) using the loop, plus temporary references held while
323
    //! posting functions to the loop, plus any references held by external code
324
    //! to keep the loop running. The loop() method exits when this count drops
325
    //! to 0 (and m_async_fns is empty).
326
    int m_num_refs MP_GUARDED_BY(m_mutex) = 0;
327
328
    //! Mutex and condition variable used to post tasks to event loop and async
329
    //! thread.
330
    Mutex m_mutex;
331
    std::condition_variable m_cv;
332
333
    //! Capnp IO context.
334
    kj::AsyncIoContext m_io_context;
335
336
    //! Capnp error handler. Needs to outlive m_task_set.
337
    LoggingErrorHandler m_error_handler{*this};
338
339
    //! Capnp list of pending promises.
340
    std::unique_ptr<kj::TaskSet> m_task_set;
341
342
    //! List of connections.
343
    std::list<Connection> m_incoming_connections;
344
345
    //! Logging options
346
    LogOptions m_log_opts;
347
348
    //! External context pointer.
349
    void* m_context;
350
351
    //! Hook called when ProxyServer<ThreadMap>::makeThread() is called.
352
    std::function<void()> testing_hook_makethread;
353
354
    //! Hook called on the worker thread inside makeThread(), after the thread
355
    //! context is set up and thread_context promise is fulfilled, but before it
356
    //! starts waiting for requests.
357
    std::function<void()> testing_hook_makethread_created;
358
359
    //! Hook called on the worker thread when it starts to execute an async
360
    //! request. Used by tests to control timing or inject behavior at this
361
    //! point in execution.
362
    std::function<void()> testing_hook_async_request_start;
363
364
    //! Hook called on the worker thread just before returning results.
365
    std::function<void()> testing_hook_async_request_done;
366
367
    //! Hook called on the event loop thread when a client has connected.
368
    std::function<void()> testing_hook_connected;
369
370
    //! Hook called on the event loop thread when a client has disconnected.
371
    std::function<void()> testing_hook_disconnected;
372
};
373
374
//! Single element task queue used to handle recursive capnp calls. (If the
375
//! server makes a callback into the client in the middle of a request, while the client
376
//! thread is blocked waiting for server response, this is what allows the
377
//! client to run the request in the same thread, the same way code would run in a
378
//! single process, with the callback sharing the same thread stack as the original
379
//! call.) To support this, the clientInvoke function calls Waiter::wait() to
380
//! block the client IPC thread while initial request is in progress. Then if
381
//! there is a callback, it is executed with Waiter::post().
382
//!
383
//! The Waiter class is also used server-side by `ProxyServer<Thread>::post()`
384
//! to execute IPC calls on worker threads.
385
struct Waiter
386
{
387
0
    Waiter() = default;
388
389
    template <typename Fn>
390
    bool post(Fn&& fn)
391
    {
392
        const Lock lock(m_mutex);
393
        if (m_fn) return false;
394
        m_fn = std::forward<Fn>(fn);
395
        m_cv.notify_all();
396
        return true;
397
    }
398
399
    template <class Predicate>
400
    void wait(Lock& lock, Predicate pred) MP_REQUIRES(m_mutex)
401
0
    {
402
0
        m_cv.wait(lock.m_lock, [&]() MP_REQUIRES(m_mutex) {
403
            // Important for this to be "while (m_fn)", not "if (m_fn)" to avoid
404
            // a lost-wakeup bug. A new m_fn and m_cv notification might be sent
405
            // after the fn() call and before the lock.lock() call in this loop
406
            // in the case where a capnp response is sent and a brand new
407
            // request is immediately received.
408
0
            while (m_fn) {
  Branch (408:20): [True: 0, False: 0]
  Branch (408:20): [True: 0, False: 0]
  Branch (408:20): [True: 0, False: 0]
  Branch (408:20): [True: 0, False: 0]
  Branch (408:20): [True: 0, False: 0]
  Branch (408:20): [True: 0, False: 0]
  Branch (408:20): [True: 0, False: 0]
  Branch (408:20): [True: 0, False: 0]
409
0
                auto fn = std::move(*m_fn);
410
0
                m_fn.reset();
411
0
                Unlock(lock, fn);
412
0
            }
413
0
            const bool done = pred();
414
0
            return done;
415
0
        });
Unexecuted instantiation: _ZZN2mp6Waiter4waitIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_9AddParamsENS7_10AddResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields1AELi1EEEJiEEENSL_INSM_INSN_1BELi1EEEJiEEENSL_INSM_INSN_6ResultELi2EEEJRiEEEEEEvRT_RKT0_DpOT1_EUlvE0_EEvRNS_4LockESY_ENKUlvE_clEv
Unexecuted instantiation: _ZZN2mp6Waiter4waitIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_18PassOutPointParamsENS7_19PassOutPointResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields3ArgELi17EEEJ9COutPointEEENSL_INSM_INSN_6ResultELi18EEEJRSQ_EEEEEEvRT_RKT0_DpOT1_EUlvE0_EEvRNS_4LockESW_ENKUlvE_clEv
Unexecuted instantiation: _ZZN2mp6Waiter4waitIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_21PassVectorUint8ParamsENS7_22PassVectorUint8ResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields3ArgELi17EEEJSt6vectorIhSaIhEEEEENSL_INSM_INSN_6ResultELi18EEEJRSS_EEEEEEvRT_RKT0_DpOT1_EUlvE0_EEvRNS_4LockESY_ENKUlvE_clEv
Unexecuted instantiation: _ZZN2mp6Waiter4waitIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_16PassScriptParamsENS7_17PassScriptResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields3ArgELi17EEEJ7CScriptEEENSL_INSM_INSN_6ResultELi18EEEJRSQ_EEEEEEvRT_RKT0_DpOT1_EUlvE0_EEvRNS_4LockESW_ENKUlvE_clEv
Unexecuted instantiation: _ZZN2mp6Waiter4waitIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_18PassUniValueParamsENS7_19PassUniValueResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields3ArgELi17EEEJ8UniValueEEENSL_INSM_INSN_6ResultELi18EEEJRSQ_EEEEEEvRT_RKT0_DpOT1_EUlvE0_EEvRNS_4LockESW_ENKUlvE_clEv
Unexecuted instantiation: _ZZN2mp6Waiter4waitIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_21PassTransactionParamsENS7_22PassTransactionResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields3ArgELi17EEEJSt10shared_ptrIK12CTransactionEEEENSL_INSM_INSN_6ResultELi18EEEJRST_EEEEEEvRT_RKT0_DpOT1_EUlvE0_EEvRNS_4LockESZ_ENKUlvE_clEv
Unexecuted instantiation: proxy.cpp:_ZZN2mp6Waiter4waitIZZNS_11ProxyServerINS_9ThreadMapEE8makePoolEN5capnp11CallContextINS3_14MakePoolParamsENS3_15MakePoolResultsEEEENK3$_0clEvEUlvE_EEvRNS_4LockET_ENKUlvE_clEv
Unexecuted instantiation: proxy.cpp:_ZZN2mp6Waiter4waitIZZNS_11ProxyServerINS_9ThreadMapEE10makeThreadEN5capnp11CallContextINS3_16MakeThreadParamsENS3_17MakeThreadResultsEEEENK3$_0clEvEUlvE_EEvRNS_4LockET_ENKUlvE_clEv
416
0
    }
Unexecuted instantiation: _ZN2mp6Waiter4waitIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_9AddParamsENS7_10AddResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields1AELi1EEEJiEEENSL_INSM_INSN_1BELi1EEEJiEEENSL_INSM_INSN_6ResultELi2EEEJRiEEEEEEvRT_RKT0_DpOT1_EUlvE0_EEvRNS_4LockESY_
Unexecuted instantiation: _ZN2mp6Waiter4waitIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_18PassOutPointParamsENS7_19PassOutPointResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields3ArgELi17EEEJ9COutPointEEENSL_INSM_INSN_6ResultELi18EEEJRSQ_EEEEEEvRT_RKT0_DpOT1_EUlvE0_EEvRNS_4LockESW_
Unexecuted instantiation: _ZN2mp6Waiter4waitIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_21PassVectorUint8ParamsENS7_22PassVectorUint8ResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields3ArgELi17EEEJSt6vectorIhSaIhEEEEENSL_INSM_INSN_6ResultELi18EEEJRSS_EEEEEEvRT_RKT0_DpOT1_EUlvE0_EEvRNS_4LockESY_
Unexecuted instantiation: _ZN2mp6Waiter4waitIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_16PassScriptParamsENS7_17PassScriptResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields3ArgELi17EEEJ7CScriptEEENSL_INSM_INSN_6ResultELi18EEEJRSQ_EEEEEEvRT_RKT0_DpOT1_EUlvE0_EEvRNS_4LockESW_
Unexecuted instantiation: _ZN2mp6Waiter4waitIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_18PassUniValueParamsENS7_19PassUniValueResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields3ArgELi17EEEJ8UniValueEEENSL_INSM_INSN_6ResultELi18EEEJRSQ_EEEEEEvRT_RKT0_DpOT1_EUlvE0_EEvRNS_4LockESW_
Unexecuted instantiation: _ZN2mp6Waiter4waitIZNS_12clientInvokeINS_11ProxyClientIN4test4fuzz8messages16IpcFuzzInterfaceEEEMNS7_6ClientEFN5capnp7RequestINS7_21PassTransactionParamsENS7_22PassTransactionResultsEEEN2kj5MaybeINSA_11MessageSizeEEEEJNS_11ClientParamINS_8AccessorINS_15ipc_fuzz_fields3ArgELi17EEEJSt10shared_ptrIK12CTransactionEEEENSL_INSM_INSN_6ResultELi18EEEJRST_EEEEEEvRT_RKT0_DpOT1_EUlvE0_EEvRNS_4LockESZ_
Unexecuted instantiation: proxy.cpp:_ZN2mp6Waiter4waitIZZNS_11ProxyServerINS_9ThreadMapEE8makePoolEN5capnp11CallContextINS3_14MakePoolParamsENS3_15MakePoolResultsEEEENK3$_0clEvEUlvE_EEvRNS_4LockET_
Unexecuted instantiation: proxy.cpp:_ZN2mp6Waiter4waitIZZNS_11ProxyServerINS_9ThreadMapEE10makeThreadEN5capnp11CallContextINS3_16MakeThreadParamsENS3_17MakeThreadResultsEEEENK3$_0clEvEUlvE_EEvRNS_4LockET_
417
418
    //! Mutex mainly used internally by waiter class, but also used externally
419
    //! to guard access to related state. Specifically, since the thread_local
420
    //! ThreadContext struct owns a Waiter, the Waiter::m_mutex is used to guard
421
    //! access to other parts of the struct to avoid needing to deal with more
422
    //! mutexes than necessary. This mutex can be held at the same time as
423
    //! EventLoop::m_mutex as long as Waiter::mutex is locked first and
424
    //! EventLoop::m_mutex is locked second.
425
    Mutex m_mutex;
426
    std::condition_variable m_cv MP_GUARDED_BY(m_mutex);
427
    std::optional<kj::Function<void()>> m_fn MP_GUARDED_BY(m_mutex);
428
};
429
430
//! Object holding network & rpc state associated with either an incoming server
431
//! connection, or an outgoing client connection. It must be created and destroyed
432
//! on the event loop thread.
433
//! In addition to Cap'n Proto state, it also holds lists of callbacks to run
434
//! when the connection is closed.
435
class Connection
436
{
437
public:
438
    Connection(EventLoop& loop, kj::Own<kj::AsyncIoStream>&& stream_)
439
0
        : m_loop(loop), m_stream(kj::mv(stream_)),
440
0
          m_network(*m_stream, ::capnp::rpc::twoparty::Side::CLIENT, ::capnp::ReaderOptions()),
441
0
          m_rpc_system(::capnp::makeRpcClient(m_network)) {}
442
    Connection(EventLoop& loop,
443
        kj::Own<kj::AsyncIoStream>&& stream_,
444
        const std::function<::capnp::Capability::Client(Connection&)>& make_client)
445
0
        : m_loop(loop), m_stream(kj::mv(stream_)),
446
0
          m_network(*m_stream, ::capnp::rpc::twoparty::Side::SERVER, ::capnp::ReaderOptions()),
447
0
          m_rpc_system(::capnp::makeRpcServer(m_network, make_client(*this))) {}
448
449
    //! Run cleanup functions. Must be called from the event loop thread. First
450
    //! calls synchronous cleanup functions while blocked (to free capnp
451
    //! Capability::Client handles owned by ProxyClient objects), then schedules
452
    //! asynchronous cleanup functions to run in a worker thread (to run
453
    //! destructors of m_impl instances owned by ProxyServer objects).
454
    ~Connection();
455
456
    //! Register synchronous cleanup function to run on event loop thread (with
457
    //! access to capnp thread local variables) when disconnect() is called.
458
    //! any new i/o.
459
    CleanupIt addSyncCleanup(std::function<void()> fn);
460
    void removeSyncCleanup(CleanupIt it);
461
462
    //! Add disconnect handler.
463
    template <typename F>
464
    void onDisconnect(F&& f)
465
0
    {
466
        // Add disconnect handler to local TaskSet to ensure it is canceled and
467
        // will never run after connection object is destroyed. But when disconnect
468
        // handler fires, do not call the function f right away, instead add it
469
        // to the EventLoop TaskSet to avoid "Promise callback destroyed itself"
470
        // error in the typical case where f deletes this Connection object.
471
0
        m_on_disconnect.add(m_network.onDisconnect().then(
472
0
            [f = std::forward<F>(f), this]() mutable { m_loop->m_task_set->add(kj::evalLater(kj::mv(f))); }));
473
0
    }
474
475
    EventLoopRef m_loop;
476
    kj::Own<kj::AsyncIoStream> m_stream;
477
    LoggingErrorHandler m_error_handler{*m_loop};
478
    //! TaskSet used to cancel the m_network.onDisconnect() handler for remote
479
    //! disconnections, if the connection is closed locally first by deleting
480
    //! this Connection object.
481
    kj::TaskSet m_on_disconnect{m_error_handler};
482
    ::capnp::TwoPartyVatNetwork m_network;
483
    std::optional<::capnp::RpcSystem<::capnp::rpc::twoparty::VatId>> m_rpc_system;
484
485
    // ThreadMap interface client, used to create a remote server thread when an
486
    // client IPC call is being made for the first time from a new thread.
487
    ThreadMap::Client m_thread_map{nullptr};
488
489
    //! Collection of server-side IPC worker threads (ProxyServer<Thread> objects previously returned by
490
    //! ThreadMap.makeThread) used to service requests to clients.
491
    ::capnp::CapabilityServerSet<Thread> m_threads;
492
493
    //! A thread created by makePool with associated pending work queue. Vector is filled once by makePool() and never resized.
494
    struct PoolSlot {
495
        Thread::Client client;
496
        size_t depth{0};
497
    };
498
    std::vector<PoolSlot> m_thread_pool;
499
500
    //! Canceler for canceling promises that we want to discard when the
501
    //! connection is destroyed. This is used to interrupt method calls that are
502
    //! still executing at time of disconnection.
503
    kj::Canceler m_canceler;
504
505
    //! Cleanup functions to run if connection is broken unexpectedly.  List
506
    //! will be empty if all ProxyClient are destroyed cleanly before the
507
    //! connection is destroyed.
508
    CleanupList m_sync_cleanup_fns;
509
};
510
511
//! Vat id for server side of connection. Required argument to RpcSystem::bootStrap()
512
//!
513
//! "Vat" is Cap'n Proto nomenclature for a host of various objects that facilitates
514
//! bidirectional communication with other vats; it is often but not always 1-1 with
515
//! processes. Cap'n Proto doesn't reference clients or servers per se; instead everything
516
//! is just a vat.
517
//!
518
//! See also: https://github.com/capnproto/capnproto/blob/9021f0c722b36cb11e3690b0860939255ebad39c/c%2B%2B/src/capnp/rpc.capnp#L42-L56
519
struct ServerVatId
520
{
521
    ::capnp::word scratch[4]{};
522
    ::capnp::MallocMessageBuilder message{scratch};
523
    ::capnp::rpc::twoparty::VatId::Builder vat_id{message.getRoot<::capnp::rpc::twoparty::VatId>()};
524
0
    ServerVatId() { vat_id.setSide(::capnp::rpc::twoparty::Side::SERVER); }
525
};
526
527
template <typename Interface, typename Impl>
528
ProxyClientBase<Interface, Impl>::ProxyClientBase(typename Interface::Client client,
529
    Connection* connection,
530
    bool destroy_connection)
531
0
    : m_client(std::move(client)), m_context(connection)
532
533
0
{
534
0
    MP_LOG(*m_context.loop, Log::Debug) << "Creating " << CxxTypeName(*this) << " " << this;
535
    // Handler for the connection getting destroyed before this client object.
536
0
    auto disconnect_cb = m_context.connection->addSyncCleanup([this]() {
537
        // Release client capability by move-assigning to temporary.
538
0
        {
539
0
            typename Interface::Client(std::move(m_client));
540
0
        }
541
0
        Lock lock{m_context.loop->m_mutex};
542
0
        m_context.connection = nullptr;
543
0
    });
Unexecuted instantiation: _ZZN2mp15ProxyClientBaseIN4test4fuzz8messages16IpcFuzzInterfaceE21IpcFuzzImplementationEC1ENS4_6ClientEPNS_10ConnectionEbENKUlvE_clEv
Unexecuted instantiation: _ZZN2mp15ProxyClientBaseINS_6ThreadEN5capnp4VoidEEC1ENS1_6ClientEPNS_10ConnectionEbENKUlvE_clEv
544
545
    // Two shutdown sequences are supported:
546
    //
547
    // - A normal sequence where client proxy objects are deleted by external
548
    //   code that no longer needs them
549
    //
550
    // - A garbage collection sequence where the connection or event loop shuts
551
    //   down while external code is still holding client references.
552
    //
553
    // The first case is handled here when m_context.connection is not null. The
554
    // second case is handled by the disconnect_cb function, which sets
555
    // m_context.connection to null so nothing happens here.
556
0
    m_context.cleanup_fns.emplace_front([this, destroy_connection, disconnect_cb]{
557
0
    {
558
        // If the capnp interface defines a destroy method, call it to destroy
559
        // the remote object, waiting for it to be deleted server side. If the
560
        // capnp interface does not define a destroy method, this will just call
561
        // an empty stub defined in the ProxyClientBase class and do nothing.
562
        // Exceptions are caught and logged rather than propagated because
563
        // ~ProxyClientBase is noexcept and the peer may be gone by the time
564
        // this runs.
565
0
        if (kj::runCatchingExceptions([&]{ Sub::destroy(*this); }) != nullptr) {
Unexecuted instantiation: _ZZZN2mp15ProxyClientBaseIN4test4fuzz8messages16IpcFuzzInterfaceE21IpcFuzzImplementationEC1ENS4_6ClientEPNS_10ConnectionEbENKUlvE0_clEvENKUlvE_clEv
Unexecuted instantiation: _ZZZN2mp15ProxyClientBaseINS_6ThreadEN5capnp4VoidEEC1ENS1_6ClientEPNS_10ConnectionEbENKUlvE0_clEvENKUlvE_clEv
  Branch (565:13): [True: 0, False: 0]
  Branch (565:13): [True: 0, False: 0]
566
0
            MP_LOG(*m_context.loop, Log::Warning) << "Remote destroy call failed during cleanup. Continuing.";
567
0
        }
568
569
        // FIXME: Could just invoke removed addCleanup fn here instead of duplicating code
570
0
        m_context.loop->sync([&]() {
571
            // Remove disconnect callback on cleanup so it doesn't run and try
572
            // to access this object after it's destroyed. This call needs to
573
            // run inside loop->sync() on the event loop thread because
574
            // otherwise, if there were an ill-timed disconnect, the
575
            // onDisconnect handler could fire and delete the Connection object
576
            // before the removeSyncCleanup call.
577
0
            if (m_context.connection) m_context.connection->removeSyncCleanup(disconnect_cb);
  Branch (577:17): [True: 0, False: 0]
  Branch (577:17): [True: 0, False: 0]
578
579
            // Release client capability by move-assigning to temporary.
580
0
            {
581
0
                typename Interface::Client(std::move(m_client));
582
0
            }
583
0
            if (destroy_connection) {
  Branch (583:17): [True: 0, False: 0]
  Branch (583:17): [True: 0, False: 0]
584
0
                delete m_context.connection;
585
0
                m_context.connection = nullptr;
586
0
            }
587
0
        });
Unexecuted instantiation: _ZZZN2mp15ProxyClientBaseIN4test4fuzz8messages16IpcFuzzInterfaceE21IpcFuzzImplementationEC1ENS4_6ClientEPNS_10ConnectionEbENKUlvE0_clEvENKUlvE0_clEv
Unexecuted instantiation: _ZZZN2mp15ProxyClientBaseINS_6ThreadEN5capnp4VoidEEC1ENS1_6ClientEPNS_10ConnectionEbENKUlvE0_clEvENKUlvE0_clEv
588
0
    }
589
0
    });
Unexecuted instantiation: _ZZN2mp15ProxyClientBaseIN4test4fuzz8messages16IpcFuzzInterfaceE21IpcFuzzImplementationEC1ENS4_6ClientEPNS_10ConnectionEbENKUlvE0_clEv
Unexecuted instantiation: _ZZN2mp15ProxyClientBaseINS_6ThreadEN5capnp4VoidEEC1ENS1_6ClientEPNS_10ConnectionEbENKUlvE0_clEv
590
0
    Sub::construct(*this);
591
0
}
Unexecuted instantiation: _ZN2mp15ProxyClientBaseIN4test4fuzz8messages16IpcFuzzInterfaceE21IpcFuzzImplementationEC2ENS4_6ClientEPNS_10ConnectionEb
Unexecuted instantiation: _ZN2mp15ProxyClientBaseINS_6ThreadEN5capnp4VoidEEC2ENS1_6ClientEPNS_10ConnectionEb
592
593
template <typename Interface, typename Impl>
594
ProxyClientBase<Interface, Impl>::~ProxyClientBase() noexcept
595
0
{
596
0
    MP_LOG(*m_context.loop, Log::Debug) << "Cleaning up " << CxxTypeName(*this) << " " << this;
597
0
    CleanupRun(m_context.cleanup_fns);
598
0
    MP_LOG(*m_context.loop, Log::Debug) << "Destroying " << CxxTypeName(*this) << " " << this;
599
0
}
Unexecuted instantiation: _ZN2mp15ProxyClientBaseIN4test4fuzz8messages16IpcFuzzInterfaceE21IpcFuzzImplementationED2Ev
Unexecuted instantiation: _ZN2mp15ProxyClientBaseINS_6ThreadEN5capnp4VoidEED2Ev
600
601
template <typename Interface, typename Impl>
602
ProxyServerBase<Interface, Impl>::ProxyServerBase(std::shared_ptr<Impl> impl, Connection& connection)
603
0
    : m_impl(std::move(impl)), m_context(&connection)
604
0
{
605
0
    MP_LOG(*m_context.loop, Log::Debug) << "Creating " << CxxTypeName(*this) << " " << this;
606
0
    assert(m_impl);
  Branch (606:5): [True: 0, False: 0]
607
0
}
608
609
//! ProxyServer destructor, called from the EventLoop thread by Cap'n Proto
610
//! garbage collection code after there are no more references to this object.
611
//! This will typically happen when the corresponding ProxyClient object on the
612
//! other side of the connection is destroyed. It can also happen earlier if the
613
//! connection is broken or destroyed. In the latter case this destructor will
614
//! typically be called inside m_rpc_system.reset() call in the ~Connection
615
//! destructor while the Connection object still exists. However, because
616
//! ProxyServer objects are refcounted, and the Connection object could be
617
//! destroyed while asynchronous IPC calls are still in-flight, it's possible
618
//! for this destructor to be called after the Connection object no longer
619
//! exists, so it is NOT valid to dereference the m_context.connection pointer
620
//! from this function.
621
template <typename Interface, typename Impl>
622
ProxyServerBase<Interface, Impl>::~ProxyServerBase()
623
0
{
624
0
    MP_LOG(*m_context.loop, Log::Debug) << "Cleaning up " << CxxTypeName(*this) << " " << this;
625
0
    if (m_impl) {
  Branch (625:9): [True: 0, False: 0]
626
        // If impl is non-null at this point, it means no client is waiting for
627
        // the m_impl server object to be destroyed synchronously. This can
628
        // happen either if the interface did not define a "destroy" method (see
629
        // invokeDestroy method below), or if a destroy method was defined, but
630
        // the connection was broken before it could be called.
631
        //
632
        // In either case, be conservative and run the cleanup on an
633
        // asynchronous thread, to avoid destructors or cleanup functions
634
        // blocking or deadlocking the current EventLoop thread, since they
635
        // could be making IPC calls.
636
        //
637
        // Technically this is a little too conservative since if the interface
638
        // defines a "destroy" method, but the destroy method does not accept a
639
        // Context parameter specifying a worker thread, the cleanup method
640
        // would run on the EventLoop thread normally (when connection is
641
        // unbroken), but will not run on the EventLoop thread now (when
642
        // connection is broken). Probably some refactoring of the destructor
643
        // and invokeDestroy function is possible to make this cleaner and more
644
        // consistent.
645
0
        m_context.loop->addAsyncCleanup([impl=std::move(m_impl), fns=std::move(m_context.cleanup_fns)]() mutable {
646
0
            impl.reset();
647
0
            CleanupRun(fns);
648
0
        });
649
0
    }
650
0
    assert(m_context.cleanup_fns.empty());
  Branch (650:5): [True: 0, False: 0]
651
0
    MP_LOG(*m_context.loop, Log::Debug) << "Destroying " << CxxTypeName(*this) << " " << this;
652
0
}
653
654
//! If the capnp interface defined a special "destroy" method, as described the
655
//! ProxyClientBase class, this method will be called and synchronously destroy
656
//! m_impl before returning to the client.
657
//!
658
//! If the capnp interface does not define a "destroy" method, this will never
659
//! be called and the ~ProxyServerBase destructor will be responsible for
660
//! deleting m_impl asynchronously, whenever the ProxyServer object gets garbage
661
//! collected by Cap'n Proto.
662
//!
663
//! This method is called in the same way other proxy server methods are called,
664
//! via the serverInvoke function. Basically serverInvoke just calls this as a
665
//! substitute for a non-existent m_impl->destroy() method. If the destroy
666
//! method has any parameters or return values they will be handled in the
667
//! normal way by PassField/ReadField/BuildField functions. Particularly if a
668
//! Context.thread parameter was passed, this method will run on the worker
669
//! thread specified by the client. Otherwise it will run on the EventLoop
670
//! thread, like other server methods without an assigned thread.
671
template <typename Interface, typename Impl>
672
void ProxyServerBase<Interface, Impl>::invokeDestroy()
673
{
674
    m_impl.reset();
675
    CleanupRun(m_context.cleanup_fns);
676
}
677
678
//! Map from Connection to local or remote thread handle which will be used over
679
//! that connection. This map will typically only contain one entry, but can
680
//! contain multiple if a single thread makes IPC calls over multiple
681
//! connections. A std::optional value type is used to avoid the map needing to
682
//! be locked while ProxyClient<Thread> objects are constructed, see
683
//! ThreadContext "Synchronization note" below.
684
using ConnThreads = std::map<Connection*, std::optional<ProxyClient<Thread>>>;
685
using ConnThread = ConnThreads::iterator;
686
687
// Retrieve ProxyClient<Thread> object associated with this connection from a
688
// map, or create a new one and insert it into the map. Return map iterator and
689
// inserted bool.
690
std::tuple<ConnThread, bool> SetThread(GuardedRef<ConnThreads> threads, Connection* connection, const std::function<Thread::Client()>& make_thread);
691
692
//! The thread_local ThreadContext g_thread_context struct provides information
693
//! about individual threads and a way of communicating between them. Because
694
//! it's a thread local struct, each ThreadContext instance is initialized by
695
//! the thread that owns it.
696
//!
697
//! ThreadContext is used for any client threads created externally which make
698
//! IPC calls, and for server threads created by
699
//! ProxyServer<ThreadMap>::makeThread() which execute IPC calls for clients.
700
//!
701
//! In both cases, the struct holds information like the thread name, and a
702
//! Waiter object where the EventLoop can post incoming IPC requests to execute
703
//! on the thread. The struct also holds ConnThread maps associating the thread
704
//! with local and remote ProxyClient<Thread> objects.
705
struct ThreadContext
706
{
707
    //! Identifying string for debug.
708
    std::string thread_name;
709
710
    //! Waiter object used to allow remote clients to execute code on this
711
    //! thread. For server threads created by
712
    //! ProxyServer<ThreadMap>::makeThread(), this is initialized in that
713
    //! function. Otherwise, for client threads created externally, this is
714
    //! initialized the first time the thread tries to make an IPC call. Having
715
    //! a waiter is necessary for threads making IPC calls in case a server they
716
    //! are calling expects them to execute a callback during the call, before
717
    //! it sends a response.
718
    //!
719
    //! For IPC client threads, the Waiter pointer is never cleared and the Waiter
720
    //! just gets destroyed when the thread does. For server threads created by
721
    //! makeThread(), this pointer is set to null in the ~ProxyServer<Thread> as
722
    //! a signal for the thread to exit and destroy itself. In both cases, the
723
    //! same Waiter object is used across different calls and only created and
724
    //! destroyed once for the lifetime of the thread.
725
    std::unique_ptr<Waiter> waiter = nullptr;
726
727
    //! When client is making a request to a server, this is the
728
    //! `callbackThread` argument it passes in the request, used by the server
729
    //! in case it needs to make callbacks into the client that need to execute
730
    //! while the client is waiting. This will be set to a local thread object.
731
    //!
732
    //! Synchronization note: The callback_thread and request_thread maps are
733
    //! only ever accessed internally by this thread's destructor and externally
734
    //! by Cap'n Proto event loop threads. Since it's possible for IPC client
735
    //! threads to make calls over different connections that could have
736
    //! different event loops, these maps are guarded by Waiter::m_mutex in case
737
    //! different event loop threads add or remove map entries simultaneously.
738
    //! However, individual ProxyClient<Thread> objects in the maps will only be
739
    //! associated with one event loop and guarded by EventLoop::m_mutex. So
740
    //! Waiter::m_mutex does not need to be held while accessing individual
741
    //! ProxyClient<Thread> instances, and may even need to be released to
742
    //! respect lock order and avoid locking Waiter::m_mutex before
743
    //! EventLoop::m_mutex.
744
    ConnThreads callback_threads MP_GUARDED_BY(waiter->m_mutex);
745
746
    //! When client is making a request to a server, this is the `thread`
747
    //! argument it passes in the request, used to control which thread on
748
    //! server will be responsible for executing it. If client call is being
749
    //! made from a local thread, this will be a remote thread object returned
750
    //! by makeThread. If a client call is being made from a thread currently
751
    //! handling a server request, this will be set to the `callbackThread`
752
    //! request thread argument passed in that request.
753
    //!
754
    //! Synchronization note: \ref callback_threads note applies here as well.
755
    ConnThreads request_threads MP_GUARDED_BY(waiter->m_mutex);
756
757
    //! Whether this thread is a capnp event loop thread. Not really used except
758
    //! to assert false if there's an attempt to execute a blocking operation
759
    //! which could deadlock the thread.
760
    bool loop_thread = false;
761
};
762
763
template<typename T, typename Fn>
764
kj::Promise<T> ProxyServer<Thread>::post(Fn&& fn)
765
{
766
    auto ready = kj::newPromiseAndFulfiller<void>(); // Signaled when waiter is ready to post again.
767
    auto cancel_monitor_ptr = kj::heap<CancelMonitor>();
768
    CancelMonitor& cancel_monitor = *cancel_monitor_ptr;
769
    // Keep a reference to the ProxyServer<Thread> instance by assigning it to
770
    // the self variable. ProxyServer instances are reference-counted and if the
771
    // client drops its reference, this variable keeps the instance alive until
772
    // the thread finishes executing. The self variable needs to be destroyed on
773
    // the event loop thread so it is freed in a sync() call below.
774
    auto self = thisCap();
775
    auto ret = m_thread_ready.then([this, self = std::move(self), fn = std::forward<Fn>(fn), ready_fulfiller = kj::mv(ready.fulfiller), cancel_monitor_ptr = kj::mv(cancel_monitor_ptr)]() mutable {
776
        auto result = kj::newPromiseAndFulfiller<T>(); // Signaled when fn() is called, with its return value.
777
        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 {
778
            // Fulfill ready.promise now, as soon as the Waiter starts executing
779
            // this lambda, so the next ProxyServer<Thread>::post() call can
780
            // immediately call waiter->post(). It is important to do this
781
            // before calling fn() because fn() can make an IPC call back to the
782
            // client, which can make another IPC call to this server thread.
783
            // (This typically happens when IPC methods take std::function
784
            // parameters.) When this happens the second call to the server
785
            // thread should not be blocked waiting for the first call.
786
            m_loop->sync([ready_fulfiller = kj::mv(ready_fulfiller)]() mutable {
787
                ready_fulfiller->fulfill();
788
                ready_fulfiller = nullptr;
789
            });
790
            std::optional<T> result_value;
791
            kj::Maybe<kj::Exception> exception{kj::runCatchingExceptions([&]{ result_value.emplace(fn(*cancel_monitor_ptr)); })};
792
            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 {
793
                // Destroy CancelMonitor here before fulfilling or rejecting the
794
                // promise so it doesn't get triggered when the promise is
795
                // destroyed.
796
                cancel_monitor_ptr = nullptr;
797
                // Send results to the fulfiller. Technically it would be ok to
798
                // skip this if promise was canceled, but it's simpler to just
799
                // do it unconditionally.
800
                KJ_IF_MAYBE(e, exception) {
801
                    assert(!result_value);
802
                    result_fulfiller->reject(kj::mv(*e));
803
                } else {
804
                    assert(result_value);
805
                    result_fulfiller->fulfill(kj::mv(*result_value));
806
                    result_value.reset();
807
                }
808
                result_fulfiller = nullptr;
809
                // Use evalLater to destroy the ProxyServer<Thread> self
810
                // reference, if it is the last reference, because the
811
                // ProxyServer<Thread> destructor needs to join the thread,
812
                // which can't happen until this sync() block has exited.
813
                m_loop->m_task_set->add(kj::evalLater([self = kj::mv(self)] {}));
814
            });
815
        });
816
        // Assert that calling Waiter::post did not fail. It could only return
817
        // false if a new function was posted before the previous one finished
818
        // executing, but new functions are only posted when m_thread_ready is
819
        // signaled, so this should never happen.
820
        assert(posted);
821
        return kj::mv(result.promise);
822
    }).attach(kj::heap<CancelProbe>(cancel_monitor));
823
    m_thread_ready = kj::mv(ready.promise);
824
    return ret;
825
}
826
827
//! Given stream file descriptor, make a new ProxyClient object to send requests
828
//! over the stream. Also create a new Connection object embedded in the
829
//! client that is freed when the client is closed.
830
template <typename InitInterface>
831
std::unique_ptr<ProxyClient<InitInterface>> ConnectStream(EventLoop& loop, int fd)
832
{
833
    typename InitInterface::Client init_client(nullptr);
834
    std::unique_ptr<Connection> connection;
835
    loop.sync([&] {
836
        auto stream =
837
            loop.m_io_context.lowLevelProvider->wrapSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP);
838
        connection = std::make_unique<Connection>(loop, kj::mv(stream));
839
        init_client = connection->m_rpc_system->bootstrap(ServerVatId().vat_id).castAs<InitInterface>();
840
        Connection* connection_ptr = connection.get();
841
        connection->onDisconnect([&loop, connection_ptr] {
842
            MP_LOG(loop, Log::Warning) << "IPC client: unexpected network disconnect.";
843
            delete connection_ptr;
844
        });
845
    });
846
    return std::make_unique<ProxyClient<InitInterface>>(
847
        kj::mv(init_client), connection.release(), /* destroy_connection= */ true);
848
}
849
850
//! Given stream and init objects, construct a new ProxyServer object that
851
//! handles requests from the stream by calling the init object. Embed the
852
//! ProxyServer in a Connection object that is stored and erased if
853
//! disconnected. This should be called from the event loop thread.
854
template <typename InitInterface, typename InitImpl, typename OnDisconnect>
855
void _Serve(EventLoop& loop, kj::Own<kj::AsyncIoStream>&& stream, InitImpl& init, OnDisconnect&& on_disconnect)
856
{
857
    loop.m_incoming_connections.emplace_front(loop, kj::mv(stream), [&](Connection& connection) {
858
        // Disable deleter so proxy server object doesn't attempt to delete the
859
        // init implementation when the proxy client is destroyed or
860
        // disconnected.
861
        return kj::heap<ProxyServer<InitInterface>>(std::shared_ptr<InitImpl>(&init, [](InitImpl*){}), connection);
862
    });
863
    auto it = loop.m_incoming_connections.begin();
864
    MP_LOG(loop, Log::Info) << "IPC server: socket connected.";
865
    if (loop.testing_hook_connected) loop.testing_hook_connected();
866
    it->onDisconnect([&loop, it, on_disconnect = std::forward<OnDisconnect>(on_disconnect)]() mutable {
867
        MP_LOG(loop, Log::Info) << "IPC server: socket disconnected.";
868
        loop.m_incoming_connections.erase(it);
869
        on_disconnect();
870
        if (loop.testing_hook_disconnected) loop.testing_hook_disconnected();
871
    });
872
}
873
874
struct Listener
875
{
876
    explicit Listener(kj::Own<kj::ConnectionReceiver>&& receiver, std::optional<size_t> max_connections)
877
0
        : m_receiver(kj::mv(receiver)), m_max_connections(max_connections) {}
878
879
    bool atCapacity() const
880
0
    {
881
0
        return m_max_connections && m_active_connections >= *m_max_connections;
882
0
    }
883
884
    kj::Own<kj::ConnectionReceiver> m_receiver;
885
    std::optional<size_t> m_max_connections;
886
    size_t m_active_connections{0};
887
};
888
889
template <typename InitInterface, typename InitImpl>
890
void _Listen(const std::shared_ptr<Listener>& listener, EventLoop& loop, InitImpl& init)
891
{
892
    if (listener->atCapacity()) return;
893
894
    auto* receiver = listener->m_receiver.get();
895
    loop.m_task_set->add(receiver->accept().then(
896
        [&loop, &init, listener](kj::Own<kj::AsyncIoStream>&& stream) {
897
            ++listener->m_active_connections;
898
            _Serve<InitInterface>(loop, kj::mv(stream), init, [&loop, &init, listener] {
899
                const bool resume_accept{listener->atCapacity()};
900
                assert(listener->m_active_connections > 0);
901
                --listener->m_active_connections;
902
                if (resume_accept) _Listen<InitInterface>(listener, loop, init);
903
            });
904
            _Listen<InitInterface>(listener, loop, init);
905
        }));
906
}
907
908
//! Given stream file descriptor and an init object, handle requests on the
909
//! stream by calling methods on the Init object.
910
template <typename InitInterface, typename InitImpl>
911
void ServeStream(EventLoop& loop, int fd, InitImpl& init)
912
{
913
    _Serve<InitInterface>(
914
        loop,
915
        loop.m_io_context.lowLevelProvider->wrapSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP),
916
        init,
917
        [] {});
918
}
919
920
//! Given listening socket file descriptor and an init object, handle incoming
921
//! connections and requests by calling methods on the Init object.
922
template <typename InitInterface, typename InitImpl>
923
void ListenConnections(EventLoop& loop, int fd, InitImpl& init, std::optional<size_t> max_connections = std::nullopt)
924
{
925
    loop.sync([&]() {
926
        auto listener{std::make_shared<Listener>(
927
            loop.m_io_context.lowLevelProvider->wrapListenSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP),
928
            max_connections)};
929
        _Listen<InitInterface>(listener, loop, init);
930
    });
931
}
932
933
extern thread_local ThreadContext g_thread_context; // NOLINT(bitcoin-nontrivial-threadlocal)
934
// Silence nonstandard bitcoin tidy error "Variable with non-trivial destructor
935
// cannot be thread_local" which should not be a problem on modern platforms, and
936
// could lead to a small memory leak at worst on older ones.
937
938
} // namespace mp
939
940
#endif // MP_PROXY_IO_H