Aller au contenu principal

Resource Limits QoS Policy

The Resource Limits policy controls the maximum resources that a DataWriter or DataReader can use to store samples. It bounds memory consumption by limiting the total number of samples, instances, samples per instance, and total payload bytes.

Purpose

Resource Limits protect against unbounded memory growth:

  • max_samples: Total samples across all instances
  • max_instances: Maximum number of tracked data instances (keyed topics)
  • max_samples_per_instance: Maximum samples stored per instance
  • max_quota_bytes: Maximum total payload size in bytes

Configuration

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

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

// Writer with default limits
let writer = participant
.topic::<SensorData>("sensors/data")?
.writer()
.qos(QoS::reliable().transient_local().keep_last(100))
.build()?;

// Reader with strict resource limits
let mut limited_qos = QoS::reliable().transient_local();
limited_qos.resource_limits = hdds::qos::ResourceLimits {
max_samples: 5,
max_instances: 1,
max_samples_per_instance: 5,
max_quota_bytes: 1_000_000,
};

let reader = participant
.topic::<SensorData>("sensors/data")?
.reader()
.qos(limited_qos)
.build()?;
#include <hdds.h>

/* Reader with resource limits: max 5 samples, 1 instance */
struct HddsQoS* qos = hdds_qos_reliable();
hdds_qos_set_resource_limits(qos,
5, /* max_samples */
1, /* max_instances */
5 /* max_samples_per_instance */
);

struct HddsDataReader* reader = hdds_reader_create_with_qos(
participant, "sensors/data", qos);
hdds_qos_destroy(qos);

Default Values

FieldDefaultDescription
max_samples100,000Total samples across all instances
max_instances100,000Maximum data instances
max_samples_per_instance100,000Samples per instance
max_quota_bytes256 MBTotal payload bytes

Fields

max_samples

The maximum total number of samples that can be stored across all instances. Once this limit is reached:

  • KEEP_LAST: Oldest samples are evicted (FIFO)
  • KEEP_ALL: New samples are rejected
let mut qos = QoS::reliable();
qos.resource_limits.max_samples = 1000;

max_instances

The maximum number of unique data instances (keys) that can be tracked. For unkeyed topics, this is effectively 1.

let mut qos = QoS::reliable();
qos.resource_limits.max_instances = 10; // Track up to 10 sensors

max_samples_per_instance

The maximum number of samples stored per instance (key). Works in conjunction with the History QoS depth.

let mut qos = QoS::reliable();
qos.resource_limits.max_samples_per_instance = 50;

max_quota_bytes

The maximum total payload size in bytes. This provides a memory ceiling independent of sample count.

let mut qos = QoS::reliable();
qos.resource_limits.max_quota_bytes = 10 * 1024 * 1024; // 10 MB

How Resource Limits Work

resource_limits(max_samples=5, max_instances=1, max_samples_per_instance=5)

Publisher sends 20 messages:

[1][2][3][4][5][6][7][8][9][10]...[20]

Reader A (limited to 5 samples):

Cache: [1][2][3][4][5] -- FULL, depends on history policy
or: [16][17][18][19][20] -- with KEEP_LAST(5)

Reader B (no limits):

Cache: [1][2][3][4][5]...[18][19][20] -- all 20 retained

Consistency Rules

Resource Limits must be consistent with History QoS:

HistoryConstraint
keep_last(N)N <= max_samples_per_instance
keep_all()max_samples_per_instance defines the actual limit
attention

If history.depth > resource_limits.max_samples_per_instance, the effective depth is clamped to max_samples_per_instance.

Additional consistency:

  • max_samples >= max_samples_per_instance (total must accommodate at least one full instance)
  • max_samples >= max_instances (at least one sample per instance)

Use Cases

Embedded Systems with Strict Memory

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

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

// Strict memory budget: 100 samples max, 10KB payload limit
let mut qos = QoS::reliable().keep_last(10);
qos.resource_limits = hdds::qos::ResourceLimits {
max_samples: 100,
max_instances: 10,
max_samples_per_instance: 10,
max_quota_bytes: 10_240,
};

let reader = participant
.topic::<SensorData>("sensors/data")?
.reader()
.qos(qos)
.build()?;

Bounded Command Queue

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

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

// Accept at most 50 pending commands
let mut qos = QoS::reliable().keep_all();
qos.resource_limits = hdds::qos::ResourceLimits {
max_samples: 50,
max_instances: 1,
max_samples_per_instance: 50,
max_quota_bytes: 1_000_000,
};

let reader = participant
.topic::<Command>("robot/commands")?
.reader()
.qos(qos)
.build()?;

Multi-Instance with Per-Key Limits

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

#[derive(Debug, Clone, DDS)]
struct SensorReading {
#[key]
sensor_id: u32,
value: f32,
}

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

// Track up to 100 sensors, 10 readings each
let mut qos = QoS::reliable().keep_last(10);
qos.resource_limits = hdds::qos::ResourceLimits {
max_samples: 1000, // 100 sensors x 10 readings
max_instances: 100, // 100 sensors
max_samples_per_instance: 10, // 10 readings per sensor
max_quota_bytes: 10_000_000, // 10 MB total
};

let reader = participant
.topic::<SensorReading>("sensors/readings")?
.reader()
.qos(qos)
.build()?;

Interaction with Other Policies

Resource Limits + History

HistoryResource LimitsBehavior
keep_last(10)max_spi=10Consistent: keeps last 10 per instance
keep_last(100)max_spi=10Effective depth is 10 (clamped)
keep_all()max_spi=50Keeps up to 50 per instance, then rejects

Resource Limits + Reliability

ReliabilityLimits HitBehavior
reliable() + keep_lastmax_samples exceededOldest evicted, newest accepted
reliable() + keep_allmax_samples exceededWrite blocks or fails
best_effort()max_samples exceededNewest samples dropped

Resource Limits + Durability

With transient_local(), resource limits also apply to the writer-side cache:

  • The writer stores at most max_samples for late joiners
  • Expired entries (via Lifespan) free up space within the limits

Common Pitfalls

  1. Limits too tight for reliable delivery: With reliable() + keep_all(), if max_samples is too small, the writer may block when the cache fills. Size limits to accommodate burst traffic.

  2. Inconsistent with history depth: Setting keep_last(100) but max_samples_per_instance=10 silently clamps the effective depth to 10. Ensure they are consistent.

  3. Forgetting max_quota_bytes: Even with a reasonable sample count limit, large payloads can exhaust memory. Set max_quota_bytes as an additional safety net.

  4. max_instances for unkeyed topics: For unkeyed topics, max_instances should be at least 1. Setting it to 0 would prevent any data storage.

Performance Notes

  • Resource limit checks add minimal overhead (counter comparisons)
  • Tighter limits reduce memory footprint
  • With keep_last, eviction is O(1)
  • max_quota_bytes requires tracking cumulative payload size

Next Steps