Best-effort delivery with transient-local durability
Rule 3 · applies to publishers and subscribers · Back to all rules
Breaks a guarantee. Late joiners may not receive the stored history you expect, because best-effort never retransmits the retained samples.
- Settings involved: Reliability and Durability
- What QoS Guard checks:
[DURABL ≥ TRAN_LOCAL] ∧ [RELIAB = BEST_EFFORT]
Example
A latched map topic uses TRANSIENT_LOCAL but best-effort. A node that starts late can miss the map instead of receiving the retained copy.
How to fix it
Use RELIABLE together with TRANSIENT_LOCAL so late joiners actually receive the retained samples.
Why this rule is flagged
What the DDS specification says
The DDS specification does not settle this case on its own, so the rule rests on the engine's implementation and direct measurement.
What the engine source code shows
TRANSIENT_LOCAL late-join replay is gated on the reliable delivery path in both Fast DDS and Cyclone DDS. Under best-effort reliability, historical data is not delivered to late joiners.
Fast DDS implementation evidence
// [TRANSIENT_LOCAL late-joiner logic resides within if (is_reliable)]
// → When best-effort, it returns false, preventing the later-joiner logic from executing → Transient behaviour does not occur
bool is_reliable = rp->is_reliable();
if (is_reliable)
{
SequenceNumber_t min_seq = get_seq_num_min();
SequenceNumber_t last_seq = get_seq_num_max();
RTPSMessageGroup group(mp_RTPSParticipant, this, rp->message_sender());
// History not empty
if (min_seq != SequenceNumber_t::unknown())
{
(void)last_seq;
assert(last_seq != SequenceNumber_t::unknown());
assert(min_seq <= last_seq);
try {
// Late-joiner
if (TRANSIENT_LOCAL <= rp->durability_kind() && TRANSIENT_LOCAL <= m_att.durabilityKind)
Cyclone DDS implementation evidence
// Providing historical data to late joiners is only performed when it is more efficient than best-effort processing.
/* Store available data into the late joining reader when it is reliable (we don't do
historical data for best-effort data over the wire, so also not locally). */
if (rd->xqos->reliability.kind > DDS_RELIABILITY_BEST_EFFORT && rd->xqos->durability.kind > DDS_DURABILITY_VOLATILE)
ddsi_deliver_historical_data (wr, rd);
| Item | Value |
|---|---|
| Dataset | Download CSV |
| Fixed QoS setting | None |
| Tested variable | RELIAB.kind, DURABL.kind, late_join_ms |
| Tested values | RELIAB ∈ {BEST_EFFORT, RELIABLE}, DURABL ∈ {VOLATILE, TRANSIENT_LOCAL}, late_join_ms ∈ {50, 150, 350, 550} |
| Rule-relevant case | RELIAB = BEST_EFFORT, DURABL = TRANSIENT_LOCAL |
| Tested engines / versions | Fast DDS 2.14.6 (Jazzy), Cyclone DDS 0.10.5 |
| Network setting | RTT = 1 ms, loss ∈ {0%, 20%}, PP = 50 ms, message size = 1024 B |


Late-join recovery was observed only for RELIAB = RELIABLE with DURABL = TRANSIENT_LOCAL in the tested loopback measurements.
What the measurements show
The tested late-join measurements for this rule are shown with the source trace above, so there is no separate result to repeat here.