Skip to content

Commit 9b78b60

Browse files
committed
Squashed 'src/ipc/libmultiprocess/' changes from 1868a84451f..789ac8c86d5
789ac8c86d5 test: m_on_cancel called after request finishes 5aa8c36cdd3 test: getParams() called after request cancel 7f954aa5eac race fix: m_on_cancel called after request finishes (bitcoin#34782) 88b4d85099d race fix: getParams() called after request cancel (bitcoin#34777) 4fa90c68015 race fix: worker thread destroyed before it is initialized (bitcoin#34711, bitcoin#34756) 22bec918c97 Merge bitcoin-core/libmultiprocess#247: type-map: Work around LLVM 22 "out of bounds index" error 8a5e3ae6ed2 Merge bitcoin-core/libmultiprocess#242: proxy-types: add CustomHasField hook to map Cap'n Proto values to null C++ values e8d35246918 Merge bitcoin-core/libmultiprocess#246: doc: Bump version 8 > 9 97d877053b6 proxy-types: add CustomHasField hook for nullable decode paths 8c2f10252c9 refactor: add missing includes to mp/type-data.h b1638aceb40 doc: Bump version 8 > 9 f61af487217 type-map: Work around LLVM 22 "out of bounds index" error git-subtree-dir: src/ipc/libmultiprocess git-subtree-split: 789ac8c86d5532438351e06296e7139565ba60d7
1 parent b7ca3bf commit 9b78b60

18 files changed

Lines changed: 245 additions & 41 deletions

doc/versions.md

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,55 +7,68 @@ Library versions are tracked with simple
77
Versioning policy is described in the [version.h](../include/mp/version.h)
88
include.
99

10-
## v8
11-
- Current unstable version in master branch.
10+
## v9
11+
- Current unstable version.
1212

13-
## v7.0
14-
- Adds SpawnProcess race fix, cmake `target_capnp_sources` option, ci and documentation improvements.
15-
- Used in Bitcoin Core master branch, pulled in by [#34363](https://github.com/bitcoin/bitcoin/pull/34363).
13+
## [v8.0](https://github.com/bitcoin-core/libmultiprocess/commits/v8.0)
14+
- Better support for non-libmultiprocess IPC clients: avoiding errors on unclean disconnects, and allowing simultaneous requests to worker threads which previously triggered "thread busy" errors.
15+
- Used in Bitcoin Core, pulled in by [#34422](https://github.com/bitcoin/bitcoin/pull/34422).
1616

17-
## v7.0-pre1
17+
## [v7.0](https://github.com/bitcoin-core/libmultiprocess/commits/v7.0)
18+
- Adds SpawnProcess race fix, cmake `target_capnp_sources` option, ci and documentation improvements. Adds `version.h` header declaring major and minor version numbers.
19+
- Used in Bitcoin Core, pulled in by [#34363](https://github.com/bitcoin/bitcoin/pull/34363).
1820

19-
- Adds support for log levels to reduce logging and "thread busy" error to avoid a crash on misuse. Fixes intermittent mptest hang and makes other minor improvements.
20-
- Used in Bitcoin Core 30.1 and 30.2 releases and 30.x branch. Pulled in by [#33412](https://github.com/bitcoin/bitcoin/pull/33412), [#33518](https://github.com/bitcoin/bitcoin/pull/33518), and [#33519](https://github.com/bitcoin/bitcoin/pull/33519).
21+
## [v7.0-pre2](https://github.com/bitcoin-core/libmultiprocess/commits/v7.0-pre2)
22+
- Fixes intermittent mptest hang and makes other minor improvements.
23+
- Used in Bitcoin Core 30.1 and 30.2 releases and 30.x branch, pulled in by [#33518](https://github.com/bitcoin/bitcoin/pull/33518) and [#33519](https://github.com/bitcoin/bitcoin/pull/33519).
2124

22-
## v6.0
25+
## [v7.0-pre1](https://github.com/bitcoin-core/libmultiprocess/commits/v7.0-pre1)
2326

24-
- Adds fixes for unclean shutdowns and thread sanitizer issues and adds CI scripts.
27+
- Adds support for log levels to reduce logging and "thread busy" error to avoid a crash on misuse.
28+
- Minimum required version for Bitcoin Core 30.1 and 30.2 releases and 30.x branch, pulled in by [#33412](https://github.com/bitcoin/bitcoin/pull/33412), [#33518](https://github.com/bitcoin/bitcoin/pull/33518), and [#33519](https://github.com/bitcoin/bitcoin/pull/33519).
29+
30+
## [v6.0](https://github.com/bitcoin-core/libmultiprocess/commits/v6.0)
31+
32+
- Adds CI scripts and build and test fixes.
33+
- Used in Bitcoin Core 30.0 release, pulled in by [#32345](https://github.com/bitcoin/bitcoin/pull/32345), [#33241](https://github.com/bitcoin/bitcoin/pull/33241), and [#33322](https://github.com/bitcoin/bitcoin/pull/33322).
34+
35+
## [v6.0-pre1](https://github.com/bitcoin-core/libmultiprocess/commits/v6.0-pre1)
36+
37+
- Adds fixes for unclean shutdowns and thread sanitizer issues.
2538
- Drops `EventLoop::addClient` and `EventLoop::removeClient` methods,
2639
requiring clients to use new `EventLoopRef` class instead.
27-
- Used in Bitcoin Core 30.0 release. Pulled in by [#31741](https://github.com/bitcoin/bitcoin/pull/31741), [#32641](https://github.com/bitcoin/bitcoin/pull/32641), [#32345](https://github.com/bitcoin/bitcoin/pull/32345), [#33241](https://github.com/bitcoin/bitcoin/pull/33241), and [#33322](https://github.com/bitcoin/bitcoin/pull/33322).
40+
- Minimum required version for Bitcoin Core 30.0 release, pulled in by [#31741](https://github.com/bitcoin/bitcoin/pull/31741), [#32641](https://github.com/bitcoin/bitcoin/pull/32641), and [#32345](https://github.com/bitcoin/bitcoin/pull/32345).
2841

29-
## v5.0
42+
## [v5.0](https://github.com/bitcoin-core/libmultiprocess/commits/v5.0)
3043
- Adds build improvements and tidy/warning fixes.
3144
- Used in Bitcoin Core 29 releases, pulled in by [#31945](https://github.com/bitcoin/bitcoin/pull/31945).
3245

33-
## v5.0-pre1
46+
## [v5.0-pre1](https://github.com/bitcoin-core/libmultiprocess/commits/v5.0-pre1)
3447
- Adds many improvements to Bitcoin Core mining interface: splitting up type headers, fixing shutdown bugs, adding subtree build support.
3548
- Broke up `proxy-types.h` into `type-*.h` files requiring clients to explicitly
3649
include overloads needed for C++ ↔️ Cap'n Proto type conversions.
3750
- Now requires C++ 20 support.
3851
- Minimum required version for Bitcoin Core 29 releases, pulled in by [#30509](https://github.com/bitcoin/bitcoin/pull/30509), [#30510](https://github.com/bitcoin/bitcoin/pull/30510), [#31105](https://github.com/bitcoin/bitcoin/pull/31105), [#31740](https://github.com/bitcoin/bitcoin/pull/31740).
3952

40-
## v4.0
53+
## [v4.0](https://github.com/bitcoin-core/libmultiprocess/commits/v4.0)
4154
- Added better cmake support, installing cmake package files so clients do not
4255
need to use pkgconfig.
4356
- Used in Bitcoin Core 28 releases, pulled in by [#30490](https://github.com/bitcoin/bitcoin/pull/30490) and [#30513](https://github.com/bitcoin/bitcoin/pull/30513).
4457

45-
## v3.0
58+
## [v3.0](https://github.com/bitcoin-core/libmultiprocess/commits/v3.0)
4659
- Dropped compatibility with Cap'n Proto versions before 0.7.
4760
- Used in Bitcoin Core 27 releases, pulled in by [#28735](https://github.com/bitcoin/bitcoin/pull/28735), [#28907](https://github.com/bitcoin/bitcoin/pull/28907), and [#29276](https://github.com/bitcoin/bitcoin/pull/29276).
4861

49-
## v2.0
62+
## [v2.0](https://github.com/bitcoin-core/libmultiprocess/commits/v2.0)
5063
- Changed `PassField` function signature.
5164
- Now requires C++17 support.
5265
- Used in Bitcoin Core 25 and 26 releases, pulled in by [#26672](https://github.com/bitcoin/bitcoin/pull/26672).
5366

54-
## v1.0
67+
## [v1.0](https://github.com/bitcoin-core/libmultiprocess/commits/v1.0)
5568
- Dropped hardcoded includes in generated files, now requiring `include` and
5669
`includeTypes` annotations.
5770
- Used in Bitcoin Core 22, 23, and 24 releases, pulled in by [#19160](https://github.com/bitcoin/bitcoin/pull/19160).
5871

59-
## v0.0
72+
## [v0.0](https://github.com/bitcoin-core/libmultiprocess/commits/v0.0)
6073
- Initial version used in a downstream release.
6174
- Used in Bitcoin Core 21 releases, pulled in by [#16367](https://github.com/bitcoin/bitcoin/pull/16367), [#18588](https://github.com/bitcoin/bitcoin/pull/18588), and [#18677](https://github.com/bitcoin/bitcoin/pull/18677).

include/mp/proxy-types.h

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ class ValueField
2323
ValueField(Value&& value) : m_value(value) {}
2424
Value& m_value;
2525

26+
const Value& get() const { return m_value; }
2627
Value& get() { return m_value; }
2728
Value& init() { return m_value; }
28-
bool has() { return true; }
29+
bool has() const { return true; }
2930
};
3031

3132
template <typename Accessor, typename Struct>
@@ -166,10 +167,52 @@ struct ReadDestUpdate
166167
Value& m_value;
167168
};
168169

169-
template <typename... LocalTypes, typename... Args>
170-
decltype(auto) ReadField(TypeList<LocalTypes...>, Args&&... args)
170+
//! Return whether to read a C++ value from a Cap'n Proto field. Returning
171+
//! false can be useful to interpret certain Cap'n Proto field values as null
172+
//! C++ values when initializing nullable C++ std::optional / std::unique_ptr /
173+
//! std::shared_ptr types.
174+
//!
175+
//! For example, when reading from a `List(Data)` field into a
176+
//! `std::vector<std::shared_ptr<const CTransaction>>` value, it's useful to be
177+
//! able to interpret empty `Data` values as null pointers. This is useful
178+
//! because the Cap'n Proto C++ API does not currently provide a way to
179+
//! distinguish between null and empty Data values in a List[*], so we need to
180+
//! choose some Data value to represent null if we want to allow passing null
181+
//! pointers. Since no CTransaction is ever serialized as empty Data, it's safe
182+
//! to use empty Data values to represent null pointers.
183+
//!
184+
//! [*] The Cap'n Proto wire format actually does distinguish between null and
185+
//! empty Data values inside Lists, and the C++ API does allow distinguishing
186+
//! between null and empty Data values in other contexts, just not the List
187+
//! context, so this limitation could be removed in the future.
188+
//!
189+
//! Design note: CustomHasField() and CustomHasValue() are inverses of each
190+
//! other. CustomHasField() allows leaving Cap'n Proto fields unset when C++
191+
//! types have certain values, and CustomHasValue() allows leaving C++ values
192+
//! unset when Cap'n Proto fields have certain values. But internally the
193+
//! functions get called in different ways. This is because in C++, unlike in
194+
//! Cap'n Proto not every C++ type is default constructible, and it may be
195+
//! impossible to leave certain C++ values unset. For example if a C++ method
196+
//! requires function parameters, there's no way to call the function without
197+
//! constructing values for each of the parameters. Similarly there's no way to
198+
//! add values to C++ vectors or maps without initializing those values. This
199+
//! is not the case in Cap'n Proto where all values are optional and it's
200+
//! possible to skip initializing parameters and list elements.
201+
//!
202+
//! Because of this difference, CustomHasValue() works universally and can be
203+
//! used to disable BuildField() calls in every context, while CustomHasField()
204+
//! can only be used to disable ReadField() calls in certain contexts like
205+
//! std::optional and pointer contexts.
206+
template <typename... LocalTypes, typename Input>
207+
bool CustomHasField(TypeList<LocalTypes...>, InvokeContext& invoke_context, const Input& input)
208+
{
209+
return input.has();
210+
}
211+
212+
template <typename... LocalTypes, typename Input, typename... Args>
213+
decltype(auto) ReadField(TypeList<LocalTypes...>, InvokeContext& invoke_context, Input&& input, Args&&... args)
171214
{
172-
return CustomReadField(TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2>(), std::forward<Args>(args)...);
215+
return CustomReadField(TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2>(), invoke_context, std::forward<Input>(input), std::forward<Args>(args)...);
173216
}
174217

175218
template <typename LocalType, typename Input>
@@ -190,6 +233,13 @@ void ThrowField(TypeList<std::exception>, InvokeContext& invoke_context, Input&&
190233
throw std::runtime_error(std::string(CharCast(data.begin()), data.size()));
191234
}
192235

236+
//! Return whether to write a C++ value into a Cap'n Proto field. Returning
237+
//! false can be useful to map certain C++ values to unset Cap'n Proto fields.
238+
//!
239+
//! For example the bitcoin `Coin` class asserts false when a spent coin is
240+
//! serialized. But some C++ methods return these coins, so there needs to be a
241+
//! way to represent them in Cap'n Proto and a null Data field is a convenient
242+
//! representation.
193243
template <typename... Values>
194244
bool CustomHasValue(InvokeContext& invoke_context, const Values&... value)
195245
{
@@ -372,7 +422,7 @@ struct ClientException
372422
void handleField(InvokeContext& invoke_context, Results& results, ParamList)
373423
{
374424
StructField<Accessor, Results> input(results);
375-
if (input.has()) {
425+
if (CustomHasField(TypeList<Exception>(), invoke_context, input)) {
376426
ThrowField(TypeList<Exception>(), invoke_context, input);
377427
}
378428
}

include/mp/proxy.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ struct ProxyContext
7070
Connection* connection;
7171
EventLoopRef loop;
7272
CleanupList cleanup_fns;
73+
//! Hook called on the worker thread just before loop->sync() in PassField
74+
//! for Context arguments. Used by tests to inject precise disconnect timing.
75+
std::function<void()> testing_hook_before_sync;
76+
//! Hook called on the worker thread just before returning results.
77+
std::function<void()> testing_hook_after_cleanup;
7378

7479
ProxyContext(Connection* connection);
7580
};

include/mp/type-context.h

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
6161
std::is_same<decltype(Accessor::get(server_context.call_context.getParams())), Context::Reader>::value,
6262
kj::Promise<typename ServerContext::CallContext>>::type
6363
{
64-
const auto& params = server_context.call_context.getParams();
65-
Context::Reader context_arg = Accessor::get(params);
6664
auto& server = server_context.proxy_server;
6765
int req = server_context.req;
6866
// Keep a reference to the ProxyServer instance by assigning it to the self
@@ -74,8 +72,10 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
7472
auto self = server.thisCap();
7573
auto invoke = [self = kj::mv(self), call_context = kj::mv(server_context.call_context), &server, req, fn, args...](CancelMonitor& cancel_monitor) mutable {
7674
MP_LOG(*server.m_context.loop, Log::Debug) << "IPC server executing request #" << req;
77-
const auto& params = call_context.getParams();
78-
Context::Reader context_arg = Accessor::get(params);
75+
if (server.m_context.testing_hook_before_sync) server.m_context.testing_hook_before_sync();
76+
// Save testing_hook_after_cleanup to a local because the
77+
// server may be freed in the cleanup sync() below.
78+
auto testing_hook_after_cleanup = std::move(server.m_context.testing_hook_after_cleanup);
7979
ServerContext server_context{server, call_context, req};
8080
{
8181
// Before invoking the function, store a reference to the
@@ -127,6 +127,8 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
127127
server_context.request_canceled = true;
128128
};
129129
// Update requests_threads map if not canceled.
130+
const auto& params = call_context.getParams();
131+
Context::Reader context_arg = Accessor::get(params);
130132
std::tie(request_thread, inserted) = SetThread(
131133
GuardedRef{thread_context.waiter->m_mutex, request_threads}, server.m_context.connection,
132134
[&] { return context_arg.getCallbackThread(); });
@@ -153,6 +155,15 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
153155
// the disconnect handler trying to destroy the thread
154156
// client object.
155157
server.m_context.loop->sync([&] {
158+
// Clear cancellation callback. At this point the
159+
// method invocation finished and the result is
160+
// either being returned, or discarded if a
161+
// cancellation happened. So we do not need to be
162+
// notified of cancellations after this point. Also
163+
// we do not want to be notified because
164+
// cancel_mutex and server_context could be out of
165+
// scope when it happens.
166+
cancel_monitor.m_on_cancel = nullptr;
156167
auto self_dispose{kj::mv(self)};
157168
if (erase_thread) {
158169
// Look up the thread again without using existing
@@ -183,12 +194,15 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
183194
}
184195
// End of scope: if KJ_DEFER was reached, it runs here
185196
}
197+
if (testing_hook_after_cleanup) testing_hook_after_cleanup();
186198
return call_context;
187199
};
188200

189201
// Lookup Thread object specified by the client. The specified thread should
190202
// be a local Thread::Server object, but it needs to be looked up
191203
// asynchronously with getLocalServer().
204+
const auto& params = server_context.call_context.getParams();
205+
Context::Reader context_arg = Accessor::get(params);
192206
auto thread_client = context_arg.getThread();
193207
auto result = server.m_context.connection->m_threads.getLocalServer(thread_client)
194208
.then([&server, invoke = kj::mv(invoke), req](const kj::Maybe<Thread::Server&>& perhaps) mutable {

include/mp/type-data.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
#include <mp/util.h>
99

10+
#include <concepts>
11+
#include <span>
12+
1013
namespace mp {
1114
template <typename T, typename U>
1215
concept IsSpanOf =

include/mp/type-function.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ struct ProxyCallFn
4848
};
4949

5050
template <typename FnR, typename... FnParams, typename Input, typename ReadDest>
51-
decltype(auto) CustomReadField(TypeList<std::function<FnR(FnParams...)>>,
51+
decltype(auto) CustomReadField(TypeList<std::function<FnR(FnParams...)>> types,
5252
Priority<1>,
5353
InvokeContext& invoke_context,
5454
Input&& input,
5555
ReadDest&& read_dest)
5656
{
57-
if (input.has()) {
57+
if (CustomHasField(types, invoke_context, input)) {
5858
using Interface = typename Decay<decltype(input.get())>::Calls;
5959
auto client = std::make_shared<ProxyClient<Interface>>(
6060
input.get(), &invoke_context.connection, /* destroy_connection= */ false);

include/mp/type-interface.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ decltype(auto) CustomReadField(TypeList<std::unique_ptr<LocalType>>,
8585
typename Decay<decltype(input.get())>::Calls* enable = nullptr)
8686
{
8787
using Interface = typename Decay<decltype(input.get())>::Calls;
88-
if (input.has()) {
88+
if (CustomHasField(TypeList<LocalType>(), invoke_context, input)) {
8989
return read_dest.construct(
9090
CustomMakeProxyClient<Interface, LocalType>(invoke_context, std::move(input.get())));
9191
}
@@ -101,7 +101,7 @@ decltype(auto) CustomReadField(TypeList<std::shared_ptr<LocalType>>,
101101
typename Decay<decltype(input.get())>::Calls* enable = nullptr)
102102
{
103103
using Interface = typename Decay<decltype(input.get())>::Calls;
104-
if (input.has()) {
104+
if (CustomHasField(TypeList<LocalType>(), invoke_context, input)) {
105105
return read_dest.construct(
106106
CustomMakeProxyClient<Interface, LocalType>(invoke_context, std::move(input.get())));
107107
}

include/mp/type-map.h

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,35 @@ void CustomBuildField(TypeList<std::map<KeyLocalType, ValueLocalType>>,
2727
}
2828
}
2929

30+
// Replacement for `m.emplace(piecewise_construct, t1, t2)` to work around a
31+
// Clang 22 regression triggered by libc++'s std::map piecewise emplace: when
32+
// the key constructor argument tuple is empty (std::tuple<>), libc++'s internal
33+
// "try key extraction" SFINAE probe instantiates std::tuple_element<0,
34+
// std::tuple<>>, which Clang 22 diagnoses as an out-of-bounds pack access ("a
35+
// parameter pack may not be accessed at an out of bounds index") instead of
36+
// treating it as substitution failure. See LLVM issue #167709 and the upstream
37+
// fix in llvm/llvm-project PR #183614.
38+
// https://github.com/llvm/llvm-project/issues/167709
39+
// https://github.com/llvm/llvm-project/pull/183614
40+
template <class Map, class Tuple1, class Tuple2>
41+
auto EmplacePiecewiseSafe(
42+
Map& m,
43+
const std::piecewise_construct_t&,
44+
Tuple1&& t1,
45+
Tuple2&& t2)
46+
{
47+
if constexpr (std::tuple_size_v<std::remove_reference_t<Tuple1>> == 0) {
48+
// Avoid tuple<> / tuple<> (LLVM 22 libc++ regression path)
49+
return m.emplace(std::piecewise_construct,
50+
std::forward_as_tuple(typename Map::key_type{}),
51+
std::forward<Tuple2>(t2));
52+
} else {
53+
return m.emplace(std::piecewise_construct,
54+
std::forward<Tuple1>(t1),
55+
std::forward<Tuple2>(t2));
56+
}
57+
}
58+
3059
template <typename KeyLocalType, typename ValueLocalType, typename Input, typename ReadDest>
3160
decltype(auto) CustomReadField(TypeList<std::map<KeyLocalType, ValueLocalType>>,
3261
Priority<1>,
@@ -42,7 +71,7 @@ decltype(auto) CustomReadField(TypeList<std::map<KeyLocalType, ValueLocalType>>,
4271
Make<ValueField>(item),
4372
ReadDestEmplace(
4473
TypeList<std::pair<const KeyLocalType, ValueLocalType>>(), [&](auto&&... args) -> auto& {
45-
return *value.emplace(std::forward<decltype(args)>(args)...).first;
74+
return *EmplacePiecewiseSafe(value, std::forward<decltype(args)>(args)...).first;
4675
}));
4776
}
4877
});

0 commit comments

Comments
 (0)