Skip to content

fix(timestamps): stamp message info with the wall clock, not the monotonic clock - #44

Closed
benaliabderrahmane wants to merge 1 commit into
mainfrom
fix/wallclock-message-timestamps
Closed

fix(timestamps): stamp message info with the wall clock, not the monotonic clock#44
benaliabderrahmane wants to merge 1 commit into
mainfrom
fix/wallclock-message-timestamps

Conversation

@benaliabderrahmane

Copy link
Copy Markdown
Owner

Problem

Bags recorded with this RMW under Jazzy show every message at 1 January 1970.

In Jazzy, rosbag2 stopped stamping messages with the recorder node's own clock and writes the send/receive timestamps as reported by the middleware (ros2/rosbag2#1531) — i.e. rmw_message_info_t::received_timestamp / source_timestamp go into the bag verbatim.

Those fields are defined as nanoseconds since the Unix epoch, but every stamp in this RMW came from std::chrono::steady_clock (CLOCK_MONOTONIC — ns since boot). A recorder started 22 minutes after boot therefore wrote 1312664618690 ns ⇒ 1970-01-01T00:21:52Z.

rmw_cyclonedds uses the wall clock for both fields (dds_time()CLOCK_REALTIME for source_timestamp, std::chrono::system_clock for received_timestamp), which is why bags recorded over Cyclone are fine.

Fix

Stamp message-visible timestamps with system_clock:

Path Stamp
rmw_publish, rmw_publish_serialized_message source_timestamp_ns
rmw_subscription drain received_timestamp_ns
rmw_wait drain (the executor/rosbag2 path) received_timestamp_ns
rmw_send_request / rmw_take_request, rmw_send_response / rmw_take_response both

rmw_wait keeps steady_clock for its poll deadlines — those must stay monotonic, so it now has both helpers. The remaining steady_clock uses (socket/shm name uniquifiers) are unrelated to message timestamps and untouched.

Wire layout is unchanged: WireHeader::source_timestamp_ns is still an int64_t at offset 24, so the existing static_assert and cross-process compatibility hold. TRANSIENT_LOCAL late-joiner replay re-sends the cached original header, so replayed samples keep their true publish time.

Tests

Three new tests, each written to fail first and bracketing the timestamps between two wall-clock reads:

  • PubSubTest.TakeWithInfoTimestampsAreUnixEpoch
  • RmwUdsNodeTest.WaitDrainStampsReceivedTimestampWithWallClock (covers the rmw_wait drain, which is the path rosbag2 actually takes)
  • ServiceClientTest.RequestResponseTimestampsAreUnixEpoch

Before the fix all three failed with e.g. actual: 1312664618690 vs 1785743133835997198. After: full suite green — 127 tests, 0 failures.

Notes

The wall clock can step (NTP), so source_timestamp may occasionally exceed received_timestamp or bag stamps may be non-monotonic. That is inherent to the epoch-based contract and matches Cyclone/Fast DDS; no timeout or ordering logic in this RMW reads these fields, so nothing can hang on a backwards step.

@benaliabderrahmane
benaliabderrahmane deleted the fix/wallclock-message-timestamps branch August 3, 2026 08:14
…tonic clock

rmw_message_info_t/rmw_service_info_t timestamps are ns since the Unix
epoch, but every stamp came from steady_clock — ns since boot. Since Jazzy,
rosbag2 no longer stamps bags with the recorder node's clock and writes the
middleware's send/receive timestamps verbatim (ros2/rosbag2#1531), so every
recorded message landed on 1 January 1970 plus the writer's uptime.

The publish, take, wait-drain and service request/response paths now stamp
with system_clock, matching rmw_cyclonedds (dds_time()/system_clock).
rmw_wait keeps steady_clock for its poll deadlines. Wire layout is
unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant