Skip to main content

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
caution

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​