IMP Rules
# IMP Rules
This page describes the QoS dependency rules derived from the specific implementation behaviors of ROS 2 Middlewares (RMWs) such as eProsima Fast DDS and Eclipse Cyclone DDS. These dependencies are not explicitly mandated by the DDS standard but are critical for functional consistency in practice.
Stage 1
Intra-entity Dependency Validation
3
RELIAB → DURABL
Functional
[DURABL ≥ TRAN_LOCAL] ∧ [RELIAB = BEST_EFFORT]
4
RELIAB → OWNST
Functional
[OWNST = EXCLUSIVE] ∧ [RELIAB = BEST_EFFORT]
5
RELIAB → LIVENS
Functional
[LIVENS = MANUAL] ∧ [RELIAB = BEST_EFFORT]
7
LFSPAN → DEADLN
Functional
LFSPAN.duration < DEADLN.period
8
HIST → DESTORD
Functional
[DESTORD = BY_SOURCE] ∧ [HIST.kind = KEEP_LAST] ∧ [HIST.depth = 1]
9
RESLIM → DESTORD
Functional
[DESTORD = BY_SOURCE] ∧ [HIST.kind = KEEP_ALL] ∧ [RESLIM.mpi = 1]
10
DEADLN → OWNST
Functional
[OWNST = EXCLUSIVE] ∧ [DEADLN.period = ∞]
11
LIVENS → OWNST
Functional
[OWNST = EXCLUSIVE] ∧ [LIVENS.lease = ∞]
12
LIVENS → RDLIFE
Functional
[autopurge_nowriter > 0] ∧ [LIVENS.lease = ∞]
13
RDLIFE → DURABL
Functional
[DURABL ≥ TRANSIENT] ∧ [autopurge_disposed ≠ ∞]
14
PART → DEADLN
Functional
[DEADLN.period > 0] ∧ [PART.names ≠ ∅]
15
PART → LIVENS
Functional
[LIVENS = MANUAL] ∧ [PART.names ≠ ∅]
16
OWNST → WDLIFE
Functional
[autodispose = TRUE] ∧ [OWNST = EXCLUSIVE]
17
HIST → LFSPAN
Operational
[HIST.kind = KEEP_LAST] ∧ [LFSPAN.duration > HIST.depth × PP]
18
RESLIM → LFSPAN
Operational
[HIST.kind = KEEP_ALL] ∧ [LFSPAN.duration > RESLIM.mpi × PP]
19
ENTFAC → DURABL
Operational
[DURABL = VOLATILE] ∧ [autoenable = FALSE]
20
PART → DURABL
Operational
[DURABL ≥ TRAN_LOCAL] ∧ [PART.names ≠ ∅]
Stage 2
Inter-entity Dependency Validation
28
WDLIFE → RDLIFE
Functional
[W.autodispose = FALSE] ∧ [R.autopurge_nowriter = 0]
29
WDLIFE → RDLIFE
Operational
[W.autodispose = FALSE] ∧ [R.autopurge_disposed > 0]
30
WDLIFE → RDLIFE
Operational
[W.autodispose = FALSE] ∧ [R.autopurge_nowriter = ∞]
Stage 3
Dynamic & Performance Rules
34
RELIAB → WDLIFE
Functional
[autodispose = TRUE] ∧ [RELIAB = BEST_EFFORT]
40
DURABL → DEADLN
Operational
[DEADLN.period > 0] ∧ [DURABL ≥ TRAN_LOCAL]
Implementation Evidence Details
Below are the code-level justifications and source references for each IMP rule.
Rule 3
- RMW/Implementation: FastDDS
// [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) - RMW/Implementation: CycloneDDS
// 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);
Rule 4
- RMW/Implementation: FastDDS
// Only the FastDDS code contains code that checks this rule. if (m_reliability.kind == BEST_EFFORT_RELIABILITY_QOS && m_ownership.kind == EXCLUSIVE_OWNERSHIP_QOS) { logError(RTPS_QOS_CHECK, "BEST_EFFORT incompatible with EXCLUSIVE ownership"); return false; } return true;
Rule 5
- RMW/Implementation: FastDDS
// When using best effort, a StatelessWriter is created. // When using Manual_by_topic, a heartbeat is sent only if it is a StatefulWriter ⇒ No heartbeat is sent. if (qos_.liveliness().kind == MANUAL_BY_TOPIC_LIVELINESS_QOS) { // As described in the RTPS specification, if liveliness kind is manual a heartbeat must be sent // This only applies to stateful writers, as stateless writers do not send heartbeats StatefulWriter* stateful_writer = dynamic_cast<StatefulWriter*>(writer_); if (stateful_writer != nullptr) { stateful_writer->send_periodic_heartbeat(true, true); } } return RETCODE_OK; - RMW/Implementation: CycloneDDS
// Only when reliable → HB transmission // When best-effort, loss is possible, so live link status determination may be incorrect /* heartbeat event will be deleted when the handler can't find a writer for it in the hash table. NEVER => won't ever be scheduled, and this can only change by writing data, which won't happen until after it becomes visible. */ if (wr->reliable) wr->heartbeat_xevent = qxev_heartbeat (wr->evq, DDSRT_MTIME_NEVER, &wr->e.guid); else wr->heartbeat_xevent = NULL;
Rule 7
- RMW/Implementation: FastDDS
// The Reader's Lifespan and Deadline timers share the same History !History detail::DataReaderHistory history_; - RMW/Implementation: CycloneDDS
// Both timers operate independently // Deadline ddsrt_mtime_t deadline_next_missed_locked (struct deadline_adm *deadline_adm, ddsrt_mtime_t tnow, void **instance) { struct deadline_elem *elem = NULL; if (!ddsrt_circlist_isempty (&deadline_adm->list)) { struct ddsrt_circlist_elem *list_elem = ddsrt_circlist_oldest (&deadline_adm->list); elem = DDSRT_FROM_CIRCLIST (struct deadline_elem, e, list_elem); if (elem->t_deadline.v <= tnow.v) { ddsrt_circlist_remove (&deadline_adm->list, &elem->e); if (instance != NULL) *instance = (char *)elem - deadline_adm->elem_offset; return (ddsrt_mtime_t) { 0 }; } } if (instance != NULL) *instance = NULL; return (elem != NULL) ? elem->t_deadline : DDSRT_MTIME_NEVER; } // Lifespan ddsrt_mtime_t lifespan_next_expired_locked (const struct lifespan_adm *lifespan_adm, ddsrt_mtime_t tnow, void **sample) { struct lifespan_fhnode *node; if ((node = ddsrt_fibheap_min(&lifespan_fhdef, &lifespan_adm->ls_exp_heap)) != NULL && node->t_expire.v <= tnow.v) { *sample = (char *)node - lifespan_adm->fhn_offset; return (ddsrt_mtime_t) { 0 }; } *sample = NULL; return (node != NULL) ? node->t_expire : DDSRT_MTIME_NEVER; }
Rule 8
- RMW/Implementation: FastDDS
// Removal is performed by comparing the sourceTimestamp; if depth=1, existing older data is simply deleted. // Therefore, as there is only one piece of archived data, there is nothing to compare it with, rendering the process meaningless. // Try to substitute the oldest sample. CacheChange_t* first_change = instance_changes.at(0); if (a_change->sourceTimestamp >= first_change->sourceTimestamp) { // As the instance is ordered by source timestamp, we can always remove the first one. ret_value = remove_change_sub(first_change); } else { // Received change is older than oldest, and should be discarded return true; } - RMW/Implementation: CycloneDDS
// In BY_SOURCE_TIMESTAMP mode, samples that are "reversed relative to the source timestamp (i.e., past-time samples arriving late)" are discarded unconditionally. // This behaviour results in retaining only the single most recent sample, rather than performing any sorting function. static int inst_accepts_sample (const struct dds_rhc_default *rhc, const struct rhc_instance *inst, const struct ddsi_writer_info *wrinfo, const struct ddsi_serdata sample, const bool has_data) { if (rhc->by_source_ordering) { if (sample->timestamp.v > inst->tstamp.v) {/ ok /} else if (sample->timestamp.v < inst->tstamp.v) {return 0;} else if (inst_accepts_sample_by_writer_guid (inst, wrinfo)) {/ ok /} else {return 0;}} if (rhc->exclusive_ownership && inst->wr_iid_islive && inst->wr_iid != wrinfo->iid) {int32_t strength = wrinfo->ownership_strength; if (strength > inst->strength) { / ok / } else if (strength < inst->strength) { return 0; } else if (inst_accepts_sample_by_writer_guid (inst, wrinfo)) { / ok / } else {return 0;}} if (has_data && !content_filter_accepts (rhc->reader, sample, inst, wrinfo->iid, inst->iid)) {return 0;} return 1;}
Rule 9
- RMW/Implementation: FastDDS
// The KeepAll policy is capped at 1 by resourcelimits, meaning no new samples are added once the buffer is full. // Consequently, with only one piece of data being retained, there is nothing to compare it with, rendering it meaningless. (DataReaderHistory.cpp) InstanceCollection::iterator vit; if (find_key(a_change->instanceHandle, vit)) { DataReaderInstance::ChangeCollection& instance_changes = vit->second->cache_changes; size_t total_size = instance_changes.size() + unknown_missing_changes_up_to; if (total_size < static_cast<size_t>(resource_limited_qos_.max_samples_per_instance)) { return add_received_change_with_key(a_change, *vit->second); } logInfo(SUBSCRIBER, "Change not added due to maximum number of samples per instance"); } - RMW/Implementation: CycloneDDS
// Set to Keep_all, but limited to 1 due to max_samples_per_instance /* Check if resource max_samples QoS exceeded */ if (rhc->reader && rhc->max_samples != DDS_LENGTH_UNLIMITED && rhc->n_vsamples >= (uint32_t) rhc->max_samples) { cb_data->raw_status_id = (int) DDS_SAMPLE_REJECTED_STATUS_ID; cb_data->extra = DDS_REJECTED_BY_SAMPLES_LIMIT; cb_data->handle = inst->iid; cb_data->add = true; return false; } /* Check if resource max_samples_per_instance QoS exceeded */ if (rhc->reader && rhc->max_samples_per_instance != DDS_LENGTH_UNLIMITED && inst->nvsamples >= (uint32_t) rhc->max_samples_per_instance) { cb_data->raw_status_id = (int) DDS_SAMPLE_REJECTED_STATUS_ID; cb_data->extra = DDS_REJECTED_BY_SAMPLES_PER_INSTANCE_LIMIT; cb_data->handle = inst->iid; cb_data->add = true; return false; }
Rule 10
- RMW/Implementation: FastDDS
// Changing the owner in deadline_missed() void deadline_missed() { if (fastdds::rtps::c_Guid_Unknown != current_owner.first) { if (alive_writers.remove_if([&](const WriterOwnership& item) { return item.first == current_owner.first; })) { current_owner.second = 0; current_owner.first = fastdds::rtps::c_Guid_Unknown; if (alive_writers.empty() && (InstanceStateKind::ALIVE_INSTANCE_STATE == instance_state)) { instance_state = InstanceStateKind::NOT_ALIVE_NO_WRITERS_INSTANCE_STATE; } if (ALIVE_INSTANCE_STATE == instance_state) { update_owner(); } } } } // However, assume that the `deadline_missed()` function is not infinite! bool DataReaderImpl::deadline_timer_reschedule() { assert(qos_.deadline().period != dds::c_TimeInfinite); - RMW/Implementation: CycloneDDS
// When a deadline is missed, inst->wr_iid_islive is reset to 0, thereby changing ownership. // However, if the deadline is set to infinity, inst->wr_iid_islive will never be reset to 0. #endif /* DDS_HAS_LIFESPAN */ #ifdef DDS_HAS_DEADLINE_MISSED ddsrt_mtime_t dds_rhc_default_deadline_missed_cb(void *hc, ddsrt_mtime_t tnow) { struct dds_rhc_default *rhc = hc; void *vinst; ddsrt_mtime_t tnext; ddsrt_mutex_lock (&rhc->lock); while ((tnext = deadline_next_missed_locked (&rhc->deadline, tnow, &vinst)).v == 0) { struct rhc_instance *inst = vinst; deadline_reregister_instance_locked (&rhc->deadline, &inst->deadline, tnow); inst->wr_iid_islive = 0; ddsi_status_cb_data_t cb_data; cb_data.raw_status_id = (int) DDS_REQUESTED_DEADLINE_MISSED_STATUS_ID; cb_data.extra = 0; cb_data.handle = inst->iid; cb_data.add = true; ddsrt_mutex_unlock (&rhc->lock); dds_reader_status_cb (&rhc->reader->m_entity, &cb_data); ddsrt_mutex_lock (&rhc->lock); tnow = ddsrt_time_monotonic (); } ddsrt_mutex_unlock (&rhc->lock); return tnext;} #endif /* DDS_HAS_DEADLINE_MISSED */
Rule 11
- RMW/Implementation: FastDDS
// If the lease duration is infinite, the writer is not registered. Consequently, lease expiry → not alive does not occur → owner change fails. if (liveliness_lease_duration_ < c_TimeInfinite) { auto wlp = this->mp_RTPSParticipant->wlp(); if ( wlp != nullptr) { wlp->sub_liveliness_manager_->add_writer( wdata.guid(), liveliness_kind_, liveliness_lease_duration_); }
Rule 12
- RMW/Implementation: FastDDS
// livelinss lost switched to NOT_ALIVE // However, if set to infinite, the transition does not occur, so the sample cannot be automatically removed. bool LivelinessManager::timer_expired() { std::unique_lock<std::mutex> lock(mutex_); if (timer_owner_ == nullptr) { EPROSIMA_LOG_ERROR(RTPS_WRITER, "Liveliness timer expired but there is no writer"); return false; } else { timer_owner_->status = LivelinessData::WriterStatus::NOT_ALIVE; } - RMW/Implementation: CycloneDDS
// Upon expiry of the lease term, case DDSI_EK_PROXY_WRITER: ddsi_proxy_writer_set_notalive ((struct ddsi_proxy_writer *) l->entity, true); break; case DDSI_EK_WRITER: ddsi_writer_set_notalive ((struct ddsi_writer *) l->entity, true); break; // However, if set to infinite, it will not expire, so the data deletion function will not operate.
Rule 13
- RMW/Implementation: FastDDS
// Stored in permanent storage void StatefulPersistentReader::set_last_notified( const GUID_t& writer_guid, const SequenceNumber_t& seq) { history_state_->history_record[writer_guid] = seq; persistence_->update_writer_seq_on_storage(persistence_guid_, writer_guid, seq); } // But the data vanished in an instant. Duration_t autopurge_no_writer_samples_delay; /** * @brief Indicates the duration the DataReader must retain information regarding instances that have the * instance_state NOT_ALIVE_DISPOSED. <br> * By default, c_TimeInfinite. */
Rule 14
- RMW/Implementation: FastDDS
// Partition change → Unmatching → Call ddsi_rhc_unregister_wr (rd->rhc, &wrinfo); // ⇒ Instance is immediately unregistered from the deadline. → Deadline timer is not updated. if (inst->deadline_reg) { inst->deadline_reg = 0; deadline_unregister_instance_locked (&rhc->deadline, &inst->deadline); } // During the process of dynamically changing partitions, instances are removed from the deadline list due to writer unregistration. // Since the timer is not reset to the "next expiry time" upon unregistration, the deadline timer ceases to function once the list becomes empty.
Rule 15
- RMW/Implementation: FastDDS
// The writer signals liveliness, but if a reader becomes involved in the partition dynamic change process, it no longer receives updates to the writer's liveliness // → Subsequently, when the lease expires, the notification is sent only to readers that are "still matched". // However, if there are no readers, the liveliness notification is disabled even during the partition dynamic change process. static void proxy_writer_notify_liveliness_change_may_unlock (struct ddsi_proxy_writer *pwr) { struct ddsi_alive_state alive_state; proxy_writer_get_alive_state_locked (pwr, &alive_state); struct ddsi_guid rdguid; struct ddsi_pwr_rd_match *m; memset (&rdguid, 0, sizeof (rdguid)); while (pwr->alive_vclock == alive_state.vclock && (m = ddsrt_avl_lookup_succ (&ddsi_pwr_readers_treedef, &pwr->readers, &rdguid)) != NULL) { rdguid = m->rd_guid; ddsrt_mutex_unlock (&pwr->e.lock); /* unlocking pwr means alive state may have changed already; we break out of the loop once we detect this but there for the reader in the current iteration, anything is possible */ ddsi_reader_update_notify_pwr_alive_state_guid (&rdguid, pwr, &alive_state); ddsrt_mutex_lock (&pwr->e.lock); }} // → The recipients of the notification are only the readers listed in pwr→readers.
Rule 16
- RMW/Implementation: FastDDS
// The system is designed so that not just anyone can delete data; // only the sender with the most powerful permissions can alter the status. bool writer_dispose ( const fastrtps::rtps::GUID_t& writer_guid, const uint32_t ownership_strength) { bool ret_val = false; writer_set(writer_guid, ownership_strength); if (ownership_strength >= current_owner.second) { current_owner.first = writer_guid; current_owner.second = ownership_strength; if (InstanceStateKind::ALIVE_INSTANCE_STATE == instance_state) { ret_val = true; instance_state = InstanceStateKind::NOT_ALIVE_DISPOSED_INSTANCE_STATE; } } return ret_val; } - RMW/Implementation: CycloneDDS
// Only writers with a stronger strength can now modify the instance's state. static int inst_accepts_sample (const struct dds_rhc_default *rhc, const struct rhc_instance *inst, const struct ddsi_writer_info *wrinfo, const struct ddsi_serdata sample, const bool has_data) {if (rhc->by_source_ordering){ if (sample->timestamp.v > inst->tstamp.v) {/ ok /} else if (sample->timestamp.v < inst->tstamp.v) {return 0;} else if (inst_accepts_sample_by_writer_guid (inst, wrinfo)) {/ ok /} else {return 0;}} if (rhc->exclusive_ownership && inst->wr_iid_islive && inst->wr_iid != wrinfo->iid) {int32_t strength = wrinfo->ownership_strength; if (strength > inst->strength) { / ok / } else if (strength < inst->strength) { return 0; } else if (inst_accepts_sample_by_writer_guid (inst, wrinfo)) { / ok */ } else { return 0;}}
Rule 17
- RMW/Implementation:FastDDS
// Keep_last removes the oldest sample when the depth is full. {// Try to substitute the oldest sample. CacheChange_t* first_change = instance_changes.at(0); if (a_change->sourceTimestamp >= first_change->sourceTimestamp){ // As the instance is ordered by source timestamp, we can always remove the first one. ret_value = remove_change_sub(first_change);} else{ // Received change is older than oldest, and should be discarded return true;}} // Looking at the lifespan timer code, it states that it "may already have been removed from the history". CacheChange_t* earliest_change; while (history_.get_earliest_change(&earliest_change)) { fastdds::rtps::Time_t expiration_ts = earliest_change->sourceTimestamp + qos_.lifespan().duration; // Check that the earliest change has expired (the change which started the timer could have been removed from the history) if (current_ts < expiration_ts) { fastdds::rtps::Time_t interval = expiration_ts - current_ts; lifespan_timer_->update_interval_millisec(interval.to_ns() * 1e-6); return true; } // The earliest change has expired history_.remove_change_sub(earliest_change); try_notify_read_conditions(); } return false; - RMW/Implementation:CycloneDDS
// A situation where a sample is overwritten and disappears due to history depth before it "expires" based on its lifespan. // In the code, it deletes data using keep_last and then deletes it again based on lifespan. if (inst->nvsamples == rhc->history_depth) { /* replace oldest sample; latest points to the latest one, the list is circular from old -> new, so latest->next is the oldest */ inst_clear_invsample_if_exists (rhc, inst, trig_qc); assert (inst->latest != NULL); s = inst->latest->next; assert (trig_qc->dec_conds_sample == 0); ddsi_serdata_unref (s->sample); #ifdef DDS_HAS_LIFESPAN lifespan_unregister_sample_locked (&rhc->lifespan, &s->lifespan); #endif trig_qc->dec_sample_read = s->isread; trig_qc->dec_conds_sample = s->conds; if (s->isread) { inst->nvread--; rhc->n_vread--; } }
Rule 18
- RMW/Implementation: FastDDS
// Keep_All waits until an ACK is received confirming that the oldest data has been successfully delivered to the other party before deleting the data. else if (history_qos_.kind == KEEP_ALL_HISTORY_QOS) { if (vit->second.cache_changes.size() < static_cast<size_t>(resource_limited_qos_.max_samples_per_instance)) { add = true; } else { SequenceNumber_t seq_to_remove = vit->second.cache_changes.front()->sequenceNumber; if (!mp_writer->wait_for_acknowledgement(seq_to_remove, max_blocking_time, lock)) { // Timeout waiting. Will not add change to history. break; } // vit may have been invalidated if (!find_or_add_key(change->instanceHandle, change->serializedPayload, &vit)) { break; } // If the change we were trying to remove was already removed, try again if (vit->second.cache_changes.empty() || vit->second.cache_changes.front()->sequenceNumber != seq_to_remove) { continue; } // Remove change if still present add = remove_change_pub(vit->second.cache_changes.front()); } } // Looking at the lifespan timer code, it states that it "may already have been removed from the history". CacheChange_t* earliest_change; while (history_.get_earliest_change(&earliest_change)) { fastdds::rtps::Time_t expiration_ts = earliest_change->sourceTimestamp + qos_.lifespan().duration; // Check that the earliest change has expired (the change which started the timer could have been removed from the history) if (current_ts < expiration_ts) { fastdds::rtps::Time_t interval = expiration_ts - current_ts; lifespan_timer_->update_interval_millisec(interval.to_ns() * 1e-6); return true; } // The earliest change has expired history_.remove_change_sub(earliest_change); try_notify_read_conditions(); } return false; - RMW/Implementation: CycloneDDS
// Although set to Keep_all, due to max_samples_per_instance, samples are not saved at all before their lifespan expires, meaning no samples are deleted by lifespan. /* Check if resource max_samples QoS exceeded */ if (rhc->reader && rhc->max_samples != DDS_LENGTH_UNLIMITED && rhc->n_vsamples >= (uint32_t) rhc->max_samples) { cb_data->raw_status_id = (int) DDS_SAMPLE_REJECTED_STATUS_ID; cb_data->extra = DDS_REJECTED_BY_SAMPLES_LIMIT; cb_data->handle = inst->iid; cb_data->add = true; return false; } /* Check if resource max_samples_per_instance QoS exceeded */ if (rhc->reader && rhc->max_samples_per_instance != DDS_LENGTH_UNLIMITED && inst->nvsamples >= (uint32_t) rhc->max_samples_per_instance) { cb_data->raw_status_id = (int) DDS_SAMPLE_REJECTED_STATUS_ID; cb_data->extra = DDS_REJECTED_BY_SAMPLES_PER_INSTANCE_LIMIT; cb_data->handle = inst->iid; cb_data->add = true; return false; }
Rule 19
- RMW/Implementation: FastDDS
// autoenable_created_entities = FALSE means entities are not automatically enabled. publisher_->rtps_participant()->register_writer(writer_, topic_desc, wqos); // ⇒ Only within enable() are register_writer() / register_reader() called to register with discovery. // As VOLATILE entities do not become existing until enabled, any previously sent data is discarded. // When set to Volatile, upon enabling, all previous data will not be received. - RMW/Implementation: CycloneDDS
// When set to VOLATILE, previous data is not fully retrieved. // The implementation where autoenable_created_entities=FALSE is not present. if(qos->durability.kind == DDS_DURABILITY_VOLATILE) { opts.historyCapacity = 0; } else { // Transient Local and stronger if (qos->durability_service.history.kind == DDS_HISTORY_KEEP_LAST) { opts.historyCapacity = (uint64_t)qos->durability_service.history.depth; } else { opts.historyCapacity = 0; } }
Rule 20
- RMW/Implementation: FastDDS
// Upon successful matching, it may be treated as a later-join, potentially resulting in duplicate data being received. if (!matched) //Different partitions { EPROSIMA_LOG_WARNING(RTPS_EDP, "INCOMPATIBLE QOS (topic: " << rdata->topic_name << "): Different Partitions"); reason.set(MatchingFailureMask::partitions); } // transient_local always enters PRMSS_SYNC upon (re)matching and does not verify whether it has already received the entire data // → Consequently, even if the partition is rematched, it is treated as a new match, leading to redundant data reception. if (rd->handle_as_transient_local) m->in_sync = PRMSS_OUT_OF_SYNC; else if (vendor_is_eclipse (pwr->c.vendor)) m->in_sync = PRMSS_OUT_OF_SYNC; else m->in_sync = PRMSS_SYNC; m->u.not_in_sync.end_of_tl_seq = MAX_SEQ_NUMBER;
Rule 28 / Rule 29 / Rule 30
- RMW/Implementation: FastDDS
// If autodispose_unregistered_instances=false and the application does not explicitly call dispose(), the DataWriter will not send a disposal notification (DISPOSE), meaning the NOT_ALIVE_DISPOSED state is not passed to the DataReader. // Consequently, the autopurge_disposed_samples_delay setting in READER_DATA_LIFECYCLE becomes irrelevant for that instance, rendering it meaningless. /** * @brief Indicates the duration the DataReader must retain information regarding instances that have the * instance_state NOT_ALIVE_DISPOSED. <br> * By default, dds::c_TimeInfinite. */ dds::Duration_t autopurge_disposed_samples_delay; - RMW/Implementation: CycloneDDS
// As the instance is not in the DISPOSED state, autopurge_disposed_samples_delay does not apply to it in the first place. void ddsi_reader_update_notify_pwr_alive_state (struct ddsi_reader *rd, const struct ddsi_proxy_writer *pwr, const struct ddsi_alive_state *alive_state) { struct ddsi_rd_pwr_match *m; bool notify = false; int delta = 0; /* -1: alive -> not_alive; 0: unchanged; 1: not_alive -> alive */ ddsrt_mutex_lock (&rd->e.lock); if ((m = ddsrt_avl_lookup (&ddsi_rd_writers_treedef, &rd->writers, &pwr->e.guid)) != NULL) { if ((int32_t) (alive_state->vclock - m->pwr_alive_vclock) > 0) { delta = (int) alive_state->alive - (int) m->pwr_alive; notify = true; m->pwr_alive = alive_state->alive; m->pwr_alive_vclock = alive_state->vclock; } } ddsrt_mutex_unlock (&rd->e.lock); if (delta < 0 && rd->rhc) { struct ddsi_writer_info wrinfo; ddsi_make_writer_info (&wrinfo, &pwr->e, pwr->c.xqos, NN_STATUSINFO_UNREGISTER); ddsi_rhc_unregister_wr (rd->rhc, &wrinfo); }
Rule 34
- RMW/Implementation: FastDDS
// Set to best-effort mode, so NOT_ALIVE_DISPOSED_UNREGISTERED messages may be lost. f !defined(NDEBUG) if (history_.is_key_registered(ih)) { WriteParams wparams; ChangeKind_t change_kind = NOT_ALIVE_DISPOSED; if (!dispose) { change_kind = qos_.writer_data_lifecycle().autodispose_unregistered_instances ? NOT_ALIVE_DISPOSED_UNREGISTERED : NOT_ALIVE_UNREGISTERED; } - RMW/Implementation: CycloneDDS
// There is absolutely no retransmission/ACK-based guarantee for the transmitted sample (data + unregister/dispose control message). static int rhc_unregister_updateinst ( …. if (!inst->isdisposed) { if (inst->latest == NULL || inst->latest->isread) { inst_set_invsample (rhc, inst, trig_qc, nda); update_inst_no_wr_iid (inst, wrinfo, tstamp); } if (!inst->autodispose) rhc->n_not_alive_no_writers++; // When the Writer sends an unregister request, the Reader's RHC receives it and, if auto_dispose == 1, transitions the instance to the DISPOSED state.
Rule 40
- RMW/Implementation: FastDDS
// 1. Writer previously issued multiple samples (e.g., one hour ago) // 2. Reader joins late and receives past samples as TRANSIENT_LOCAL // 3. Each retransmitted sample calls on_new_cache_change_added // 4. Deadline timer is reset with each retransmission bool DataReaderImpl::on_new_cache_change_added( const CacheChange_t* const change) { std::lock_guard<RecursiveTimedMutex> guard(reader_->getMutex()); if (qos_.deadline().period != c_TimeInfinite) { if (!history_.set_next_deadline( change->instanceHandle, steady_clock::now() + duration_cast<system_clock::duration>(deadline_duration_us_))) { logError(SUBSCRIBER, "Could not set next deadline in the history"); } else if (timer_owner_ == change->instanceHandle || timer_owner_ == InstanceHandle_t()) { if (deadline_timer_reschedule()) { deadline_timer_->cancel_timer(); deadline_timer_->restart_timer(); } } } - RMW/Implementation: CycloneDDS
The deadline is set based on the sample reception time // → With the transient setting, previous data arrives, causing the sample reception time to reset the deadline. static void postprocess_instance_update (struct dds_rhc_default * __restrict rhc, struct rhc_instance * __restrict * __restrict instptr, const struct trigger_info_pre *pre, const struct trigger_info_post *post, struct trigger_info_qcond *trig_qc) { { struct rhc_instance *inst = *instptr; #ifdef DDS_HAS_DEADLINE_MISSED if (inst->isdisposed) { if (inst->deadline_reg) { inst->deadline_reg = 0; deadline_unregister_instance_locked (&rhc->deadline, &inst->deadline); } } else { if (inst->deadline_reg) deadline_renew_instance_locked (&rhc->deadline, &inst->deadline); else { deadline_register_instance_locked (&rhc->deadline, &inst->deadline, ddsrt_time_monotonic ()); inst->deadline_reg = 1; } } #endif