Skip to main content

Liveliness QoS Policy

The Liveliness policy detects when writers become unavailable.

Purpose

Liveliness monitors writer health:

  • Writers assert their liveliness periodically
  • Readers detect when writers stop responding
  • Enables failure detection and recovery

Kinds

KindDescriptionAssertion Method
AutomaticDDS infrastructure handles assertionsImplicit (network activity)
ManualByParticipantApplication asserts per participantExplicit call
ManualByTopicApplication asserts per writerExplicit call

Automatic (Default)

DDS automatically asserts liveliness based on network activity.

use hdds::prelude::*;
use std::time::Duration;

let qos = DataWriterQos::default()
.liveliness(Liveliness::Automatic {
lease_duration: Duration::from_secs(10),
});

Behavior:

  • Any DDS activity (write, heartbeat) counts as assertion
  • Simplest to use
  • Lease duration defines "alive" timeout

Manual By Participant

Application explicitly asserts liveliness for all writers in a participant.

let qos = DataWriterQos::default()
.liveliness(Liveliness::ManualByParticipant {
lease_duration: Duration::from_secs(5),
});

// In application loop
loop {
participant.assert_liveliness()?;
std::thread::sleep(Duration::from_secs(1));
}

Behavior:

  • Single assertion covers all writers in participant
  • Useful for grouped health monitoring
  • Application controls assertion timing

Manual By Topic

Application explicitly asserts liveliness per writer.

let qos = DataWriterQos::default()
.liveliness(Liveliness::ManualByTopic {
lease_duration: Duration::from_secs(5),
});

// In application loop
loop {
writer.assert_liveliness()?;
std::thread::sleep(Duration::from_secs(1));
}

Behavior:

  • Fine-grained control per writer
  • Detects individual writer failures
  • Most overhead but most precise

Lease Duration

The lease duration defines how long without assertion before considered "not alive":

// Writer must assert within 5 seconds
let writer_qos = DataWriterQos::default()
.liveliness(Liveliness::Automatic {
lease_duration: Duration::from_secs(5),
});

// Reader expects assertions within 10 seconds
let reader_qos = DataReaderQos::default()
.liveliness(Liveliness::Automatic {
lease_duration: Duration::from_secs(10),
});

Compatibility Rules

Both kind and lease duration must be compatible:

Kind Compatibility

WriterReaderMatch?
ManualByTopicManualByTopic✅ Yes
ManualByTopicManualByParticipant✅ Yes
ManualByTopicAutomatic✅ Yes
ManualByParticipantManualByParticipant✅ Yes
ManualByParticipantAutomatic✅ Yes
ManualByParticipantManualByTopic❌ No
AutomaticAutomatic✅ Yes
AutomaticManualByParticipant❌ No
AutomaticManualByTopic❌ No

Rule: Writer kind must be >= Reader kind (more strict → less strict)

Duration Compatibility

Writer lease must be ≤ Reader lease:

WriterReaderMatch?
5s10s✅ Yes
5s5s✅ Yes
10s5s❌ No

Rule: Writer.lease_duration ≤ Reader.lease_duration

Handling Liveliness Changes

Listener Callbacks

use hdds::prelude::*;

struct MyListener;

impl DataReaderListener for MyListener {
fn on_liveliness_changed(
&mut self,
reader: &DataReader<SensorData>,
status: LivelinessChangedStatus,
) {
println!(
"Alive writers: {}, Not alive: {}",
status.alive_count,
status.not_alive_count
);

if status.alive_count_change < 0 {
println!("Writer went offline!");
}
}
}

Status Polling

let status = reader.get_liveliness_changed_status()?;
if status.not_alive_count > 0 {
println!("{} writers are not responding", status.not_alive_count);
}

Use Cases

Heartbeat Monitoring

// Simple heartbeat for node health
let writer_qos = DataWriterQos::default()
.liveliness(Liveliness::ManualByTopic {
lease_duration: Duration::from_secs(3),
});

// Writer loop
loop {
writer.write(&heartbeat)?; // Implicitly asserts liveliness
std::thread::sleep(Duration::from_secs(1));
}

Failover Detection

// Primary/secondary pattern
let qos = DataWriterQos::default()
.liveliness(Liveliness::Automatic {
lease_duration: Duration::from_secs(5),
})
.ownership(Ownership::Exclusive)
.ownership_strength(OwnershipStrength::new(100));

// Reader detects primary failure
impl DataReaderListener for FailoverHandler {
fn on_liveliness_changed(&mut self, reader: &DataReader<State>, status: LivelinessChangedStatus) {
if status.alive_count == 0 {
self.activate_backup();
}
}
}

Application Health Check

// Application-level health monitoring
let participant = DomainParticipant::new(0)?;

let writer_qos = DataWriterQos::default()
.liveliness(Liveliness::ManualByParticipant {
lease_duration: Duration::from_secs(10),
});

// Health check thread
std::thread::spawn(move || {
loop {
if application_healthy() {
participant.assert_liveliness().ok();
}
std::thread::sleep(Duration::from_secs(2));
}
});

Comparison: Liveliness vs Deadline

AspectLivelinessDeadline
MonitorsWriter existenceData updates
GranularityPer writer or participantPer instance
TriggerNo assertionNo data received
Use caseFailure detectionData freshness

Use both for comprehensive monitoring:

let qos = DataWriterQos::default()
// Data must arrive every 100ms
.deadline(Deadline::new(Duration::from_millis(100)))
// Writer must be alive (even if not publishing)
.liveliness(Liveliness::Automatic {
lease_duration: Duration::from_secs(10),
});

Best Practices

  1. Set reader lease >= writer lease with margin for network delays
  2. Use ManualByTopic for critical per-writer monitoring
  3. Use Automatic when simplicity is preferred
  4. Assert faster than lease (e.g., assert every 1s for 5s lease)
// Good practice: Assert at 1/3 to 1/5 of lease duration
let lease = Duration::from_secs(5);
let assert_period = lease / 3; // ~1.67s

Performance Notes

  • Automatic: No application overhead
  • ManualByParticipant: Single assertion, minimal overhead
  • ManualByTopic: One assertion per writer, scales with writer count
  • Lease checking: ~1 μs per check

Next Steps