ringfire ๐ฅ
ringfire is an ultra-low-latency, zero-copy, lock-free Inter-Process Communication (IPC) ring buffer and shared memory state bus for Linux.
It is engineered for high-frequency trading (HFT) engines, real-time market data ingestion, telemetry buses, and performance-critical distributed pipelines where microsecond socket latencies and kernel overhead are unacceptable.
๐ก The Problem: Why Traditional IPC Fails Under High Load
When communicating between processes on the same host, developers usually default to Unix Domain Sockets (UDS), TCP loopback, pipes, ZeroMQ, or broker-based message queues (NATS, Redis). In high-throughput, low-latency environments, these primitives introduce severe architectural bottlenecks:
| IPC Mechanism | Kernel Overhead | Memory Copies | Typical Latency | Backpressure / Crash Behavior |
|---|---|---|---|---|
| Unix Domain Sockets (UDS) | 2 syscalls (send/recv) + context switch |
User $\to$ Kernel $\to$ User (2 copies) | 2,000 โ 15,000 ns (2โ15 ยตs) | Socket buffer fills up; blocks producer or drops packets |
TCP Loopback (127.0.0.1) |
Full TCP/IP stack + packetization | Multiple copies + TCP buffers | 5,000 โ 25,000 ns (5โ25 ยตs) | Heavy CPU jitter, flow control stalls |
| Pipes / FIFOs | Pipe inode lock + syscalls | Buffer copy through VFS | 1,500 โ 8,000 ns (1.5โ8 ยตs) | Blocking write when pipe buffer (64 KB) fills |
| Message Brokers (Redis / NATS) | Network stack + daemon context switch | Multi-hop serialization | 50,000 โ 500,000 ns (50โ500 ยตs) | High GC/memory pressure, single point of failure |
ringfire (Shared Memory) |
0 syscalls on hot path | 0 copies (in-place memory access) | < 30 nanoseconds | Writer is unblockable; crash-isolated |
The Three Critical Pain Points:
- The Syscall & Context Switch Tax: Every
write()andread()triggers CPU privilege elevation from user-space to kernel-space and back, polluting CPU L1/L2 caches and branch predictors. - Buffer Bloat & Head-of-Line Blocking: If a consumer process stalls (e.g. garbage collection pause in Python, disk IO hiccup, or debug pause), standard socket buffers fill up immediately, stalling the critical producer or blowing up memory.
- Serialization Overhead: Marshalling data to and from JSON, Protobuf, or even compact binary encoders consumes valuable CPU cycles and allocates memory on the hot path.
๐ฏ The Solution & Vision: What ringfire Solves
ringfire moves the communication fabric directly into physical RAM via POSIX shared memory (/dev/shm):
- Pure Shared Memory (
/dev/shm): Producer and consumers map the exact same physical memory region directly into their virtual address spaces. - Atomic Acquire/Release Synchronization: State is coordinated via 64-bit atomic sequence numbers using CPU-level memory barriers (
core::sync::atomic), completely bypassing the operating system kernel. - Single-Writer Freedom (
LatestWinsPolicy): The producer always writes to the ring. Slow, paused, or dead consumers can never block, stall, or crash the producer. If a consumer falls behind the ring buffer capacity, it detects that it was lapped and skips cleanly to the live stream. - Cache-Line Isolated Layout: Memory structures are aligned to 128-byte cache lines to eliminate false sharing between producer write heads and consumer read heads.
- O(1) State Blackboard: Besides sequential stream events,
ringfireprovides a direct seqlock-synchronized slot table. Consumers can instantly inspect the latest state (e.g., current Best Bid & Offer for 500 coins) in ~5 nanoseconds without replaying historical events. - Polyglot First-Class Support: Because the layout in
/dev/shmis standard C-ABI memory, consumers can be written in Rust, C, C++, or Python (mmap+ctypes/numpy) with zero bridge penalty.
๐ค The Rapidfire Family
ringfire is designed as the cross-process counterpart to rapidfire:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ IN-PROCESS (Single Process) โ
โ โ
โ rapidfire โ
โ โข Intra-process MPMC / MPSC channels across threads & Tokio โ
โ โข Ultra-low latency (< 15 ns), zero heap allocations โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CROSS-PROCESS (Multi-Process IPC) โ
โ โ
โ ringfire โ
โ โข Inter-process lock-free ring buffer via /dev/shm โ
โ โข O(1) Shared State Blackboard (Seqlock) โ
โ โข Sub-50ns latency, C11 header, Python zero-copy bindings โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ Verified Benchmarks (AMD Ryzen 9 7950X on Linux booster)
Benchmarked using Criterion directly against POSIX shared memory (/dev/shm) on host booster (16 Cores / 32 Threads, Linux 6.8):
| Metric | Measured Value | Rate / Target |
|---|---|---|
| SPMC Single-Message Push | 1.70 ns | 588.70 Million msgs / sec |
SPMC Non-Blocking try_recv |
11.94 ns | 83.74 Million msgs / sec |
SPMC Batch Drain (recv_batch(32)) |
262 ns (8.1 ns / msg) | 121.9 Million msgs / sec |
| Roundtrip Latency (Ping-Pong RTT) | 245.80 ns | ~61 ns one-way cross-thread IPC |
| Blackboard Seqlock Read (O(1)) | 4.09 ns | Direct sub-5ns snapshot read |
| Blackboard Seqlock Write (O(1)) | 1.19 ns | Instant atomic update |
| Multi-Process Saturation | 1 writer + 8 readers | 100% verified zero gaps / zero corruption |
๐ฆ Quickstart (Rust)
1. Producer: Publish Fixed-Size Binary Records
use RingProducer;
2. Tokio Async Consumer: Cooperative Non-Blocking Streaming
In async bots, busy loops starve the Tokio runtime. AsyncRingConsumer solves this with adaptive fast-path spinning, cooperative tokio::task::yield_now(), and 0% CPU idle sleep:
use AsyncRingConsumer;
async
3. Synchronous Low-Latency Consumer (Dedicated Cores)
use ;
let mut consumer = attach?;
let mut wait = new;
loop
4. Shared State Blackboard (O(1) Snapshot Table)
use ;
// Producer updates snapshot table
let mut bb_prod = create?;
bb_prod.write?; // 1.19 ns write
// Consumer reads instantaneous current state by asset ID
let bb_cons = attach?;
if let Some = bb_cons.read?
5. Raw Binary Payloads ([u8; N]) & Pointer Casting
You can also operate over raw byte buffers without declaring fixed structs:
use ;
// Producer sends a raw 64-byte binary packet
let mut producer = create?;
let raw_bytes = ;
producer.push;
// Consumer reads the 64 bytes and casts to struct in-place
let mut consumer = attach?;
let bytes: = consumer.recv.await;
let ticker: &MarketTicker = unsafe ;
๐ก๏ธ Memory Safety: Why Raw Pointers into Shared Memory Are Dangerous
In cross-process shared memory with a non-blocking writer (LatestWins), returning a raw pointer (*const T) directly into the mapped /dev/shm buffer is fundamentally unsafe:
- If a consumer holds a raw pointer to slot $K$, and the writer laps the buffer and begins overwriting slot $K$ on another CPU core, the consumer will observe a torn read (half old data, half new data).
- In high-frequency trading and order book streaming, a torn read corrupts prices and sizes, leading to disastrous trading errors.
The ringfire Solution: Two-Phase Seqlock Validation
ringfire enforces tear-free memory safety without locks:
- Consumer loads slot sequence $s_1$ with
Ordering::Acquire. - Copies the payload into the consumer's local registers/stack (takes 1 CPU clock cycle for 32โ64B via
vmovups). - Loads slot sequence $s_2$ with
Ordering::Acquire. - If $s_1 == s_2$, the copy is mathematically guaranteed to be consistent, uncorrupted, and tear-free.
- If $s_1 \neq s_2$ (writer updated the slot mid-read), the consumer immediately discards the partial copy and re-evaluates the latest sequence.
[!WARNING] Anti-Pattern: Returning Raw Pointers into Shared Memory (
*const T) Some naive IPC designs attempt to return a direct pointer or slice&[u8]into/dev/shmto claim "zero-memcpy". In a multi-process architecture with a non-blocking writer (LatestWins), this is a dangerous anti-pattern: the writer can overwrite that memory slot at any microsecond while the reader is parsing it, causing undefined behavior, silent data races, and torn reads.
ringfiredeliberately copies the slot payload into the reader's stack/register space inside a seqlock validation boundary (s1 == s2). For modern x86_64/ARM64 architectures, copying 32โ64 bytes takes ~1 CPU clock cycle (viavmovups) and is orders of magnitude faster than recovering from corrupted state or dealing with UB.
โก Wait Strategies
ringfire supports selectable wait strategies depending on CPU budget:
WaitStrategy::BusySpin: Sub-30ns reaction time. Spins tightly on CPU (core::hint::spin_loop()). Recommended for dedicated HFT cores.WaitStrategy::YieldBackoff: Spins for $K$ iterations then callsstd::thread::yield_now(). Balanced CPU usage with ~150ns reaction time.WaitStrategy::Futex: Sleeps on Linuxfutexwhen the queue is idle. Consumes 0% CPU when waiting, wakes up via atomic event signaling.
๐ Polyglot Access (C / C++ / Python)
- C11 Header: Include
include/ringfire.hin any C/C++ project without linking overhead. - Python (
ringfire-py): Read shared memory slots directly via Pythonmmapandctypes.Structurewith no serialization penalty.
๐ฅ Author
Alexander Panasenko
- Email: alex@prod.codes
- GitHub: @alex09x
๐ License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT License (LICENSE-MIT)
at your option.