Dual-Stack IPC Architecture
October 1, 2025 · View on GitHub
QAULTRA-CPP now supports two independent IPC implementations:
- IceOryx (v1) - Eclipse Foundation's C++ IPC middleware
- iceoryx2 (v2) - Rust-based rewrite with C++/Rust interoperability
Why Dual-Stack?
IceOryx (v1) Advantages
- Mature and stable - Production-tested in automotive industry
- Pure C++ - Native C++ API with no FFI overhead
- RouDi daemon - Centralized service discovery and management
- Performance - Proven zero-copy shared memory IPC
iceoryx2 (v2) Advantages
- Cross-language - Seamless C++/Rust interoperability
- Modern architecture - Distributed service discovery, no daemon required
- Memory safety - Rust implementation reduces bugs
- Future-proof - Active development, modern design patterns
Architecture
qaultra::ipc
├── v1 (IceOryx)
│ ├── DataBroadcaster
│ ├── DataSubscriber
│ └── BroadcastManager
└── v2 (iceoryx2)
├── DataBroadcaster
├── DataSubscriber
└── BroadcastManager
API Compatibility
Both versions provide identical high-level APIs:
// V1 - IceOryx
#include "qaultra/ipc/broadcast_hub_v1.hpp"
using namespace qaultra::ipc::v1;
// V2 - iceoryx2
#include "qaultra/ipc/broadcast_hub_v2.hpp"
using namespace qaultra::ipc::v2;
// Both share the same interface
DataBroadcaster::initialize_runtime("myapp"); // V1 only
auto broadcaster = DataBroadcaster(config, "stream");
broadcaster.broadcast(data, size, count, type);
Usage Examples
V1 - IceOryx (C++ to C++)
#include "qaultra/ipc/broadcast_hub_v1.hpp"
using namespace qaultra::ipc::v1;
// Start RouDi daemon first:
// $ /home/quantaxis/iceoryx/build/install/bin/iox-roudi
int main() {
DataBroadcaster::initialize_runtime("publisher");
BroadcastConfig config;
config.service_name = "MarketData";
DataBroadcaster broadcaster(config, "ticks");
uint8_t data[1024];
broadcaster.broadcast(data, 1024, 100, MarketDataType::Tick);
}
V2 - iceoryx2 (C++ to C++ or C++ to Rust)
#include "qaultra/ipc/broadcast_hub_v2.hpp"
using namespace qaultra::ipc::v2;
// No daemon required!
int main() {
BroadcastConfig config;
config.service_name = "MarketData";
DataBroadcaster broadcaster(config, "ticks");
uint8_t data[1024];
broadcaster.broadcast(data, 1024, 100, MarketDataType::Tick);
}
V2 - Cross-Language (C++ Publisher + Rust Subscriber)
C++ Publisher:
#include "qaultra/ipc/broadcast_hub_v2.hpp"
using namespace qaultra::ipc::v2;
DataBroadcaster broadcaster(config, "market_data");
broadcaster.broadcast(data, size, count, MarketDataType::Tick);
Rust Subscriber:
use iceoryx2::prelude::*;
let node = NodeBuilder::new().create::<ipc::Service>().unwrap();
let service = node.service_builder(&"market_data".try_into().unwrap())
.publish_subscribe::<ZeroCopyMarketBlock>()
.open_or_create()
.unwrap();
let subscriber = service.subscriber_builder().create().unwrap();
while let Some(sample) = subscriber.receive().unwrap() {
println!("Received: {:?}", sample.payload());
}
Build Configuration
Both versions can be enabled/disabled independently:
# CMakeLists.txt
option(QAULTRA_USE_ICEORYX "Use IceOryx for zero-copy IPC" ON)
option(QAULTRA_USE_ICEORYX2 "Use iceoryx2 for zero-copy IPC" ON)
Build status:
-- IceOryx Available: TRUE
-- iceoryx2 Available: TRUE
Performance Comparison
IceOryx V1 (C++ to C++)
- Throughput: 63,291 msg/sec
- Latency: 7.97 μs average
- Overhead: Minimal (pure C++)
iceoryx2 V2 (C++ to C++)
- Throughput: TBD (expected similar)
- Latency: TBD (expected slightly higher due to FFI)
- Overhead: Small FFI layer to Rust core
iceoryx2 V2 (C++ to Rust)
- Throughput: TBD (zero-copy across languages)
- Latency: TBD (true zero-copy, no serialization)
- Overhead: None (shared memory direct access)
Dependencies
IceOryx V1
/home/quantaxis/iceoryx/build/install/
├── lib/libiceoryx_posh.a
├── lib/libiceoryx_hoofs.a
└── bin/iox-roudi (daemon)
iceoryx2 V2
/home/quantaxis/iceoryx2/build/
├── iceoryx2-cxx/libiceoryx2_cxx.a (C++ bindings)
└── rust/native/release/libiceoryx2_ffi_c.a (Rust FFI layer)
Runtime Requirements
V1 - IceOryx
Required: RouDi daemon must be running
/home/quantaxis/iceoryx/build/install/bin/iox-roudi &
V2 - iceoryx2
No daemon required - fully distributed architecture
When to Use Which?
Use IceOryx V1 when:
- ✅ Pure C++ application
- ✅ Mature, production-tested solution needed
- ✅ Automotive-grade reliability required
- ✅ Centralized management preferred
Use iceoryx2 V2 when:
- ✅ Cross-language communication needed (C++/Rust)
- ✅ No daemon overhead acceptable
- ✅ Modern distributed architecture preferred
- ✅ Future integration with QARS Rust code
Migration Path
Applications can:
- Start with V1 for immediate stability
- Migrate to V2 when cross-language features needed
- Run both during transition period (different namespaces)
Limitations
V1 Limitations
- ❌ No cross-language support (C++ only)
- ❌ Requires RouDi daemon
- ❌ Single-point-of-failure (daemon)
V2 Limitations
- ⚠️ Newer, less battle-tested
- ⚠️ Small FFI overhead for C++ bindings
- ⚠️ Requires Rust toolchain for building
Future Work
- Performance benchmarks V1 vs V2
- Cross-language integration tests
- Python bindings via V2
- Unified abstraction layer (auto-select V1/V2)
Summary
The dual-stack architecture provides flexibility and future-proofing:
- Use V1 for immediate C++ needs
- Use V2 for C++/Rust interoperability
- Both share common data structures (
ZeroCopyMarketBlock) - Seamless migration path as requirements evolve