Needed for bitcoin/bitcoin#10102 since bitcoin/bitcoin#34882 was merged, which uses NodeClock::time_point in the CNodeStats struct (m_last_send, m_last_recv, m_ping_start) returned by Node::getNodesStats.
type-chrono: Add CustomBuildField/CustomReadField overloads for std::chrono::time_point #303
pull ryanofsky wants to merge 3 commits into bitcoin-core:master from ryanofsky:pr/timepoint changing 3 files +53 −6-
ryanofsky commented at 6:08 PM on July 8, 2026: collaborator
-
DrahtBot commented at 6:08 PM on July 8, 2026: none
<!--e57a25ab6845829454e8d69fc972939a-->
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
<!--021abf342d371248e50ceaed478a90ca-->
Reviews
See the guideline and AI policy for information on the review process.
Type Reviewers ACK maflcko, ViniciusCestarii If your review is incorrectly listed, please copy-paste <code><!--meta-tag:bot-skip--></code> into the comment that the bot should ignore.
<!--5faf32d7da4f0f540f40219e4f7537a3-->
-
maflcko commented at 8:09 PM on July 8, 2026: contributor
lgtm ACK b37d1d71f115f6b5a0000e09c7be063994387e1a
-
Sjors commented at 11:55 AM on July 9, 2026: member
That's merged, by I assume you still need this?
-
maflcko commented at 12:01 PM on July 9, 2026: contributor
Needed by bitcoin/bitcoin#34882
That's merged, by I assume you still need this?
There was no rebase of https://github.com/bitcoin/bitcoin/pull/10102 in 4 months, so I don't think https://github.com/bitcoin/bitcoin/pull/10102 sees the changes from 34882 yet.
-
ryanofsky commented at 12:42 PM on July 9, 2026: collaborator
Sorry PR description was written in a confusing way, should hopefully be clearer now. In order for bitcoin/bitcoin#10102 to work after bitcoin/bitcoin#34882 it needs these
ReadField/BuildFieldoverloads. It could define them itself, but it makes sense for libmultiprocess to provide them since it already provides many similar ones including ones forstd::chrono::duration -
in include/mp/type-chrono.h:42 in b37d1d71f1
37 | +template <class Clock, class Duration, typename Value, typename Output> 38 | +void CustomBuildField(TypeList<std::chrono::time_point<Clock, Duration>>, Priority<1>, InvokeContext& invoke_context, 39 | + Value&& value, Output&& output) 40 | +{ 41 | + using Rep = typename Duration::rep; 42 | + static_assert(std::numeric_limits<decltype(output.get())>::lowest() <= std::numeric_limits<Rep>::lowest(),
ViniciusCestarii commented at 1:17 PM on July 9, 2026:The range checks in both CustomBuildField overloads have a signedness bug:
static_assert(std::numeric_limits<decltype(output.get())>::lowest() <= std::numeric_limits<Rep>::lowest(), ...);When the capnp field type is unsigned (e.g. UInt64) and Rep is signed (e.g. int64_t for std::chrono::nanoseconds), the usual arithmetic conversions convert Rep::lowest() (INT64_MIN) to the unsigned type before comparing, turning it into a huge positive number. So 0 <= INT64_MIN silently evaluates to true, and the assert passes for a field that can't actually represent negative tick counts (e.g. any pre-epoch system_clock::time_point).
Concretely, this means a UInt64 field paired with a signed Duration::rep compiles today, and output.set(negative_count) wraps the value into UINT64_MAX. That's silently wrong for anything that reads the field as its declared (unsigned) type — a different language's capnp bindings, a JSON dump, or a raw comparison would see 18446744073709551615 instead of -1. A C++ round trip through the same chrono type can look fine, since the wrap is bit-for-bit invertible at equal width, which is what makes this easy to miss in testing.
Fix: use std::cmp_less_equal/std::cmp_greater_equal (C++20, <utility>) instead of <=/>=, since they're designed to compare integers of differing signedness correctly:
static_assert(std::cmp_less_equal(std::numeric_limits<decltype(output.get())>::lowest(), std::numeric_limits<Rep>::lowest()), "capnp type does not have enough range to hold lowest std::chrono::time_point value"); static_assert(std::cmp_greater_equal(std::numeric_limits<decltype(output.get())>::max(), std::numeric_limits<Rep>::max()), "capnp type does not have enough range to hold highest std::chrono::time_point value");I verified this by temporarily declaring a test field as UInt64 against a signed Rep: it compiled before the fix and correctly fails to compile after.
Happy to push chrono type tests as a follow-up PR if useful.
Note: I had AI help me write this up clearly, apologies if the phrasing feels more polished or sloppy than my usual comments.
ryanofsky commented at 2:09 PM on July 9, 2026:re: #303 (review)
Good catch! I think this makes sense and it looks like this same problem also exists other places: for durations above and also in type-number.h. I'll see if it's possible to fix them all here without breaking anything.
ryanofsky commented at 12:45 AM on July 30, 2026:re: #303 (review)
Added a new commit to fix this problem. Using std::cmp functions directly didn't work for time_point comparisons because floating point time point types are used in some places so I had to add wrappers to handle floats.
ryanofsky referenced this in commit 7dce1a915e on Jul 30, 2026ryanofsky force-pushed on Jul 30, 2026ryanofsky commented at 12:47 AM on July 30, 2026: collaboratorThanks for the reviews!
<!-- begin push-2 -->
Updated b37d1d71f115f6b5a0000e09c7be063994387e1a -> 978028ffd0d14e5e46b313412ef17b7ad3647e33 (
pr/timepoint.1->pr/timepoint.2, compare)<!-- end --> switching to std::cmp functions for more reliable static assertsmaflcko commented at 5:42 AM on July 30, 2026: contributorLooks like iwyu fails?
ryanofsky referenced this in commit 2a4ef6318f on Jul 30, 2026ryanofsky force-pushed on Jul 30, 2026ryanofsky commented at 2:20 PM on July 30, 2026: collaborator<!-- begin push-3 -->
Updated 978028ffd0d14e5e46b313412ef17b7ad3647e33 -> 1e3311158138ac9d458656805e666663f8f19f40 (
pr/timepoint.2->pr/timepoint.3, compare)<!-- end --> to fix IWYU error https://github.com/bitcoin-core/libmultiprocess/actions/runs/30503532739/job/90748227074<!-- begin push-4 -->
Rebased 1e3311158138ac9d458656805e666663f8f19f40 -> 7a72df02e2d46f953c4c97662c3e109aa3874ea9 (
pr/timepoint.3->pr/timepoint.4, compare)<!-- end --> adding new commit and rebasing to fix incompatibility withboolandstd::cmp_less_equal/std::cmp_greater_equalin libc++ https://github.com/bitcoin-core/libmultiprocess/actions/runs/30551079943/job/90899596238?pr=30345f6255975type-number: exclude bool from the integral overload
Exclude bool from the integral BuildPrimitive overload (bool is integral per std::is_integral_v but not a standard integer per std::cmp_*); add a dedicated bool overload that static_asserts the capnp LocalType is also bool, so a schema mismatch (e.g. Float64 used for a bool parameter) produces a clear error pointing at the schema. This change is also needed so the next commit can tighten the integer range changes in the BuildPrimitive integer overload. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
45b685c3f5type-number, type-chrono: Fix static assert signed/unsigned comparisons
Use std::cmp_less_equal/std::cmp_greater_equal (C++20) in the range static_asserts to avoid signed/unsigned comparison pitfall: when one side is unsigned and the other signed, the usual arithmetic conversions silently convert the signed lowest() to a huge positive number, making the assert pass when it should fire. Also fix the identical pattern in type-number.h BuildPrimitive. Problem was reported by ViniciusCestarii in https://github.com/bitcoin-core/libmultiprocess/pull/303#discussion_r3551873101 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7a72df02e2type-chrono: Add CustomBuildField/CustomReadField overloads for std::chrono::time_point
Needed by bitcoin/bitcoin#34882 which uses NodeClock::time_point in the Node stats struct (m_last_send, m_last_recv, m_ping_start), requiring IPC serialization support for time_point types. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ryanofsky force-pushed on Jul 30, 2026in include/mp/util.h:48 in 7a72df02e2
43 | +} 44 | +template <typename A, typename B> 45 | +constexpr bool safe_greater_equal(A a, B b) 46 | +{ 47 | + if constexpr (std::is_floating_point_v<A> || std::is_floating_point_v<B>) return a >= b; 48 | + else return std::cmp_greater_equal(a, b);
uqlidi commented at 6:48 AM on July 31, 2026:else return safe_less_equal(b, a);
ryanofsky commented at 1:37 PM on August 3, 2026:re: #303 (review)
This does seem like a good idea, but it's a minor cleanup so not planning to make the change here. Could be worth doing if this code changes again.
uqlidi changes_requesteduqlidi commented at 6:49 AM on July 31, 2026: nonelooks good but there's a small suggestion
maflcko commented at 8:41 AM on July 31, 2026: contributorlgtm ACK 7a72df02e2d46f953c4c97662c3e109aa3874ea9
ViniciusCestarii commented at 6:11 PM on August 1, 2026: contributorACK 7a72df02e2d46f953c4c97662c3e109aa3874ea9
ryanofsky merged this on Aug 3, 2026ryanofsky closed this on Aug 3, 2026
This is a metadata mirror of the GitHub repository bitcoin-core/libmultiprocess. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-08-05 20:30 UTC
More mirrored repositories can be found on mirror.b10c.me