/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 |