Aller au contenu principal

Lifespan QoS Policy

The Lifespan policy specifies the maximum duration a data sample remains valid. Samples older than the lifespan are considered expired and are automatically discarded from the reader cache.

Purpose

Lifespan controls data freshness:

  • Writers declare how long their samples remain meaningful
  • Readers automatically discard expired samples
  • Late joiners only receive samples that are still within their validity window
Difference from Deadline

Deadline monitors the time between samples (publication rate). Lifespan controls the time for a sample (validity duration). They solve different problems and can be combined.

Configuration

use hdds::{Participant, QoS, TransportMode};
use std::time::Duration;

let participant = Participant::builder("sensor_app")
.domain_id(0)
.with_transport(TransportMode::UdpMulticast)
.build()?;

// Writer: samples expire after 5 seconds
let writer = participant
.topic::<SensorData>("sensors/temperature")?
.writer()
.qos(QoS::reliable().transient_local().lifespan_secs(5))
.build()?;

// Reader: also configured with lifespan
let reader = participant
.topic::<SensorData>("sensors/temperature")?
.reader()
.qos(QoS::reliable().transient_local().lifespan_secs(5))
.build()?;
#include <hdds.h>

/* Create writer with 5-second lifespan */
struct HddsQoS* qos = hdds_qos_transient_local();
hdds_qos_set_lifespan_ns(qos, 5000000000ULL); /* 5s in nanoseconds */

struct HddsDataWriter* writer = hdds_writer_create_with_qos(
participant, "sensors/temperature", qos);
hdds_qos_destroy(qos);

Default Value

Default is infinite (samples never expire):

let qos = QoS::reliable();
// lifespan = Duration::MAX (infinite, no expiration)

Fluent Builder Methods

MethodDescription
.lifespan_secs(n)Set lifespan in seconds
.lifespan_millis(n)Set lifespan in milliseconds

How Expiration Works

Lifespan: 2 seconds

Time: 0.0s 0.5s 1.0s 1.5s 2.0s 2.5s 3.0s
| | | | | | |
Write: [1] [2] [3] [4] [5] [6] [7]
| | | | | | |
| expires expires | | |
| at 2.5s at 3.0s | | |
DEAD DEAD DEAD DEAD alive alive alive
^
|
Late-joiner at t=3.0s only sees [5][6][7]

Expiration is enforced at multiple points:

  1. Writer cache: Expired samples are purged before sending to late joiners
  2. Reader cache: Expired samples are removed before delivery to the application
  3. On read/take: The is_expired() check filters out stale data

Compatibility Rules

Writer lifespan must be greater than or equal to Reader lifespan:

WriterReaderMatch?
10s5sYes
5s5sYes
5s10sNo
Infinite5sYes
5sInfiniteNo
InfiniteInfiniteYes

Rule: Writer.lifespan >= Reader.lifespan

Incompatibility

A writer with a shorter lifespan cannot satisfy a reader expecting longer validity. The reader would receive samples that expire before it can process them.

Use Cases

Sensor Data Aging

use hdds::{Participant, QoS, TransportMode};

let participant = Participant::builder("sensor_system")
.domain_id(0)
.with_transport(TransportMode::UdpMulticast)
.build()?;

// IMU readings become stale after 100ms
let writer = participant
.topic::<ImuData>("sensors/imu")?
.writer()
.qos(QoS::reliable().transient_local().lifespan_millis(100))
.build()?;

Time-Sensitive Commands

use hdds::{Participant, QoS, TransportMode};

let participant = Participant::builder("command_system")
.domain_id(0)
.with_transport(TransportMode::UdpMulticast)
.build()?;

// Commands expire after 1 second if not processed
let writer = participant
.topic::<Command>("robot/commands")?
.writer()
.qos(QoS::reliable().transient_local().lifespan_secs(1))
.build()?;

Cache Freshness for Late Joiners

use hdds::{Participant, QoS, TransportMode};

let participant = Participant::builder("cache_demo")
.domain_id(0)
.with_transport(TransportMode::UdpMulticast)
.build()?;

// Combined with TRANSIENT_LOCAL: late joiners get only fresh data
let writer = participant
.topic::<MarketData>("market/prices")?
.writer()
.qos(QoS::reliable().transient_local().lifespan_secs(10))
.build()?;

// Late-joining reader gets cached data that is still within 10s window
let reader = participant
.topic::<MarketData>("market/prices")?
.reader()
.qos(QoS::reliable().transient_local().lifespan_secs(10))
.build()?;

Interaction with Other Policies

Lifespan + Durability

LifespanDurabilityBehavior
Setvolatile()Samples expire but no cache for late joiners
Settransient_local()Late joiners get only non-expired cached samples
Infinitetransient_local()Late joiners get all cached samples
attention

Lifespan without transient_local() or persistent() has limited impact since samples are only delivered to currently matched readers anyway.

Lifespan + History

With keep_last(N), both history depth and lifespan work together. A sample can be evicted by either:

  • Being pushed out by newer samples (history depth exceeded)
  • Exceeding the lifespan duration

Lifespan + Deadline

PolicyPurpose
Deadline"I expect a new sample every N ms"
Lifespan"Each sample is valid for N ms"

These are complementary and often used together for periodic sensor data.

Common Pitfalls

  1. Lifespan without durability: Setting lifespan on volatile() writers has minimal effect since data is only sent to currently-matched readers.

  2. Too-short lifespan on slow networks: If the lifespan is shorter than the network latency, samples may expire before reaching the reader.

  3. Clock synchronization: In distributed systems, ensure clocks are reasonably synchronized. Large clock skew can cause premature expiration or delayed expiration.

  4. Forgetting reader-side lifespan: For late-joiner scenarios, set lifespan on both writer and reader QoS.

Performance Notes

  • Lifespan checking adds minimal CPU overhead (timestamp comparison per sample)
  • Expired samples are lazily purged, not on a timer
  • Memory for expired samples is reclaimed when the cache is accessed
  • With transient_local(), the writer cache size is bounded by both history depth and lifespan

Next Steps