tango
A lock-free, high-performance SPSC (single-producer single-consumer) channel inspired by Firedancer's Tango IPC subsystem.
Performance
Benchmarked on Apple M3 Pro with 1000 samples per benchmark:
SPSC Throughput (10K messages, 64-byte payload)
| Channel | Throughput | Relative |
|---|---|---|
| tango | 26.6 M msg/s | 1.0x |
| ringbuf | 10.7 M msg/s | 2.5x slower |
| std unbounded | 9.1 M msg/s | 2.9x slower |
| std bounded | 9.0 M msg/s | 3.0x slower |
| crossbeam bounded | 7.3 M msg/s | 3.6x slower |
| crossbeam unbounded | 6.8 M msg/s | 3.9x slower |
Ping-Pong Latency (100 round trips, 8-byte payload)
| Channel | Latency | Relative |
|---|---|---|
| tango | 90 µs | 1.0x |
| ringbuf | 100 µs | 1.1x slower |
| crossbeam | 105 µs | 1.2x slower |
| std | 380 µs | 4.2x slower |
Large Payload Throughput (2K messages, 1024-byte payload)
| Channel | Throughput | Relative |
|---|---|---|
| tango | 12.4 GiB/s | 1.0x |
| ringbuf | 9.4 GiB/s | 1.3x slower |
| crossbeam | 6.9 GiB/s | 1.8x slower |
Run benchmarks yourself:
RUST_MIN_STACK=67108864
Features
- Zero-copy reads - Access message payloads directly without allocation
- Lock-free - No mutexes, just atomic operations with careful memory ordering
- Backpressure - Optional credit-based flow control prevents overruns
- Overrun detection - Consumers detect when they've been lapped by producers
- Metrics - Built-in observability for throughput, lag, and errors
no_stdsupport - Works in embedded/kernel environments- Thoroughly tested - Unit tests, Miri, Loom, proptest, and fuzz testing
Quick Start
use ;
// Create the channel components
let mcache = new; // 64-slot metadata ring buffer
let dcache = new; // 64 chunks of 256 bytes each
let fseq = new; // Sequence counter starting at 1
let producer = new;
let mut consumer = new;
// Publish a message
producer.publish.unwrap;
// Consume it (zero-copy)
if let Ok = consumer.poll
With Flow Control
Use Fctl to prevent the producer from overwriting unconsumed messages:
use ;
let mcache = new;
let dcache = new;
let fseq = new;
let fctl = new; // 64 credits = buffer capacity
let producer = with_flow_control;
let mut consumer = with_flow_control;
// Producer returns NoCredits error when buffer is full
// Consumer automatically releases credits after consuming
With Metrics
Track throughput, lag, and errors:
use ;
let mcache = new;
let dcache = new;
let fseq = new;
let metrics = new;
let producer = new.with_metrics;
let mut consumer = new.with_metrics;
// ... publish and consume ...
let snapshot = metrics.snapshot;
println!;
println!;
println!;
Using the Builder
For ergonomic channel creation:
use ;
let = new
.with_flow_control
.with_metrics
.build;
let producer = with_flow_control
.with_metrics;
let mut consumer = with_flow_control
.with_metrics;
Installation
Add to your Cargo.toml:
[]
= "0.1"
Feature Flags
| Feature | Default | Description |
|---|---|---|
std |
Yes | Enable standard library support (Vec, Error trait) |
loom |
No | Enable Loom testing for concurrency verification |
For no_std environments:
[]
= { = "0.1", = false }
Architecture
Tango uses a split metadata/data architecture for cache efficiency:
- MCache: Ring buffer of 32-byte metadata entries with sequence-based validation
- DCache: Fixed-size chunk storage for payloads (cache-line aligned)
- Fseq: Atomic sequence counter shared between producer threads
- Fctl: Credit counter for backpressure between producer and consumer
The lock-free protocol uses Release/Acquire ordering:
- Producer writes payload to DCache
- Producer writes metadata to MCache slot
- Producer stores sequence number with
Releaseordering - Consumer loads sequence with
Acquire, reads metadata, re-checks sequence
This double-read validation detects overwrites without locks.
Safety & Testing
Tango is extensively tested:
- Unit tests: Core functionality
- Miri: Undefined behavior detection
- Loom: Exhaustive thread interleaving exploration
- Proptest: Property-based testing for invariants
- Fuzz testing: Edge case discovery with cargo-fuzz
All unsafe blocks are documented with // SAFETY: comments explaining the invariants.
When to Use Tango
Best for:
- Single-producer single-consumer scenarios
- Latency-sensitive applications (trading, gaming, audio)
- High-throughput message passing
- When you can dedicate a core to busy-polling
Consider alternatives if:
- You need multiple producers or consumers (use crossbeam)
- You can't busy-poll (use async channels)
- Messages are very large (consider shared memory + handles)
Minimum Supported Rust Version
Rust 1.85 or later (edition 2024).
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.