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
| Kind | Description | Assertion Method |
|---|---|---|
Automatic | DDS infrastructure handles assertions | Implicit (network activity) |
ManualByParticipant | Application asserts per participant | Explicit call |
ManualByTopic | Application asserts per writer | Explicit 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
| Writer | Reader | Match? |
|---|---|---|
| ManualByTopic | ManualByTopic | ✅ Yes |
| ManualByTopic | ManualByParticipant | ✅ Yes |
| ManualByTopic | Automatic | ✅ Yes |
| ManualByParticipant | ManualByParticipant | ✅ Yes |
| ManualByParticipant | Automatic | ✅ Yes |
| ManualByParticipant | ManualByTopic | ❌ No |
| Automatic | Automatic | ✅ Yes |
| Automatic | ManualByParticipant | ❌ No |
| Automatic | ManualByTopic | ❌ No |
Rule: Writer kind must be >= Reader kind (more strict → less strict)
Duration Compatibility
Writer lease must be ≤ Reader lease:
| Writer | Reader | Match? |
|---|---|---|
| 5s | 10s | ✅ Yes |
| 5s | 5s | ✅ Yes |
| 10s | 5s | ❌ 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
| Aspect | Liveliness | Deadline |
|---|---|---|
| Monitors | Writer existence | Data updates |
| Granularity | Per writer or participant | Per instance |
| Trigger | No assertion | No data received |
| Use case | Failure detection | Data 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
- Set reader lease >= writer lease with margin for network delays
- Use ManualByTopic for critical per-writer monitoring
- Use Automatic when simplicity is preferred
- 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