Skip to content

Commit 0d48f84

Browse files
committed
test(pubsub): assert delivery ordering, not completeness, on the bounded path
transient_local_delivery_preserves_order published 500 samples through a KeepLast(10) subscriber and asserted all 500 arrived. That asserts a promise no RMW makes: rmw_zenoh_cpp's add_new_message drops the oldest once message_queue_.size() >= adapted_qos_profile.depth, for every arriving sample, with no TransientLocal exemption -- the check reads the history policy only. The test's stated property is ordering, and its own doc says so. Assert that instead: a strictly increasing subsequence of what was published. That catches reordering whether or not anything was dropped, where an equality check conflated the two failures. Losslessness is still covered, on the profile that actually promises it, by keep_all_delivers_every_local_sample. Also records in the dispatcher docs that both implementations drop silently w.r.t. the ROS event API: upstream's MESSAGE_LOST comes from sequence-number gaps among arriving messages, which a depth-drop cannot produce, so bounding introduces no reporting gap.
1 parent 5b6f0c2 commit 0d48f84

2 files changed

Lines changed: 58 additions & 15 deletions

File tree

crates/hiroz-tests/tests/reentrant_publish.rs

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,19 @@ fn intra_closed_loop_runs_iteratively() {
613613
/// The shim enqueues from inside `handle_sample` — i.e. under zenoh-ext's state
614614
/// mutex, in exactly the order zenoh-ext chose to deliver — and a single thread
615615
/// pops FIFO, so the observed order must be the publish order.
616+
///
617+
/// **Ordering, not completeness.** The burst is far deeper than the declared
618+
/// `KeepLast` depth, so the queue drops the oldest — exactly as
619+
/// `rmw_zenoh_cpp`'s `add_new_message` does (`rmw_subscription_data.cpp`:
620+
/// `size() >= adapted_qos_profile.depth` → `pop_front()`, with no
621+
/// `TransientLocal` exemption). Asserting the delivered values were *all*
622+
/// published would therefore assert a promise no RMW makes.
623+
///
624+
/// Asserting a strictly increasing subsequence is the stronger test anyway: it
625+
/// catches reordering whether or not anything was dropped, whereas an equality
626+
/// check conflates the two failures. `keep_all_delivers_every_local_sample` in
627+
/// `dispatch_backpressure.rs` covers losslessness on the profile that promises
628+
/// it.
616629
#[test]
617630
#[serial]
618631
fn transient_local_delivery_preserves_order() {
@@ -655,14 +668,35 @@ fn transient_local_delivery_preserves_order() {
655668
.expect("publish failed");
656669
}
657670

658-
await_deliveries(&seen, COUNT as usize);
671+
// Settle rather than wait for a fixed count: with a bounded queue the
672+
// delivered total is a property of scheduling, not of the publish count.
673+
let mut last = 0usize;
674+
let mut stable_since = Instant::now();
675+
let deadline = Instant::now() + DELIVERY_TIMEOUT;
676+
loop {
677+
let len = seen.load(Ordering::SeqCst);
678+
if len != last {
679+
last = len;
680+
stable_since = Instant::now();
681+
} else if stable_since.elapsed() >= Duration::from_millis(300) {
682+
break;
683+
}
684+
assert!(Instant::now() < deadline, "delivery never settled");
685+
thread::sleep(Duration::from_millis(25));
686+
}
659687

660688
let received = received.lock().unwrap();
661-
let expected: Vec<u64> = (0..COUNT).collect();
662-
assert_eq!(
663-
&received[..COUNT as usize],
664-
&expected[..],
665-
"delivery thread reordered samples"
689+
assert!(
690+
!received.is_empty(),
691+
"nothing was delivered — the scenario proved nothing"
692+
);
693+
assert!(
694+
received.windows(2).all(|w| w[0] < w[1]),
695+
"delivery thread reordered samples: {received:?}"
696+
);
697+
assert!(
698+
received.iter().all(|&c| c < COUNT),
699+
"delivered a counter that was never published: {received:?}"
666700
);
667701
});
668702
}

crates/hiroz/src/pubsub.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -342,15 +342,24 @@ impl DispatchQueue {
342342
/// survives, so the ordering objection that applies to the advanced path does
343343
/// not apply here.
344344
///
345-
/// * **Advanced path — same bound, and the same expression.** A `TransientLocal`
346-
/// subscriber exists to replay history and to recover samples flagged as
347-
/// missed, so dropping discards data zenoh-ext went out of its way to fetch
348-
/// and breaks the reordering contract mid-flight — a single
349-
/// `deliver_and_flush` enqueues several back-to-back samples whose contiguity
350-
/// is the point. That argument is why `KeepAll` maps to
351-
/// [`DISPATCH_UNBOUNDED`]. It is *not* a reason to ignore a declared
352-
/// `KeepLast(depth)`, which is what an unbounded capacity on every profile
353-
/// amounted to.
345+
/// * **Advanced path — same bound, and the same expression.** This matches
346+
/// `rmw_zenoh_cpp`: `SubscriptionData::add_new_message` drops the oldest once
347+
/// `message_queue_.size() >= adapted_qos_profile.depth`, for every arriving
348+
/// sample, with **no `TransientLocal` exemption** — the check reads the
349+
/// history policy only. Its advanced-subscriber cache is sized the same way
350+
/// (`adv_sub_opts.history->max_samples = qos_.depth`).
351+
///
352+
/// An earlier revision left this path unbounded on *every* profile, reasoning
353+
/// that dropping discards what miss-detection recovered. That is why `KeepAll`
354+
/// maps to [`DISPATCH_UNBOUNDED`] — but applied to a declared
355+
/// `KeepLast(depth)` it ignores the QoS, and since [`Self::always_shim`]
356+
/// enqueues remote samples too, it also traded zenoh's transport backpressure
357+
/// for unbounded growth.
358+
///
359+
/// Both implementations drop **silently** as far as the ROS event API is
360+
/// concerned: upstream's `MESSAGE_LOST` is raised from *sequence-number gaps*
361+
/// among arriving messages, which a depth-drop cannot produce. The escalating
362+
/// `warn!` below is strictly more visible than upstream's debug log.
354363
///
355364
/// Two consequences are worth stating rather than discovering.
356365
///

0 commit comments

Comments
 (0)