Skip to main content

Shared Memory Transport

Zero-copy inter-process communication for participants on the same host.

Overview

The SHM transport bypasses the network stack entirely, using memory-mapped segments and futex/WaitOnAddress synchronization for sub-microsecond latency.

Performance targets:

  • Writer push: < 200 ns
  • Reader poll: < 100 ns
  • End-to-end (with wake): < 1 us

Platform Support

PlatformSegment APISynchronizationStatus
Linuxshm_open / mmapfutex(FUTEX_WAIT/WAKE)Production
Windows 8+CreateFileMappingW / MapViewOfFileWaitOnAddress / WakeByAddressAllProduction
macOSN/AN/ANot supported (use UDP)

How It Works

Process A (Writer)              Shared Memory              Process B (Reader)
| +-------------------+ |
|--- push(data) ----->| Ring Buffer | |
| | [slot0][slot1]... |<--- poll() ----|
|--- futex_wake() --->| Control Block |--- wake ------>|
| +-------------------+ |
  1. Writer allocates a shared memory segment (/hdds_d{domain}_w{guid})
  2. Writer pushes data into a lock-free ring buffer using atomic operations
  3. Writer wakes readers via futex (Linux) or WakeByAddressAll (Windows)
  4. Reader discovers the segment via SEDP user_data (shm=1;host_id=...)
  5. Reader opens the segment and polls the ring buffer

Configuration

SHM Policy

use hdds::{Participant, TransportMode};
use hdds::transport::shm::ShmPolicy;

// Prefer SHM when available, fallback to UDP (default)
let p = Participant::builder("app")
.shm_policy(ShmPolicy::Prefer)
.build()?;

// Require SHM -- fail if not on same host
let p = Participant::builder("local_only")
.shm_policy(ShmPolicy::Require)
.build()?;

// Disable SHM -- always use UDP
let p = Participant::builder("udp_only")
.shm_policy(ShmPolicy::Disable)
.build()?;
PolicyDescription
PreferUse SHM when conditions are met, fallback to UDP (default)
RequireFail if SHM conditions are not met
DisableNever use SHM, always use network transport

Requirements for SHM

SHM is automatically selected when all conditions are met:

  1. Both participants are on the same host (matching host_id)
  2. Both endpoints use BestEffort QoS (SHM does not support retransmission)
  3. Remote participant advertises SHM capability in SEDP user_data
  4. SHM policy is Prefer or Require (not Disable)

When any condition is not met, HDDS falls back to UDP transparently.

C API

#include <hdds.h>

HddsParticipantConfig* cfg = hdds_config_create("my_app");
hdds_config_set_domain_id(cfg, 0);

// Set SHM policy
hdds_config_set_shm_policy(cfg, HDDS_SHM_PREFER);

// Combine with transport preference
hdds_config_set_transport_preference(cfg, HDDS_TRANSPORT_PREF_SHM_PREFERRED);

HddsParticipant* p = hdds_config_build(cfg);

C Enums

typedef enum {
HDDS_SHM_PREFER = 0, // Prefer SHM, fallback to UDP
HDDS_SHM_REQUIRE = 1, // Require SHM, fail if unavailable
HDDS_SHM_DISABLE = 2, // Disable SHM, always use network
} HddsShmPolicy;

Monitoring

SHM Metrics

use hdds::transport::shm_global_metrics;

let metrics = shm_global_metrics();
println!("SHM writes: {}", metrics.writes);
println!("SHM reads: {}", metrics.reads);
println!("Fallback to UDP: {}", metrics.fallback_udp);
println!("Overruns: {}", metrics.overruns);

hdds-shm-viewer

Inspect live SHM segments:

# Linux: scan /dev/shm for HDDS segments
hdds-shm-viewer

# Windows: inspect a specific segment
hdds-shm-viewer --segment hdds_d0_w0102030405060708090a0b0c0d0e0f10

# JSON output
hdds-shm-viewer --json

# Watch mode (refresh every second)
hdds-shm-viewer --watch

Architecture Details

Segment Naming

PlatformFormatExample
Linux/hdds_d{domain}_w{guid_hex}/hdds_d0_w0102030405060708090a0b0c0d0e0f10
WindowsLocal\hdds_d{domain}_w{guid_hex}Local\hdds_d0_w0102030405060708090a0b0c0d0e0f10

Ring Buffer Layout

+------------------+  offset 0
| ShmControl | 64 bytes (cache-line aligned)
| head: AtomicU64| -- writer's current position
| capacity: u32 | -- number of slots (power of 2)
| magic: u32 | -- 0xDD5F0001
+------------------+ offset 64
| ShmSlot[0] | 4160 bytes each
| seq: AtomicU64 | -- sequence number (odd = writing)
| len: AtomicU32 | -- payload length
| payload: [u8] | -- 4096 bytes
+------------------+
| ShmSlot[1] |
| ... |
| ShmSlot[N-1] |
+------------------+

Synchronization

  • Writes: Release ordering ensures payload is visible before sequence commit
  • Reads: Acquire ordering ensures sequence is read before payload access
  • ABA prevention: Sequence numbers are monotonically increasing, never reused
  • Torn read detection: Odd sequence = write in progress, reader retries

Troubleshooting

SHM Not Being Used

Check that both participants advertise SHM capability:

# Look for shm=1 in discovery dump
hdds-discovery-dump --domain 0 | grep shm

Common causes:

  • Different hosts (check host_id match)
  • One endpoint uses Reliable QoS
  • SHM policy set to Disable

Segment Cleanup (Linux)

Stale segments from crashed processes can accumulate in /dev/shm:

# List HDDS segments
ls /dev/shm/hdds_*

# Clean up stale segments for domain 0
hdds-shm-viewer --cleanup --domain 0

On Windows, the OS automatically cleans up segments when all handles are closed.

Limitations

LimitationDescription
Same host onlySHM requires participants on the same machine
BestEffort onlySHM does not support reliable QoS (no retransmission)
No macOSmacOS is not supported (use UDP)
4 KB payloadEach ring buffer slot holds up to 4096 bytes
Stale segmentsCrashed processes may leave segments in /dev/shm (Linux)

Next Steps