Ownership QoS Policy
The Ownership policy controls whether multiple DataWriters can update the same data instance simultaneously. Combined with Ownership Strength, it enables primary/backup failover patterns.
Purpose
Ownership provides writer arbitration:
- SHARED (default): All writers can publish to the same instance; readers receive from all of them
- EXCLUSIVE: Only the writer with the highest Ownership Strength can publish to a given instance; lower-strength writers are ignored
Ownership Kinds
| Kind | Behavior | Use Case |
|---|---|---|
ownership_shared() | All writers deliver (default) | Normal publish/subscribe |
ownership_exclusive() | Only highest-strength writer wins | Primary/backup, failover |
Configuration
Shared Ownership (Default)
use hdds::{Participant, QoS, TransportMode};
let participant = Participant::builder("shared_app")
.domain_id(0)
.with_transport(TransportMode::UdpMulticast)
.build()?;
// Multiple writers can all publish to the same topic
let writer_a = participant
.topic::<SensorData>("sensors/temperature")?
.writer()
.qos(QoS::reliable().ownership_shared())
.build()?;
let writer_b = participant
.topic::<SensorData>("sensors/temperature")?
.writer()
.qos(QoS::reliable().ownership_shared())
.build()?;
// Reader receives data from BOTH writers
let reader = participant
.topic::<SensorData>("sensors/temperature")?
.reader()
.qos(QoS::reliable().ownership_shared())
.build()?;
Exclusive Ownership
use hdds::{Participant, QoS, TransportMode};
let participant = Participant::builder("failover_app")
.domain_id(0)
.with_transport(TransportMode::UdpMulticast)
.build()?;
// Primary writer (high strength)
let primary = participant
.topic::<ControlData>("control/commands")?
.writer()
.qos(QoS::reliable().ownership_exclusive().ownership_strength(200))
.build()?;
// Backup writer (low strength) - only publishes if primary fails
let backup = participant
.topic::<ControlData>("control/commands")?
.writer()
.qos(QoS::reliable().ownership_exclusive().ownership_strength(100))
.build()?;
// Reader only receives from the highest-strength active writer
let reader = participant
.topic::<ControlData>("control/commands")?
.reader()
.qos(QoS::reliable().ownership_exclusive())
.build()?;
#include <hdds.h>
/* Primary writer with EXCLUSIVE ownership, strength 200 */
struct HddsQoS* qos_primary = hdds_qos_reliable();
hdds_qos_set_ownership_exclusive(qos_primary, 200);
struct HddsDataWriter* primary = hdds_writer_create_with_qos(
participant, "control/commands", qos_primary);
hdds_qos_destroy(qos_primary);
/* Backup writer with strength 100 */
struct HddsQoS* qos_backup = hdds_qos_reliable();
hdds_qos_set_ownership_exclusive(qos_backup, 100);
struct HddsDataWriter* backup = hdds_writer_create_with_qos(
participant, "control/commands", qos_backup);
hdds_qos_destroy(qos_backup);
/* Reader with EXCLUSIVE ownership */
struct HddsQoS* qos_reader = hdds_qos_reliable();
hdds_qos_set_ownership_exclusive(qos_reader, 0);
struct HddsDataReader* reader = hdds_reader_create_with_qos(
participant, "control/commands", qos_reader);
hdds_qos_destroy(qos_reader);
Default Value
Default is SHARED with strength 0:
let qos = QoS::reliable();
// ownership = SHARED, ownership_strength = 0
Fluent Builder Methods
| Method | Description |
|---|---|
.ownership_shared() | Set shared ownership (default) |
.ownership_exclusive() | Set exclusive ownership |
.ownership_strength(value) | Set strength value (higher wins) |
.ownership_strength_high() | Set high strength (value: 100) |
.ownership_strength_low() | Set low strength (value: -100) |
How Exclusive Ownership Works
Writer A (strength=100) ----------> \
\
> Reader only sees Writer B
/
Writer B (strength=200) ----------> /
If Writer B crashes --> Reader sees Writer A (failover!)
Arbitration Rules
- The first writer to publish an instance becomes the owner
- A writer with higher or equal strength takes over ownership
- A writer with lower strength is rejected
- When the current owner is no longer alive, the next-highest-strength writer takes over
| Writer A Strength | Writer B Strength | Owner |
|---|---|---|
| 100 | 200 | Writer B |
| 200 | 100 | Writer A |
| 100 | 100 | First to publish |
Compatibility Rules
Writer and Reader ownership kinds must match exactly:
| Writer | Reader | Match? |
|---|---|---|
| SHARED | SHARED | Yes |
| EXCLUSIVE | EXCLUSIVE | Yes |
| SHARED | EXCLUSIVE | No |
| EXCLUSIVE | SHARED | No |
A SHARED writer cannot match an EXCLUSIVE reader, and vice versa. Both sides must agree on the ownership mode.
Use Cases
Primary/Backup Failover
use hdds::{Participant, QoS, TransportMode};
let participant = Participant::builder("failover_system")
.domain_id(0)
.with_transport(TransportMode::UdpMulticast)
.build()?;
// Primary node: strength 200
let primary = participant
.topic::<StateUpdate>("system/state")?
.writer()
.qos(QoS::reliable().ownership_exclusive().ownership_strength(200))
.build()?;
// Hot standby: strength 100 (takes over if primary fails)
let standby = participant
.topic::<StateUpdate>("system/state")?
.writer()
.qos(QoS::reliable().ownership_exclusive().ownership_strength(100))
.build()?;
Redundant Sensors
use hdds::{Participant, QoS, TransportMode};
let participant = Participant::builder("redundant_sensors")
.domain_id(0)
.with_transport(TransportMode::UdpMulticast)
.build()?;
// Primary sensor (higher quality, higher strength)
let primary_sensor = participant
.topic::<SensorData>("sensors/position")?
.writer()
.qos(QoS::reliable().ownership_exclusive().ownership_strength_high())
.build()?;
// Backup sensor (lower quality, lower strength)
let backup_sensor = participant
.topic::<SensorData>("sensors/position")?
.writer()
.qos(QoS::reliable().ownership_exclusive().ownership_strength_low())
.build()?;
Multi-Robot Coordination
use hdds::{Participant, QoS, TransportMode};
let participant = Participant::builder("robot_coordinator")
.domain_id(0)
.with_transport(TransportMode::UdpMulticast)
.build()?;
// Leader robot (highest priority for shared resource control)
let leader = participant
.topic::<ResourceControl>("shared/resource")?
.writer()
.qos(QoS::reliable().ownership_exclusive().ownership_strength(300))
.build()?;
// Follower robots (lower priority)
let follower = participant
.topic::<ResourceControl>("shared/resource")?
.writer()
.qos(QoS::reliable().ownership_exclusive().ownership_strength(100))
.build()?;
Interaction with Other Policies
Ownership + Liveliness
Exclusive ownership relies on liveliness to detect when the current owner has failed:
| Scenario | Behavior |
|---|---|
| Owner alive | Only owner's data delivered |
| Owner liveliness lost | Next-highest-strength writer takes over |
| Owner comes back (higher strength) | Regains ownership |
Combine Exclusive Ownership with Liveliness monitoring for robust failover. Without liveliness, the middleware may not detect owner failure promptly.
Ownership + Reliability
| Ownership | Reliability | Behavior |
|---|---|---|
| EXCLUSIVE | reliable() | Guaranteed delivery from the owner only |
| EXCLUSIVE | best_effort() | Best-effort from owner, backup may take over |
| SHARED | reliable() | Guaranteed delivery from all writers |
Ownership + Durability
With transient_local() and EXCLUSIVE ownership, late joiners receive only samples from the current owner, not from lower-strength writers.
Common Pitfalls
-
Mismatched kinds: SHARED writer + EXCLUSIVE reader (or vice versa) results in no match. Both must use the same kind.
-
Forgetting strength on EXCLUSIVE: With EXCLUSIVE ownership, always set
ownership_strength(). The default strength is 0, which may lead to unexpected arbitration with negative-strength writers. -
No liveliness monitoring: Without liveliness, failover may be slow because the middleware relies on other mechanisms to detect writer loss.
-
Strength ties: When two writers have equal strength, the first to publish wins. This can lead to non-deterministic behavior. Use distinct strength values.
-
Per-instance arbitration: Ownership is per data instance (key), not per topic. Different instances can have different owners.
Performance Notes
- Ownership checking adds minimal overhead (atomic integer comparison)
- EXCLUSIVE mode reduces reader-side traffic (only one writer's data delivered)
- Strength-based arbitration is O(1) per sample
- The OwnershipArbiter uses lock-free atomics for thread safety
Next Steps
- Liveliness - Writer health monitoring
- Partition - Logical data isolation
- Overview - All QoS policies