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
| Field | Default | Description |
|---|---|---|
max_samples | 100,000 | Total samples across all instances |
max_instances | 100,000 | Maximum data instances |
max_samples_per_instance | 100,000 | Samples per instance |
max_quota_bytes | 256 MB | Total 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:
| History | Constraint |
|---|---|
keep_last(N) | N <= max_samples_per_instance |
keep_all() | max_samples_per_instance defines the actual limit |
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
| History | Resource Limits | Behavior |
|---|---|---|
keep_last(10) | max_spi=10 | Consistent: keeps last 10 per instance |
keep_last(100) | max_spi=10 | Effective depth is 10 (clamped) |
keep_all() | max_spi=50 | Keeps up to 50 per instance, then rejects |
Resource Limits + Reliability
| Reliability | Limits Hit | Behavior |
|---|---|---|
reliable() + keep_last | max_samples exceeded | Oldest evicted, newest accepted |
reliable() + keep_all | max_samples exceeded | Write blocks or fails |
best_effort() | max_samples exceeded | Newest samples dropped |
Resource Limits + Durability
With transient_local(), resource limits also apply to the writer-side cache:
- The writer stores at most
max_samplesfor late joiners - Expired entries (via Lifespan) free up space within the limits
Common Pitfalls
-
Limits too tight for reliable delivery: With
reliable()+keep_all(), ifmax_samplesis too small, the writer may block when the cache fills. Size limits to accommodate burst traffic. -
Inconsistent with history depth: Setting
keep_last(100)butmax_samples_per_instance=10silently clamps the effective depth to 10. Ensure they are consistent. -
Forgetting max_quota_bytes: Even with a reasonable sample count limit, large payloads can exhaust memory. Set
max_quota_bytesas an additional safety net. -
max_instances for unkeyed topics: For unkeyed topics,
max_instancesshould 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_bytesrequires tracking cumulative payload size
Next Steps
- History - Sample buffering depth
- Reliability - Delivery guarantees
- Overview - All QoS policies