Skip to content

fix(subscription): write rmw_take_sequence output at the taken cursor [P2] - #7

Merged
benaliabderrahmane merged 2 commits into
mainfrom
fix/p2-2-take-sequence-cursor
Jun 5, 2026
Merged

benaliabderrahmane merged 2 commits into
mainfrom
fix/p2-2-take-sequence-cursor

Conversation

@benaliabderrahmane

Copy link
Copy Markdown
Owner

Severity: P2 — found by a full multi-agent code audit (method + findings in AUDIT_FINAL.md).

Problem

rmw_take_sequence() wrote each message to data[i] indexed by the loop counter, but counted successes separately in *taken and did a bare continue on a deserialize failure. If message j failed but a later k > j succeeded, the consumer reads data[0 .. *taken-1], which (a) includes the never-written data[j] (uninitialized caller storage surfaces as a "valid" message) and (b) excludes the valid data[k] (already popped from the queue → permanently lost). It was also the only take path that didn't log the dropped datagram.

Fix

Use a single write cursor: write each success to data[*taken] then increment *taken; on failure, log (throttled) and continue without advancing the cursor. Output is contiguous [0 .. *taken-1] and size == *taken.

File: rmw_unix_socket_cpp/src/rmw_subscription.cpp
Build: clean.

@benaliabderrahmane

Copy link
Copy Markdown
Owner Author

Reviewed the fix end-to-end against main -- the cursor change is correct and clean. I confirmed (git show main:rmw_unix_socket_cpp/src/rmw_subscription.cpp) that the base version writes message_sequence->data[i] / message_info_sequence->data[i] by the loop counter while counting successes separately in *taken. On a partial deserialize failure that leaves an uninitialized hole inside [0..*taken-1] (surfaced to the consumer as a valid message) and drops a later success that was already pop_front()'d. Switching both writes to data[*taken] (lines 378, 390) and incrementing only on success (line 400) gives contiguous output with size == *taken. Verified the failure path doesn't advance the cursor, so the next success fully overwrites. The added throttled-log block mirrors the sibling rmw_take path (lines 257-261) byte-for-byte. CI green on jazzy/kilted/rolling.

One blocking item before merge:

  • Missing regression test (CLAUDE.md §4, failing-test-first). This is a silent-data-corruption fix in an API with zero existing coverage: gh pr diff --name-only touches only rmw_subscription.cpp, and grep -rn take_sequence test/ is empty (test_rmw_pub_sub.cpp exercises only rmw_take / rmw_take_with_info). The bug is deterministic and cheap to pin: enqueue [good, corrupt, good], call rmw_take_sequence with count=3, and assert *taken == 2, message_sequence->size == 2, message_info_sequence->size == 2, and that data[0]/data[1] are the two good messages. That test fails on main and passes here. AUDIT_FINAL.md:56 calls this exact test the single highest-leverage preventive action, with the recipe at :178-179.

Non-blocking notes:

  • The same test is the natural home for the unverified pre-existing edge cases: to_take clamping against count / both capacities / queue size (lines 365-368) and the empty-queue (to_take == 0) path.
  • Minor scope: the 5-line RMW_UDS_LOG_ERROR_THROTTLE block (lines 380-386) is beyond the audit's stated "single write cursor" scope (CLAUDE.md §3), but it's disclosed in the PR body and exactly mirrors the existing rmw_take path -- fine to keep.

Code is ready; please add the regression test before merge.

@benaliabderrahmane
benaliabderrahmane force-pushed the fix/p2-2-take-sequence-cursor branch 2 times, most recently from a801a98 to 08678ae Compare June 5, 2026 18:10
Inject [good, corrupt, good] datagrams into a subscription socket (public
rmw_serialize for the payload, raw sendto for delivery) and assert take_sequence
yields two contiguous messages (size == *taken == 2) with no hole and nothing
lost. Fails before the cursor fix (data[1] was a hole and the second good
message, written at data[2], was dropped).
@benaliabderrahmane
benaliabderrahmane force-pushed the fix/p2-2-take-sequence-cursor branch from 08678ae to 0b86dcb Compare June 5, 2026 18:14
@benaliabderrahmane
benaliabderrahmane merged commit 29c6eb7 into main Jun 5, 2026
3 checks passed
@benaliabderrahmane
benaliabderrahmane deleted the fix/p2-2-take-sequence-cursor branch June 17, 2026 08:47
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