This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new 7b6be90dc fix(c++): fix c++ duration serialization (#3598)
7b6be90dc is described below
commit 7b6be90dc39119247589f8df53fe2e2d29590521
Author: Shawn Yang <[email protected]>
AuthorDate: Tue Apr 21 16:18:24 2026 +0800
fix(c++): fix c++ duration serialization (#3598)
## Why?
## What does this PR do?
## Related issues
## AI Contribution Checklist
- [ ] Substantial AI assistance was used in this PR: `yes` / `no`
- [ ] If `yes`, I included a completed [AI Contribution
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
in this PR description and the required `AI Usage Disclosure`.
- [ ] If `yes`, my PR description includes the required `ai_review`
summary and screenshot evidence of the final clean AI review results
from both fresh reviewers on the current PR diff or current HEAD after
the latest code changes.
## Does this PR introduce any user-facing change?
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
---
cpp/fory/serialization/serialization_test.cc | 80 +++++++++++++++++++++++++++
cpp/fory/serialization/skip.cc | 9 ++-
cpp/fory/serialization/temporal_serializers.h | 19 +++++--
3 files changed, 100 insertions(+), 8 deletions(-)
diff --git a/cpp/fory/serialization/serialization_test.cc
b/cpp/fory/serialization/serialization_test.cc
index d5ec5a86e..479fcd038 100644
--- a/cpp/fory/serialization/serialization_test.cc
+++ b/cpp/fory/serialization/serialization_test.cc
@@ -19,8 +19,10 @@
#include "fory/serialization/fory.h"
#include "fory/serialization/ref_resolver.h"
+#include "fory/serialization/skip.h"
#include "gtest/gtest.h"
#include <atomic>
+#include <chrono>
#include <cstdint>
#include <cstring>
#include <map>
@@ -98,6 +100,11 @@ inline void register_test_types(Fory &fory) {
fory.register_enum<OldStatus>(type_id++);
}
+inline std::vector<uint8_t> buffer_bytes(Buffer &buffer) {
+ return std::vector<uint8_t>(buffer.data(),
+ buffer.data() + buffer.writer_index());
+}
+
template <typename T>
void test_roundtrip(const T &original, bool should_equal = true) {
auto fory = Fory::builder().xlang(true).track_ref(false).build();
@@ -182,6 +189,79 @@ TEST(SerializationTest, StringRoundtrip) {
test_roundtrip(std::string("UTF-8: 你好世界"));
}
+TEST(SerializationTest, DurationRoundtrip) {
+ auto fory = Fory::builder().xlang(true).track_ref(false).build();
+ std::vector<Duration> values = {
+ Duration(0),
+ std::chrono::seconds(12) + Duration(345678901),
+ -std::chrono::seconds(7) - std::chrono::milliseconds(45) - Duration(67),
+ Duration(-1),
+ };
+
+ for (const Duration &original : values) {
+ auto serialize_result = fory.serialize(original);
+ ASSERT_TRUE(serialize_result.ok())
+ << "Serialization failed: " << serialize_result.error().to_string();
+
+ std::vector<uint8_t> bytes = std::move(serialize_result).value();
+ auto deserialize_result =
+ fory.deserialize<Duration>(bytes.data(), bytes.size());
+ ASSERT_TRUE(deserialize_result.ok())
+ << "Deserialization failed: " <<
deserialize_result.error().to_string();
+ EXPECT_EQ(deserialize_result.value(), original);
+ }
+}
+
+TEST(SerializationTest, DurationUsesSecondsAndNanosecondsPayload) {
+ struct TestCase {
+ Duration value;
+ int64_t expected_seconds;
+ int32_t expected_nanos;
+ };
+
+ auto fory = Fory::builder().xlang(true).track_ref(false).build();
+ std::vector<TestCase> cases = {
+ {Duration(1234567890), 1, 234567890},
+ {Duration(-1234567890), -1, -234567890},
+ {Duration(-1), 0, -1},
+ };
+
+ for (const TestCase &test_case : cases) {
+ WriteContext write_ctx(fory.config(), fory.type_resolver().clone());
+ Serializer<Duration>::write_data(test_case.value, write_ctx);
+ ASSERT_FALSE(write_ctx.has_error()) << write_ctx.error().to_string();
+
+ Buffer expected;
+ expected.write_var_int64(test_case.expected_seconds);
+ expected.write_int32(test_case.expected_nanos);
+ EXPECT_EQ(buffer_bytes(write_ctx.buffer()), buffer_bytes(expected));
+
+ ReadContext read_ctx(fory.config(), fory.type_resolver().clone());
+ read_ctx.attach(write_ctx.buffer());
+ Duration decoded = Serializer<Duration>::read_data(read_ctx);
+ ASSERT_FALSE(read_ctx.has_error()) << read_ctx.error().to_string();
+ EXPECT_EQ(decoded, test_case.value);
+ EXPECT_EQ(read_ctx.buffer().reader_index(),
+ write_ctx.buffer().writer_index());
+ }
+}
+
+TEST(SerializationTest, DurationSkipConsumesSecondsAndNanosecondsPayload) {
+ auto fory = Fory::builder().xlang(true).track_ref(false).build();
+ WriteContext write_ctx(fory.config(), fory.type_resolver().clone());
+ Serializer<Duration>::write_data(Duration(-1), write_ctx);
+ ASSERT_FALSE(write_ctx.has_error()) << write_ctx.error().to_string();
+
+ ReadContext read_ctx(fory.config(), fory.type_resolver().clone());
+ read_ctx.attach(write_ctx.buffer());
+ skip_field_value(read_ctx,
+ FieldType(static_cast<uint32_t>(TypeId::DURATION), false),
+ RefMode::None);
+ ASSERT_FALSE(read_ctx.has_error()) << read_ctx.error().to_string();
+ EXPECT_EQ(read_ctx.buffer().reader_index(),
+ write_ctx.buffer().writer_index());
+}
+
// ============================================================================
// Character Type Tests (C++ native only)
// ============================================================================
diff --git a/cpp/fory/serialization/skip.cc b/cpp/fory/serialization/skip.cc
index 15e8bbb94..99c0b1bee 100644
--- a/cpp/fory/serialization/skip.cc
+++ b/cpp/fory/serialization/skip.cc
@@ -549,8 +549,13 @@ void skip_field_value(ReadContext &ctx, const FieldType
&field_type,
return;
case TypeId::DURATION: {
- // Duration is stored as fixed 8-byte nanosecond count.
- constexpr uint32_t k_bytes = static_cast<uint32_t>(sizeof(int64_t));
+ // Duration is stored as signed varint64 seconds + signed int32
+ // nanoseconds.
+ skip_varint(ctx);
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return;
+ }
+ constexpr uint32_t k_bytes = static_cast<uint32_t>(sizeof(int32_t));
ctx.buffer().increase_reader_index(k_bytes, ctx.error());
return;
}
diff --git a/cpp/fory/serialization/temporal_serializers.h
b/cpp/fory/serialization/temporal_serializers.h
index 046599ae8..ac167ff11 100644
--- a/cpp/fory/serialization/temporal_serializers.h
+++ b/cpp/fory/serialization/temporal_serializers.h
@@ -55,7 +55,8 @@ struct Date {
// ============================================================================
/// Serializer for Duration (std::chrono::nanoseconds)
-/// Per xlang spec: serialized as int64 nanosecond count
+/// Per xlang spec: serialized as signed varint64 seconds + signed int32
+/// nanoseconds
template <> struct Serializer<Duration> {
static constexpr TypeId type_id = TypeId::DURATION;
@@ -85,8 +86,10 @@ template <> struct Serializer<Duration> {
}
static inline void write_data(const Duration &duration, WriteContext &ctx) {
- int64_t nanos = duration.count();
- ctx.write_bytes(&nanos, sizeof(int64_t));
+ auto seconds = std::chrono::duration_cast<std::chrono::seconds>(duration);
+ auto remainder = duration - seconds;
+ ctx.write_var_int64(seconds.count());
+ ctx.buffer().write_int32(static_cast<int32_t>(remainder.count()));
}
static inline void write_data_generic(const Duration &duration,
@@ -115,9 +118,13 @@ template <> struct Serializer<Duration> {
}
static inline Duration read_data(ReadContext &ctx) {
- int64_t nanos;
- ctx.read_bytes(&nanos, sizeof(int64_t), ctx.error());
- return Duration(nanos);
+ int64_t seconds = ctx.read_var_int64(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return Duration(0);
+ }
+ int32_t nanos = ctx.read_int32(ctx.error());
+ return std::chrono::duration_cast<Duration>(std::chrono::seconds(seconds))
+
+ Duration(nanos);
}
static inline Duration read_with_type_info(ReadContext &ctx, RefMode
ref_mode,
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]