/tmp/bitcoin/src/ipc/libmultiprocess/src/mp/proxy.cpp
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 | | #include <mp/proxy.h> |
6 | | |
7 | | #include <mp/proxy-io.h> |
8 | | #include <mp/proxy-types.h> |
9 | | #include <mp/proxy.capnp.h> |
10 | | #include <mp/type-threadmap.h> |
11 | | #include <mp/util.h> |
12 | | |
13 | | #include <atomic> |
14 | | #include <capnp/capability.h> |
15 | | #include <capnp/common.h> // IWYU pragma: keep |
16 | | #include <capnp/rpc.h> |
17 | | #include <condition_variable> |
18 | | #include <functional> |
19 | | #include <future> |
20 | | #include <kj/async.h> |
21 | | #include <kj/async-io.h> |
22 | | #include <kj/async-prelude.h> |
23 | | #include <kj/common.h> |
24 | | #include <kj/debug.h> |
25 | | #include <kj/exception.h> |
26 | | #include <kj/function.h> |
27 | | #include <kj/memory.h> |
28 | | #include <kj/string.h> |
29 | | #include <cstdint> |
30 | | #include <map> |
31 | | #include <memory> |
32 | | #include <optional> |
33 | | #include <stdexcept> |
34 | | #include <string> |
35 | | #include <sys/socket.h> |
36 | | #include <thread> |
37 | | #include <tuple> |
38 | | #include <unistd.h> |
39 | | #include <utility> |
40 | | #include <vector> |
41 | | |
42 | | namespace mp { |
43 | | |
44 | | thread_local ThreadContext g_thread_context; // NOLINT(bitcoin-nontrivial-threadlocal) |
45 | | |
46 | | void LoggingErrorHandler::taskFailed(kj::Exception&& exception) |
47 | 0 | { |
48 | 0 | KJ_LOG(ERROR, "Uncaught exception in daemonized task.", exception); |
49 | 0 | MP_LOG(m_loop, Log::Error) << "Uncaught exception in daemonized task."; |
50 | 0 | } |
51 | | |
52 | 333 | EventLoopRef::EventLoopRef(EventLoop& loop, Lock* lock) : m_loop(&loop), m_lock(lock) |
53 | 333 | { |
54 | 333 | auto loop_lock{PtrOrValue{m_lock, m_loop->m_mutex}}; |
55 | 333 | loop_lock->assert_locked(m_loop->m_mutex); |
56 | 333 | m_loop->m_num_refs += 1; |
57 | 333 | } |
58 | | |
59 | | // Due to the conditionals in this function, MP_NO_TSA is required to avoid |
60 | | // error "error: mutex 'loop_lock' is not held on every path through here |
61 | | // [-Wthread-safety-analysis]" |
62 | | void EventLoopRef::reset(bool relock) MP_NO_TSA |
63 | 348 | { |
64 | 348 | if (auto* loop{m_loop}) { |
65 | 333 | m_loop = nullptr; |
66 | 333 | auto loop_lock{PtrOrValue{m_lock, loop->m_mutex}}; |
67 | 333 | loop_lock->assert_locked(loop->m_mutex); |
68 | 333 | assert(loop->m_num_refs > 0); |
69 | 333 | loop->m_num_refs -= 1; |
70 | 333 | if (loop->done()) { |
71 | 8 | loop->m_cv.notify_all(); |
72 | 8 | int post_fd{loop->m_post_fd}; |
73 | 8 | loop_lock->unlock(); |
74 | 8 | char buffer = 0; |
75 | 8 | KJ_SYSCALL(write(post_fd, &buffer, 1)); // NOLINT(bugprone-suspicious-semicolon) |
76 | | // By default, do not try to relock `loop_lock` after writing, |
77 | | // because the event loop could wake up and destroy itself and the |
78 | | // mutex might no longer exist. |
79 | 8 | if (relock) loop_lock->lock(); |
80 | 8 | } |
81 | 333 | } |
82 | 348 | } |
83 | | |
84 | 88 | ProxyContext::ProxyContext(Connection* connection) : connection(connection), loop{*connection->m_loop} {} |
85 | | |
86 | | Connection::~Connection() |
87 | 22 | { |
88 | | // Connection destructor is always called on the event loop thread. If this |
89 | | // is a local disconnect, it will trigger I/O, so this needs to run on the |
90 | | // event loop thread, and if there was a remote disconnect, this is called |
91 | | // by an onDisconnect callback directly from the event loop thread. |
92 | 22 | assert(std::this_thread::get_id() == m_loop->m_thread_id); |
93 | | |
94 | | // Try to cancel any calls that may be executing. |
95 | 22 | m_canceler.cancel("Interrupted by disconnect"); |
96 | | |
97 | | // Shut down RPC system first, since this will garbage collect any |
98 | | // ProxyServer objects that were not freed before the connection was closed. |
99 | | // Typically all ProxyServer objects associated with this connection will be |
100 | | // freed before this call returns. However that will not be the case if |
101 | | // there are asynchronous IPC calls over this connection still currently |
102 | | // executing. In that case, Cap'n Proto will destroy the ProxyServer objects |
103 | | // after the calls finish. |
104 | 22 | m_rpc_system.reset(); |
105 | | |
106 | | // ProxyClient cleanup handlers are in sync list, and ProxyServer cleanup |
107 | | // handlers are in the async list. |
108 | | // |
109 | | // The ProxyClient cleanup handlers are synchronous because they are fast |
110 | | // and don't do anything besides release capnp resources and reset state so |
111 | | // future calls to client methods immediately throw exceptions instead of |
112 | | // trying to communicate across the socket. The synchronous callbacks set |
113 | | // ProxyClient capability pointers to null, so new method calls on client |
114 | | // objects fail without triggering i/o or relying on event loop which may go |
115 | | // out of scope or trigger obscure capnp i/o errors. |
116 | | // |
117 | | // The ProxyServer cleanup handlers call user defined destructors on the server |
118 | | // object, which can run arbitrary blocking bitcoin code so they have to run |
119 | | // asynchronously in a different thread. The asynchronous cleanup functions |
120 | | // intentionally aren't started until after the synchronous cleanup |
121 | | // functions run, so client objects are fully disconnected before bitcoin |
122 | | // code in the destructors are run. This way if the bitcoin code tries to |
123 | | // make client requests the requests will just fail immediately instead of |
124 | | // sending i/o or accessing the event loop. |
125 | | // |
126 | | // The context where Connection objects are destroyed and this destructor is invoked |
127 | | // is different depending on whether this is an outgoing connection being used |
128 | | // to make an Init.makeX call() (e.g. Init.makeNode or Init.makeWalletClient) or an incoming |
129 | | // connection implementing the Init interface and handling the Init.makeX() calls. |
130 | | // |
131 | | // Either way when a connection is closed, capnp behavior is to call all |
132 | | // ProxyServer object destructors first, and then trigger an onDisconnect |
133 | | // callback. |
134 | | // |
135 | | // On incoming side of the connection, the onDisconnect callback is written |
136 | | // to delete the Connection object from the m_incoming_connections and call |
137 | | // this destructor which calls Connection::disconnect. |
138 | | // |
139 | | // On the outgoing side, the Connection object is owned by top level client |
140 | | // object client, which onDisconnect handler doesn't have ready access to, |
141 | | // so onDisconnect handler just calls Connection::disconnect directly |
142 | | // instead. |
143 | | // |
144 | | // Either way disconnect code runs in the event loop thread and called both |
145 | | // on clean and unclean shutdowns. In unclean shutdown case when the |
146 | | // connection is broken, sync and async cleanup lists will be filled with |
147 | | // callbacks. In the clean shutdown case both lists will be empty. |
148 | 22 | Lock lock{m_loop->m_mutex}; |
149 | 42 | while (!m_sync_cleanup_fns.empty()) { |
150 | 20 | CleanupList fn; |
151 | 20 | fn.splice(fn.begin(), m_sync_cleanup_fns, m_sync_cleanup_fns.begin()); |
152 | 20 | Unlock(lock, fn.front()); |
153 | 20 | } |
154 | 22 | } |
155 | | |
156 | | CleanupIt Connection::addSyncCleanup(std::function<void()> fn) |
157 | 113 | { |
158 | 113 | const Lock lock(m_loop->m_mutex); |
159 | | // Add cleanup callbacks to the front of list, so sync cleanup functions run |
160 | | // in LIFO order. This is a good approach because sync cleanup functions are |
161 | | // added as client objects are created, and it is natural to clean up |
162 | | // objects in the reverse order they were created. In practice, however, |
163 | | // order should not be significant because the cleanup callbacks run |
164 | | // synchronously in a single batch when the connection is broken, and they |
165 | | // only reset the connection pointers in the client objects without actually |
166 | | // deleting the client objects. |
167 | 113 | return m_sync_cleanup_fns.emplace(m_sync_cleanup_fns.begin(), std::move(fn)); |
168 | 113 | } |
169 | | |
170 | | void Connection::removeSyncCleanup(CleanupIt it) |
171 | 93 | { |
172 | | // Require cleanup functions to be removed on the event loop thread to avoid |
173 | | // needing to deal with them being removed in the middle of a disconnect. |
174 | 93 | assert(std::this_thread::get_id() == m_loop->m_thread_id); |
175 | 93 | const Lock lock(m_loop->m_mutex); |
176 | 93 | m_sync_cleanup_fns.erase(it); |
177 | 93 | } |
178 | | |
179 | | void EventLoop::addAsyncCleanup(std::function<void()> fn) |
180 | 15 | { |
181 | 15 | const Lock lock(m_mutex); |
182 | | // Add async cleanup callbacks to the back of the list. Unlike the sync |
183 | | // cleanup list, this list order is more significant because it determines |
184 | | // the order server objects are destroyed when there is a sudden disconnect, |
185 | | // and it is possible objects may need to be destroyed in a certain order. |
186 | | // This function is called in ProxyServerBase destructors, and since capnp |
187 | | // destroys ProxyServer objects in LIFO order, we should preserve this |
188 | | // order, and add cleanup callbacks to the end of the list so they can be |
189 | | // run starting from the beginning of the list. |
190 | | // |
191 | | // In bitcoin core, running these callbacks in the right order is |
192 | | // particularly important for the wallet process, because it uses blocking |
193 | | // shared_ptrs and requires Chain::Notification pointers owned by the node |
194 | | // process to be destroyed before the WalletLoader objects owned by the node |
195 | | // process, otherwise shared pointer counts of the CWallet objects (which |
196 | | // inherit from Chain::Notification) will not be 1 when WalletLoader |
197 | | // destructor runs and it will wait forever for them to be released. |
198 | 15 | m_async_fns->emplace_back(std::move(fn)); |
199 | 15 | startAsyncThread(); |
200 | 15 | } |
201 | | |
202 | | EventLoop::EventLoop(const char* exe_name, LogOptions log_opts, void* context) |
203 | 8 | : m_exe_name(exe_name), |
204 | 8 | m_io_context(kj::setupAsyncIo()), |
205 | 8 | m_task_set(new kj::TaskSet(m_error_handler)), |
206 | 8 | m_log_opts(std::move(log_opts)), |
207 | 8 | m_context(context) |
208 | 8 | { |
209 | 8 | int fds[2]; |
210 | 8 | KJ_SYSCALL(socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); |
211 | 8 | m_wait_fd = fds[0]; |
212 | 8 | m_post_fd = fds[1]; |
213 | 8 | } |
214 | | |
215 | | EventLoop::~EventLoop() |
216 | 8 | { |
217 | 8 | if (m_async_thread.joinable()) m_async_thread.join(); |
218 | 8 | const Lock lock(m_mutex); |
219 | 8 | KJ_ASSERT(m_post_fn == nullptr); |
220 | 8 | KJ_ASSERT(!m_async_fns); |
221 | 8 | KJ_ASSERT(m_wait_fd == -1); |
222 | 8 | KJ_ASSERT(m_post_fd == -1); |
223 | 8 | KJ_ASSERT(m_num_refs == 0); |
224 | | |
225 | | // Spin event loop. wait for any promises triggered by RPC shutdown. |
226 | | // auto cleanup = kj::evalLater([]{}); |
227 | | // cleanup.wait(m_io_context.waitScope); |
228 | 8 | } |
229 | | |
230 | | void EventLoop::loop() |
231 | 8 | { |
232 | 8 | assert(!g_thread_context.loop_thread); |
233 | 8 | g_thread_context.loop_thread = true; |
234 | 8 | KJ_DEFER(g_thread_context.loop_thread = false); |
235 | | |
236 | 8 | { |
237 | 8 | const Lock lock(m_mutex); |
238 | 8 | assert(!m_async_fns); |
239 | 8 | m_async_fns.emplace(); |
240 | 8 | } |
241 | | |
242 | 0 | kj::Own<kj::AsyncIoStream> wait_stream{ |
243 | 8 | m_io_context.lowLevelProvider->wrapSocketFd(m_wait_fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP)}; |
244 | 8 | int post_fd{m_post_fd}; |
245 | 8 | char buffer = 0; |
246 | 190 | for (;;) { |
247 | 190 | const size_t read_bytes = wait_stream->read(&buffer, 0, 1).wait(m_io_context.waitScope); |
248 | 190 | if (read_bytes != 1) throw std::logic_error("EventLoop wait_stream closed unexpectedly"); |
249 | 190 | Lock lock(m_mutex); |
250 | 190 | if (m_post_fn) { |
251 | | // m_post_fn throwing is never expected. If it does happen, the caller |
252 | | // of EventLoop::post() will return without any indication of failure, |
253 | | // which will likely cause other bugs. Log the error and continue. |
254 | 182 | KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() MP_REQUIRES(m_mutex) { Unlock(lock, *m_post_fn); })) { |
255 | 0 | MP_LOG(*this, Log::Error) << "EventLoop: m_post_fn threw: " << kj::str(*exception).cStr(); |
256 | 0 | } |
257 | 182 | m_post_fn = nullptr; |
258 | 182 | m_cv.notify_all(); |
259 | 182 | } else if (done()) { |
260 | | // Intentionally do not break if m_post_fn was set, even if done() |
261 | | // would return true, to ensure that the EventLoopRef write(post_fd) |
262 | | // call always succeeds and the loop does not exit between the time |
263 | | // that the done condition is set and the write call is made. |
264 | 8 | break; |
265 | 8 | } |
266 | 190 | } |
267 | 8 | MP_LOG(*this, Log::Info) << "EventLoop::loop done, cancelling event listeners."; |
268 | 8 | m_task_set.reset(); |
269 | 8 | MP_LOG(*this, Log::Info) << "EventLoop::loop bye."; |
270 | 8 | wait_stream = nullptr; |
271 | 8 | KJ_SYSCALL(::close(post_fd)); |
272 | 8 | const Lock lock(m_mutex); |
273 | 8 | m_wait_fd = -1; |
274 | 8 | m_post_fd = -1; |
275 | 8 | m_async_fns.reset(); |
276 | 8 | m_cv.notify_all(); |
277 | 8 | } |
278 | | |
279 | | void EventLoop::post(kj::Function<void()> fn) |
280 | 254 | { |
281 | 254 | if (std::this_thread::get_id() == m_thread_id) { |
282 | 72 | fn(); |
283 | 72 | return; |
284 | 72 | } |
285 | 182 | Lock lock(m_mutex); |
286 | 182 | EventLoopRef ref(*this, &lock); |
287 | 182 | m_cv.wait(lock.m_lock, [this]() MP_REQUIRES(m_mutex) { return m_post_fn == nullptr; }); |
288 | 182 | m_post_fn = &fn; |
289 | 182 | int post_fd{m_post_fd}; |
290 | 182 | Unlock(lock, [&] { |
291 | 182 | char buffer = 0; |
292 | 182 | KJ_SYSCALL(write(post_fd, &buffer, 1)); |
293 | 182 | }); |
294 | 365 | m_cv.wait(lock.m_lock, [this, &fn]() MP_REQUIRES(m_mutex) { return m_post_fn != &fn; }); |
295 | 182 | } |
296 | | |
297 | | void EventLoop::startAsyncThread() |
298 | 15 | { |
299 | 15 | assert (std::this_thread::get_id() == m_thread_id); |
300 | 15 | if (m_async_thread.joinable()) { |
301 | | // Notify to wake up the async thread if it is already running. |
302 | 11 | m_cv.notify_all(); |
303 | 11 | } else if (!m_async_fns->empty()) { |
304 | 4 | m_async_thread = std::thread([this] { |
305 | 4 | Lock lock(m_mutex); |
306 | 138 | while (m_async_fns) { |
307 | 134 | if (!m_async_fns->empty()) { |
308 | 15 | EventLoopRef ref{*this, &lock}; |
309 | 15 | const std::function<void()> fn = std::move(m_async_fns->front()); |
310 | 15 | m_async_fns->pop_front(); |
311 | 15 | Unlock(lock, fn); |
312 | | // Important to relock because of the wait() call below. |
313 | 15 | ref.reset(/*relock=*/true); |
314 | | // Continue without waiting in case there are more async_fns |
315 | 15 | continue; |
316 | 15 | } |
317 | 119 | m_cv.wait(lock.m_lock); |
318 | 119 | } |
319 | 4 | }); |
320 | 4 | } |
321 | 15 | } |
322 | | |
323 | | bool EventLoop::done() const |
324 | 341 | { |
325 | 341 | assert(m_num_refs >= 0); |
326 | 341 | return m_num_refs == 0 && m_async_fns->empty(); |
327 | 341 | } |
328 | | |
329 | | std::tuple<ConnThread, bool> SetThread(GuardedRef<ConnThreads> threads, Connection* connection, const std::function<Thread::Client()>& make_thread) |
330 | 78 | { |
331 | 78 | assert(std::this_thread::get_id() == connection->m_loop->m_thread_id); |
332 | 78 | ConnThread thread; |
333 | 78 | bool inserted; |
334 | 78 | { |
335 | 78 | const Lock lock(threads.mutex); |
336 | 78 | std::tie(thread, inserted) = threads.ref.try_emplace(connection); |
337 | 78 | } |
338 | 78 | if (inserted) { |
339 | 46 | thread->second.emplace(make_thread(), connection, /* destroy_connection= */ false); |
340 | 46 | thread->second->m_disconnect_cb = connection->addSyncCleanup([threads, thread] { |
341 | | // Note: it is safe to use the `thread` iterator in this cleanup |
342 | | // function, because the iterator would only be invalid if the map entry |
343 | | // was removed, and if the map entry is removed the ProxyClient<Thread> |
344 | | // destructor unregisters the cleanup. |
345 | | |
346 | | // Connection is being destroyed before thread client is, so reset |
347 | | // thread client m_disconnect_cb member so thread client destructor does not |
348 | | // try to unregister this callback after connection is destroyed. |
349 | 20 | thread->second->m_disconnect_cb.reset(); |
350 | | |
351 | | // Remove connection pointer about to be destroyed from the map |
352 | 20 | const Lock lock(threads.mutex); |
353 | 20 | threads.ref.erase(thread); |
354 | 20 | }); |
355 | 46 | } |
356 | 78 | return {thread, inserted}; |
357 | 78 | } |
358 | | |
359 | | ProxyClient<Thread>::~ProxyClient() |
360 | 46 | { |
361 | | // If thread is being destroyed before connection is destroyed, remove the |
362 | | // cleanup callback that was registered to handle the connection being |
363 | | // destroyed before the thread being destroyed. |
364 | 46 | if (m_disconnect_cb) { |
365 | | // Remove disconnect callback on the event loop thread with |
366 | | // loop->sync(), so if the connection is broken there is not a race |
367 | | // between this thread trying to remove the callback and the disconnect |
368 | | // handler attempting to call it. |
369 | 26 | m_context.loop->sync([&]() { |
370 | 26 | if (m_disconnect_cb) { |
371 | 26 | m_context.connection->removeSyncCleanup(*m_disconnect_cb); |
372 | 26 | } |
373 | 26 | }); |
374 | 26 | } |
375 | 46 | } |
376 | | |
377 | | ProxyServer<Thread>::ProxyServer(Connection& connection, ThreadContext& thread_context, std::thread&& thread) |
378 | 20 | : m_loop{*connection.m_loop}, m_thread_context(thread_context), m_thread(std::move(thread)) |
379 | 20 | { |
380 | 20 | assert(m_thread_context.waiter.get() != nullptr); |
381 | 20 | } Unexecuted instantiation: mp::ProxyServer<mp::Thread>::ProxyServer(mp::Connection&, mp::ThreadContext&, std::thread&&) mp::ProxyServer<mp::Thread>::ProxyServer(mp::Connection&, mp::ThreadContext&, std::thread&&) Line | Count | Source | 378 | 20 | : m_loop{*connection.m_loop}, m_thread_context(thread_context), m_thread(std::move(thread)) | 379 | 20 | { | 380 | | assert(m_thread_context.waiter.get() != nullptr); | 381 | 20 | } |
|
382 | | |
383 | | ProxyServer<Thread>::~ProxyServer() |
384 | 20 | { |
385 | 20 | if (!m_thread.joinable()) return; |
386 | | // Stop async thread and wait for it to exit. Need to wait because the |
387 | | // m_thread handle needs to outlive the thread to avoid "terminate called |
388 | | // without an active exception" error. An alternative to waiting would be |
389 | | // detach the thread, but this would introduce nondeterminism which could |
390 | | // make code harder to debug or extend. |
391 | 20 | assert(m_thread_context.waiter.get()); |
392 | 10 | std::unique_ptr<Waiter> waiter; |
393 | 10 | { |
394 | 10 | const Lock lock(m_thread_context.waiter->m_mutex); |
395 | | //! Reset thread context waiter pointer, as shutdown signal for done |
396 | | //! lambda passed as waiter->wait() argument in makeThread code below. |
397 | 10 | waiter = std::move(m_thread_context.waiter); |
398 | | //! Assert waiter is idle. This destructor shouldn't be getting called if it is busy. |
399 | 10 | assert(!waiter->m_fn); |
400 | | // Clear client maps now to avoid deadlock in m_thread.join() call |
401 | | // below. The maps contain Thread::Client objects that need to be |
402 | | // destroyed from the event loop thread (this thread), which can't |
403 | | // happen if this thread is busy calling join. |
404 | 10 | m_thread_context.request_threads.clear(); |
405 | 10 | m_thread_context.callback_threads.clear(); |
406 | | //! Ping waiter. |
407 | 10 | waiter->m_cv.notify_all(); |
408 | 10 | } |
409 | 0 | m_thread.join(); |
410 | 10 | } |
411 | | |
412 | | kj::Promise<void> ProxyServer<Thread>::getName(GetNameContext context) |
413 | 0 | { |
414 | 0 | context.getResults().setResult(m_thread_context.thread_name); |
415 | 0 | return kj::READY_NOW; |
416 | 0 | } |
417 | | |
418 | 20 | ProxyServer<ThreadMap>::ProxyServer(Connection& connection) : m_connection(connection) {}Unexecuted instantiation: mp::ProxyServer<mp::ThreadMap>::ProxyServer(mp::Connection&) mp::ProxyServer<mp::ThreadMap>::ProxyServer(mp::Connection&) Line | Count | Source | 418 | 20 | ProxyServer<ThreadMap>::ProxyServer(Connection& connection) : m_connection(connection) {} |
|
419 | | |
420 | | kj::Promise<void> ProxyServer<ThreadMap>::makePool(MakePoolContext context) |
421 | 0 | { |
422 | 0 | if (!m_connection.m_thread_pool.empty()) { |
423 | 0 | throw std::runtime_error("makePool called on connection with existing pool"); |
424 | 0 | } |
425 | 0 | EventLoop& loop{*m_connection.m_loop}; |
426 | 0 | const uint32_t count = context.getParams().getCount(); |
427 | 0 | for (uint32_t i = 0; i < count; ++i) { |
428 | 0 | const std::string thread_name = "pool/" + std::to_string(i); |
429 | 0 | std::promise<ThreadContext*> thread_context; |
430 | 0 | std::thread thread([&loop, &thread_context, thread_name]() { |
431 | 0 | g_thread_context.thread_name = ThreadName(loop.m_exe_name) + " (" + thread_name + ")"; |
432 | 0 | g_thread_context.waiter = std::make_unique<Waiter>(); |
433 | 0 | Lock lock(g_thread_context.waiter->m_mutex); |
434 | 0 | thread_context.set_value(&g_thread_context); |
435 | 0 | g_thread_context.waiter->wait(lock, [] { return !g_thread_context.waiter; }); |
436 | 0 | }); |
437 | 0 | auto thread_server = kj::heap<ProxyServer<Thread>>(m_connection, *thread_context.get_future().get(), std::move(thread)); |
438 | 0 | m_connection.m_thread_pool.push_back({m_connection.m_threads.add(kj::mv(thread_server))}); |
439 | 0 | } |
440 | 0 | return kj::READY_NOW; |
441 | 0 | } |
442 | | |
443 | | kj::Promise<void> ProxyServer<ThreadMap>::makeThread(MakeThreadContext context) |
444 | 10 | { |
445 | 10 | EventLoop& loop{*m_connection.m_loop}; |
446 | 10 | if (loop.testing_hook_makethread) loop.testing_hook_makethread(); |
447 | 10 | const std::string from = context.getParams().getName(); |
448 | 10 | std::promise<ThreadContext*> thread_context; |
449 | 10 | std::thread thread([&loop, &thread_context, from]() { |
450 | 10 | g_thread_context.thread_name = ThreadName(loop.m_exe_name) + " (from " + from + ")"; |
451 | 10 | g_thread_context.waiter = std::make_unique<Waiter>(); |
452 | 10 | Lock lock(g_thread_context.waiter->m_mutex); |
453 | 10 | thread_context.set_value(&g_thread_context); |
454 | 10 | if (loop.testing_hook_makethread_created) loop.testing_hook_makethread_created(); |
455 | | // Wait for shutdown signal from ProxyServer<Thread> destructor (signal |
456 | | // is just waiter getting set to null.) |
457 | 46 | g_thread_context.waiter->wait(lock, [] { return !g_thread_context.waiter; }); |
458 | 10 | }); |
459 | 10 | auto thread_server = kj::heap<ProxyServer<Thread>>(m_connection, *thread_context.get_future().get(), std::move(thread)); |
460 | 10 | auto thread_client = m_connection.m_threads.add(kj::mv(thread_server)); |
461 | 10 | context.getResults().setResult(kj::mv(thread_client)); |
462 | 10 | return kj::READY_NOW; |
463 | 10 | } |
464 | | |
465 | | std::atomic<int> server_reqs{0}; |
466 | | |
467 | | std::string LongThreadName(const char* exe_name) |
468 | 510 | { |
469 | 510 | return g_thread_context.thread_name.empty() ? ThreadName(exe_name) : g_thread_context.thread_name; |
470 | 510 | } |
471 | | |
472 | | kj::StringPtr KJ_STRINGIFY(Log v) |
473 | 0 | { |
474 | 0 | switch (v) { |
475 | 0 | case Log::Trace: return "Trace"; |
476 | 0 | case Log::Debug: return "Debug"; |
477 | 0 | case Log::Info: return "Info"; |
478 | 0 | case Log::Warning: return "Warning"; |
479 | 0 | case Log::Error: return "Error"; |
480 | 0 | case Log::Raise: return "Raise"; |
481 | 0 | } |
482 | 0 | return "<Log?>"; |
483 | 0 | } |
484 | | } // namespace mp |