Releases: userFRM/photon-ring
v2.5.0 — 7 cons eliminated
Highlights
- Arbitrary capacity — any ring size >= 2 (Lemire fastmod, zero regression for pow2)
- photon-ring-async — runtime-agnostic async wrappers (no tokio)
- photon-ring-metrics — observability with snapshot/delta tracking
- Pipeline WaitStrategy —
then_with(f, WaitStrategy::YieldSpin) #[photon(as_enum)]— explicit enum marking, compile error for unknown types- Loom MPMC model — exhaustive concurrency testing (7 scenarios)
Install
photon-ring = "2.5.0"
photon-ring-async = "2.5.0" # optional
photon-ring-metrics = "2.5.0" # optionalBreaking
#[derive(Message)]enum fields now require#[photon(as_enum)]attribute
Full changelog: v2.4.0...v2.5.0
v2.4.0 — atomic-slots + multi-model audit
What's New
atomic-slots feature — formally sound seqlock
Replaces write_volatile/read_volatile with AtomicU64 stripes. Zero performance cost on x86-64 (identical MOV instructions). ~5-10ns ARM64 reader overhead. Miri-passable. First formally-sound seqlock implementation in the Rust ecosystem.
photon-ring = { version = "2.4.0", features = ["atomic-slots"] }Multi-model audit (17 findings fixed)
Full codebase audit by Gemini 2.5 Pro, Claude Opus 4.6, and Kimi k2.5:
- Critical: MPMC
AcqRelordering, Pod derive#[repr(C)]check, enum transmute docs - High:
Weakbackpressure trackers, PipelineDrop/panic propagation, derive signed-int/usize fixes - Medium:
prefault()assertion, MonitorWait docs, NUMA bounds check, Pipeline Lagged spin
Performance
- PREFETCHW on x86 publisher (-10.7% fanout 1 sub, -14.6% fanout 2 subs)
- WFE in
recv()Phase 2 on AArch64 (~12ns cache-line-event wakeup) - Dead
rdtscasm block removed (~20-25 cycles saved per UMWAIT/TPAUSE)
Benchmarks (default vs atomic-slots, x86-64)
| Benchmark | Default | atomic-slots |
|---|---|---|
| Publish only | 1.94 ns | 1.98 ns |
| Fanout 10 subs | 15.68 ns | 15.96 ns |
| Cross-thread | 96.2 ns | 95.2 ns |
| MPMC 1p 1s | 12.23 ns | 11.99 ns |
CI improvements
- MSRV 1.94 verification job
photon-ring-deriveauto-publishatomic-slotsfeature gate job- Test timeouts to prevent CI hangs
Research
3 research documents exploring seqlock alternatives via constraint-anchored analysis. All 3 independent agents converged on the same design. See docs/research-*.md.
Full Changelog: v2.3.0...v2.4.0
v2.3.0 — Message derive macro
Zero-friction Pod conversion with #[derive(Message)]:
#[repr(u8)]
#[derive(Clone, Copy)]
enum Side { Buy = 0, Sell = 1 }
#[derive(photon_ring::DeriveMessage)]
struct Order {
price: f64,
qty: u32,
side: Side, // enum → u8 automatically
filled: bool, // bool → u8 automatically
tag: Option<u32>, // Option → u64 automatically
}
// Generated: OrderWire (Pod) + From<Order> + From<OrderWire>
let wire: OrderWire = order.into();
publisher.publish(wire);
let order: Order = subscriber.recv().into();Also: channel.rs and topology.rs split into module directories (no API changes).
v2.2.0 — DependencyBarrier
Consumer dependency graphs for ordered multi-stage processing.
let mut upstream_a = subs.subscribe_tracked();
let mut upstream_b = subs.subscribe_tracked();
let barrier = DependencyBarrier::from_subscribers(&[&upstream_a, &upstream_b]);
let mut downstream = subs.subscribe_tracked();
// downstream won't read event N until both A and B have finished it
let value = downstream.recv_gated(&barrier);Zero cost on the default path — independent subscribers pay nothing.
10 new tests. 132 total.
v2.1.1
v2.1.0 — MPMC Deadlock Fix, UMWAIT Strategies, Prefetch
Highlights
Critical Bug Fix
- MPMC
catch_up_cursordeadlock — After ring wraparound, the shared cursor could strand permanently, causing all subscribers to spin forever. One-character fix:!=→<in the successor stamp check. Any MPMC channel publishing more messages than its capacity was affected. (#1)
New Wait Strategies
MonitorWait— UMONITOR/UMWAIT on Intel Alder Lake+ with runtime CPUID WAITPKG detection. Near-zero power, ~30 ns wakeup latency. Safe constructor:WaitStrategy::monitor_wait(&AtomicU64). Falls back to PAUSE on older x86, SEVL+WFE on aarch64. (#2)MonitorWaitFallback— TPAUSE timed C0.1 pause without address monitoring. Same platform fallbacks.
Performance
- Prefetch next slot on publish —
PREFETCHT0(x86) /PRFM PSTL1KEEP(ARM) hides the RFO stall. Applied to all SPMC and MPMC publish paths. (~15-25% write latency reduction) (#3) recv_with()direct slot access — Pre-computes slot pointer outside the spin loop, eliminating per-iterationtry_recv()overhead.- Cached
has_backpressureon Publisher — Avoids Arc deref on every lossy publish. - WFE in MPMC predecessor spin — Low-power wait on aarch64 instead of YIELD.
Correctness
write_volatile/read_volatilein seqlock — Eliminates formal UB when a reader observes a partially-written slot. Zero runtime cost. (#4)- Removed dead
countfield fromSubscriberGroup— Was always equal to const genericN.
Review
Three rounds of automated review with OpenAI Codex CLI. All findings addressed.
Upgrade
[dependencies]
photon-ring = "2.1.0"No breaking API changes. WaitStrategy has two new variants (MonitorWait, MonitorWaitFallback); existing match arms are unaffected if using _ wildcard.
Full Changelog: v2.0.0...v2.1.0
v2.0.0 — Pod Trait, Derive Macro, Safety Contract
Photon Ring v2.0.0
BREAKING CHANGE: T: Copy replaced with unsafe trait Pod across the entire API.
Why this matters
The seqlock read protocol performs optimistic non-atomic reads that may produce torn bit patterns. Previously, the API accepted any T: Copy, including types like bool, char, and NonZero<u32> where certain bit patterns are invalid — making torn reads technically undefined behavior.
Pod ("Plain Old Data") guarantees that every bit pattern is valid, eliminating this class of UB entirely. The trait is pre-implemented for all numeric primitives, arrays, and tuples up to 12 elements.
Migration
// Before (v1.x)
let (mut p, s) = channel::<u64>(1024);
// After (v2.0) — primitives just work (Pod is pre-implemented)
let (mut p, s) = channel::<u64>(1024);
// Custom structs need unsafe impl:
#[repr(C)]
#[derive(Clone, Copy)]
struct Quote { price: f64, volume: u32 }
unsafe impl photon_ring::Pod for Quote {}
// Or with the derive macro (optional feature):
// photon-ring = { version = "2", features = ["derive"] }
#[derive(Clone, Copy, photon_ring::DerivePod)]
#[repr(C)]
struct Quote { price: f64, volume: u32 }Other changes
try_publisher()onPhoton<T>andTypedBus(returnsOption, no panic)- Verification README: explicit limitations (SPMC-only, SC-only model)
- Benchmark methodology documentation
- 118 tests, all passing
v1.0.1
v1.0.0 — Topology Builder, O(1) Fanout, Payload Scaling
Photon Ring v1.0.0
The first major release. Addresses every LMAX Disruptor feature gap identified in independent review.
Highlights
Topology Builder
let (mut pub_, stages) = Pipeline::builder().capacity(1024).input::<RawTick>();
let (mut output, pipeline) = stages
.then(|tick| enrich(tick))
.then(|tick| generate_signal(tick))
.build();Dedicated thread per stage, graceful shutdown, fan-out support.
O(1) Subscriber Group Fanout
2.8 ns for 1, 10, or 1000 subscribers. Single cursor, compiler-unrolled.
Batch Processing
recv_batch(&mut self, buf)— fill a slice in one calldrain()— iterator over all available messagesShutdown— coordinated graceful termination
Payload Scaling
Benchmarked 8B to 4KB payloads. Photon Ring outperforms Disruptor at every size.
Performance
| Metric | Value |
|---|---|
| One-way latency (RDTSC p50) | 48 ns |
| Cross-thread roundtrip | 96 ns |
| Publish cost | 2.9 ns |
| SubscriberGroup (any N) | 2.8 ns |
Quality
- 117 tests (28 unit + 69 integration + 20 doc)
- MIRI verified (single-threaded)
- TLA+ formal specification
- 944-line academic technical report
- Codex CLI reviewed (2 rounds, all findings addressed)
- GitHub Actions CI (9 jobs, cross-platform)
Full changelog: https://github.com/userFRM/photon-ring/blob/master/CHANGELOG.md
