Coverage Report

Created: 2026-07-08 14:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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