ringfire 0.5.2

Zero-copy lock-free inter-process communication (IPC) ring buffer and shared memory bus in Rust, with ring mirroring across hosts
Documentation
# Changelog

All notable changes to `ringfire` will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.5.2] - 2026-09-26

### Fixed
- Receive benches (`throughput`, `arena_vs_fixed`) time only successful,
  sequence-checked receives. Messages are refilled outside the timer. Before, they timed
  empty polls once the backlog drained, or pushes whenever the reader caught up. The
  in-place view bench no longer reuses a drained consumer. `recv_batch(32)` counts 32
  messages per iteration.
- Benches use unique ring paths on `/dev/shm` (or `RINGFIRE_BENCH_DIR`) and remove them.
  They validate replies; watchdogs and smoke-run process deadlines bound stalls.
- Replication examples no longer hang when a record is lost: phases end on the last
  sequence or an idle timeout. Losses, duplicates and reordering are counted by distinct
  sequence, pacing no longer drifts, mirror errors set the exit status, and
  `replication_stages` stamps pushes without a lock and checks clock drift.

### Added
- CI requires at least 95% production Rust line coverage, including CLI/FFI and
  optional Tokio/replication code. Test source is excluded; no production module is.
  JSON/HTML coverage reports and benchmark smoke logs are uploaded as artifacts.
  Measured Linux AArch64 production line coverage: 96.67% (4,961 / 5,132).
- Behavioral coverage of malformed layouts, cursor/checkpoint/registry behavior,
  async wake-up/fairness, FFI errors and data transfer, CLI commands, and TCP/UDP
  protocol recovery. Multicast prerequisites and real connection errors fail tests.
- Multiprocess stress verifies all eight child readers actually execute the test,
  receive 100,000 records each and validate the full payload with zero lapping.
- `scripts/bench-measure.sh` records three Criterion runs, raw samples and host metadata.
  Corrected ARM results are published in `docs/measurements/2026-09-27-arm/`.
- Separate IPC measurements for default adaptive futex waiting (32 spin attempts)
  and zero-spin futex waiting, with full reply validation on every transport.
- CLI `top --iterations N` and TCP `serve --once` support finite, observable runs;
  SIGINT/SIGTERM retain their normal behavior.
- `docs/benchmarking.md`: measurement definitions, clock domains, the audit, the status of
  every published figure, and how to reproduce a measurement.
- `scripts/bench-smoke.sh` and `tests/harness_support_tests.rs` check the harnesses.
- `--exit-on-close` for the pongers of `replication_pingpong` and `replication_stress`.
  `--warmup` and `--timeout-ms` for the pingpong pinger, `--idle-ms` for
  `replication_latency` and `replication_stress`, and `--drain-secs` for the
  `replication_stages` master.

### Changed
- README: invalid historical receive numbers are withdrawn; historical network results
  are labelled separately from measurements made with the corrected harnesses.

## [0.5.1] - 2026-09-26

Documentation release; library behavior and wire protocols are unchanged from v0.5.0.

### Added
- Seven SVG figures covering the mirror pipeline, LAN multicast topology, WAN site hubs,
  protocol exchanges, per-stage latency, WAN latency percentiles, and frame pacing.
- A dependency-free generator, `docs/gen_replication_figures.py`, to reproduce the figures.

### Changed
- README and network mirror guide use the figures alongside measured results and recipes.
- The published crate now includes the illustrated documentation added after v0.5.0.

### Fixed
- Correct the GAP direction and multicast fan-out labels in the diagrams.
- Match charts to the documented measurements: per-mirror median ranges, the tested
  pacing settings, and WAN precision. Remove an unsupported unpaced 100,000/s point.
- Write generated SVGs explicitly as UTF-8.

## [0.5.0] - 2026-09-26

Network mirrors: a ring on one host, identical copies with the same sequence numbers on
others, over TCP, UDP multicast or UDP unicast. See [docs/replication.md](docs/replication.md).

### Added
- **Network mirrors** (`ringfire::replication`): `ReplicaServer` streams a ring to other
  hosts over TCP; `Mirror` keeps a ring with the same geometry and the same sequence
  numbers in the local `/dev/shm`, so readers attach to it as they would on the source
  host. Raw slot payloads, own binary protocol with a 16-byte frame header
  (`HELLO` / `GEOMETRY` / `DATA` / `GAP` / `HEARTBEAT`), batching, resume from the last
  local sequence, restart detection, busy-poll mode. CLI: `ringfire serve <ring> --bind
  <addr>` and `ringfire mirror <source> <ring>`.
- **UDP multicast delivery** (`ReplicaServer::multicast`, `MulticastConfig`, CLI
  `--multicast GROUP:PORT --iface ADDR --mtu N --ttl N`): each `DATA` frame goes out once
  as a datagram to every mirror; the TCP connection carries `NAK` retransmission from the
  source ring and a `MULTICAST` handshake frame. Mirrors hold back datagrams that overtake
  a hole, so rings are still written in order; a 1 ms multicast heartbeat exposes a lost
  last datagram; a per-source session byte drops datagrams from an earlier incarnation.
- Frame linger (`ReplicaServer::linger`, CLI `--linger-us`): fill a frame for a bounded
  time before sending it; adaptive by default (frames leave at most once per 50 µs unless
  full; a record arriving later than that after the previous frame goes out at once).
  Without it every record above ~20,000/s costs its own datagram and system call, and
  latency jumps from ~50 µs to over a millisecond on a kernel stack.
- `examples/replication_stages.rs`: per-stage latency on every host at once (push on the
  master → read on the master, on same-host mirrors, on each remote mirror), with a
  PTP-style clock offset so remote consumers report in the master's clock.
- `examples/replication_stress.rs`: open-loop two-host stress with delivery ratio,
  round-trip percentiles and per-mirror NAK/retransmission/gap counters. It found and this
  release fixes: multicast heartbeats announcing a sequence whose datagram had not been
  sent yet (spurious NAKs under load), and `Mirror::step` draining datagrams without bound
  so a caller interleaving its own work never got control back.
- **UDP unicast delivery** (`ReplicaServer::unicast`, `ReplicaServer::duplicate`,
  `MirrorBuilder::unicast`, CLI `serve --udp PORT --dup N`, `mirror --unicast`): for
  routes without multicast (between sites, into clouds). Mirrors announce the capability
  in `HELLO`, learn the source's UDP port and a token from the `MULTICAST` frame (group
  `0.0.0.0`), and `PUNCH` to it, so delivery works from behind NAT; the source sends to
  the punched address and, with `--dup`, several times. Mirrors can be served again, so
  one copy per site crosses the WAN.
- **Blob rings are mirrored too** (`BlobProducer` / `BlobConsumer`): the `GEOMETRY` frame
  carries the arena, `DATA` records carry the descriptor followed by the blob bytes, the
  mirror writes blobs into its own arena and rewrites the descriptor's location. A blob
  overwritten at the source before it could be copied becomes a `GAP`. Oversized blobs
  (beyond the MTU, or beyond what the host lets a datagram be) still arrive, over TCP.
- `FLAG_SPARSE` (0x0400): rings whose sequence numbers may have holes. `RingConsumer` and
  `BlobConsumer` skip to the next message present instead of waiting on a hole (checked
  every 64 empty polls, only on sparse rings; other rings are unchanged).
- `RingfireError::Unsupported` and `RingfireError::Protocol`.
- `examples/replication_latency.rs`: one-way latency and burst throughput over loopback.

## [0.4.0] - 2026-09-22

Correctness release. The v0.3.0 review found torn reads, hangs, file truncation and
layout bugs across the Rust core, FFI, C header, Python bindings and CLI; each fix below
has a regression test in `tests/regression_tests.rs`.

### Breaking
- **Wire protocol v2 (`RINGFIRE_VERSION = 2`)**: v1 and v2 readers/writers refuse each other.
  - Writers mark a slot `SLOT_WRITING` (`u64::MAX`) before overwriting its payload.
  - `RingHeader` gains `slots_offset` (taken from padding; header is still 128 bytes).
    Rust, FFI, C and Python readers locate slots through it instead of guessing.
- `LayoutSignature` hashes the type name without module paths, so the same struct defined
  in two crates matches. Signatures differ from v0.3.0 (irrelevant across the protocol bump).
- `MpmcQueueConsumer` delivery is documented and implemented as at-most-once: overrun
  items are dropped and counted (`dropped_count()`) instead of hanging the consumer.
- `ReaderRegistry::reader_lag` / `headroom` (and the `RingProducer`/`BlobProducer`
  wrappers) take the last published sequence and count unread messages consistently;
  `BlobProducer::reader_lag` was off by one.
- Attaching a `RingConsumer` to a `LosslessBackpressure` ring whose registry is full now
  fails with `NoAvailableReaderSlots` instead of silently leaving the reader unprotected.
- `ringfire_blackboard_read` (FFI) returns `-2` and `BlackboardConsumer::read` returns
  `RingfireError::WriterStalled` when a slot stays mid-write for over 100 ms.

### Fixed
- **Torn reads** (all readers): a reader one slot short of being lapped accepted payloads
  the writer was overwriting (1875 torn out of 16.9M reads in 3 s on a 7950X). Fixed by
  the v2 slot marker plus acquire/release fences for AArch64.
- **`BlobConsumer` hang**: `recv`/`view` spun forever once the arena wrapped before the
  descriptor ring did. Lapped payloads are now skipped and counted; descriptors are
  bounds-checked before the arena is touched.
- **Live ring truncation**: a second `create` on a live path truncated the file before
  failing the `flock`, so the running producer and readers hit `SIGBUS`. All producers
  (ring, blob, MPMC, blackboard, FFI, Python) now lock first; a stale ring from a dead
  producer is replaced by a fresh inode so orphaned readers stay consistent.
- **`push_batch` ignored `LosslessBackpressure`.**
- **Lossless producer cost**: every `push` scanned the registry and checked each
  reader's liveness via `/proc` (about 830 ns per push+recv). The producer now caches the slowest
  cursor and rescans only when approaching it or every half ring; liveness checks run only
  while blocked. Cost is on par with lossy mode.
- **MPMC**: queue consumers hung on overwritten tickets; concurrent producers one lap apart
  could interleave payloads in a slot. Slots are now taken over by CAS in lap order; a slot
  that stays mid-write for over 10 ms is never taken over (its writer may just be
  descheduled): the waiting producer drops its message instead of tearing the payload.
- **Reader registry**: reclaiming a dead reader could wipe a registration that had just
  replaced it (non-CAS store); `update_cursor` used `Relaxed`, letting the producer
  overwrite a slot on AArch64 before the reader finished copying it.
- **Blackboard**: writer/reader fences for AArch64; readers no longer spin forever on a
  slot left odd by a crashed writer.
- **Attach validation**: truncated or foreign files are rejected (`CorruptLayout`) instead
  of being read out of bounds; the header magic is published last.
- **Python**: `BlobConsumer` read the arena mask from the `reserved` counter (wrong
  payloads for most offsets) and never checked for arena lapping; added
  `try_recv_copy`. Producers take an exclusive `flock`.
- **C header / FFI consumer** ignored the reader registry offset and read registry bytes
  as slots on lossless and blob rings. The C header struct is updated to the v2 layout.
- **CLI**: `dump` printed the header as slots on rings without a registry; the retained
  window was off by one; JSON output did not escape strings; `prune` used non-CAS stores.
- **`AsyncRingConsumer` as `Stream`** spawned a Tokio task per pending poll and busy-looped
  while idle; it now yields a few times and then waits on a timer.
- **`FutexWait` lost wake-ups**: a consumer going to sleep could miss a message published at
  the same instant and sleep for the full timeout (50 ms by default; about 1 in 1000 round
  trips in a ping-pong, averaging 53 µs per round trip). Fixed with an asymmetric barrier:
  producers register with `membarrier(REGISTER_GLOBAL_EXPEDITED)` and a consumer issues
  `membarrier(GLOBAL_EXPEDITED)` before sleeping, so the producer hot path stays barrier-free.
  Futex ping-pong round trip: 2.3 µs (Unix socket: 4.8 µs). Sleeps stay bounded as a fallback
  (`timeout: None` = 10 ms) for producers that cannot register.
- `SPMC` producers wake all sleeping readers (broadcast), not just one.
- `CycleStamp` on AArch64 reads `cntvct_el0` (it was a +1 counter); added
  `CycleStamp::counter_frequency_hz()`.
- `ConsumerStartMode::Sequence(0)` no longer reports a phantom lapped message.

### Added
- `tests/regression_tests.rs` (19 tests), `examples/quickstart.rs` (README snippets, run in CI).
- `benches/ipc_compare.rs`: 64-byte round trip over ringfire (spin and futex), Unix socket,
  pipe and TCP loopback, measured with one harness; results at the top of the README.
- `rust-version = "1.88"` (edition 2024, let-chains).
- GitHub Actions CI: Linux x86-64 and macOS AArch64, clippy with and without `tokio`.

## [0.3.0] - 2026-09-22

### Added
- **Lossless Backpressure Flow Control (`FlowControl::LosslessBackpressure`)**:
  - Optional lossless flow control policy configured via `RingProducerBuilder::flow_control(FlowControl::LosslessBackpressure)`.
  - Producer coordinates with `ReaderRegistry` to guarantee the slowest active reader is never overwritten.
  - Blocking `push()` automatically throttles via spin/yield backoff when headroom is exhausted; non-blocking `try_push()` returns `Err(RingfireError::BackpressureBufferFull)`.
  - Verified safe RAII drop order ensuring shared memory remains mapped while registry slots are deregistered.
- **Channel Multiplexing (`RingMultiplexer` & `AsyncRingMultiplexer`)**:
  - `RingMultiplexer`: Multi-channel ingestion engine supporting fair Round-Robin polling (`try_recv_any`), strict Priority scheduling (`try_recv_priority`), and batch draining (`recv_batch_any`) across multiple distinct ring buffers.
  - `AsyncRingMultiplexer`: Cooperative Tokio async multiplexer (`recv_any().await`) with zero task starvation.
- **Python Variable-Length Payload Support (`BlobConsumer`)**:
  - Implemented `BlobConsumer` in `python/ringfire` returning `(metadata, memoryview)` directly into the mapped `PayloadArena` with zero memory copies.
- **CLI Monitoring & Diagnostics Tool (`ringfire`)**:
  - Zero-dependency diagnostics binary:
    - `stat`: Inspect buffer headers, sequence state, arena allocation, and reader lag (human-readable or `--json`).
    - `top`: Real-time terminal dashboard with instantaneous message rate (`msg/s`), throughput (`MB/s`), and consumer lag.
    - `dump`: Inspect recent slots and hex/ASCII payload snippets.
    - `prune`: Clean up and reclaim abandoned reader slots from terminated processes.

## [0.2.0] - 2026-09-21

### Added
- **Consumer Start Modes (`ConsumerStartMode`)**:
  - `Latest`: Instant jump to the newest published sequence without reading historical backlog.
  - `Head` / `Oldest`: Start reading from the oldest available non-overwritten sequence.
  - `Sequence(u64)`: Position consumer at an exact target sequence number.
  - Dynamic repositioning via `RingConsumer::seek(ConsumerStartMode)`.
- **Lock-Free SHM Offset Checkpointing (`OffsetCheckpoint`)**:
  - Cache-line aligned (64 bytes) persistent offset storage in `/dev/shm`.
  - Sub-10ns atomic commits (`commit_offset(seq)`) with Acquire/Release synchronization.
  - Automatic consumer crash recovery and resumption upon restart.
- **Fluent Consumer Builder (`RingConsumerBuilder`)**:
  - Convenient construction API: `start_from_latest()`, `start_from_head()`, `start_from_sequence(seq)`, `offset_shm(path)`.
- **Python Integration**:
  - Updated `ringfire.py` with `ConsumerStartMode` support and atomic offset checkpointing.

## [0.1.0] - 2026-09-21

### Added
- **Core SPMC Ring Buffer**: Lock-free single-producer multi-consumer ring buffer in `/dev/shm` with 128-byte cache-line aligned `RingHeader`, atomic Acquire/Release synchronization, and zero heap allocations.
- **MPMC Ring Buffer**: Multi-producer support with atomic ticket reservation (`claim_seq`) and competing worker-queue consumers (`MpmcQueueConsumer`).
- **Variable-Length Payload Arena (`PayloadArena`, `BlobProducer`, `BlobConsumer`)**:
  - Out-of-band circular payload arena for large, variable-sized binary messages.
  - Zero heap allocation during push/pull.
- **Reader Registry & Heartbeat (`ReaderRegistry`)**:
  - Liveness monitoring, consumer registration, and dead-reader detection via cycle stamps.
- **ABI & Layout Validation (`LayoutSignature`, `CycleStamp`)**:
  - Compile-time and runtime validation signatures in SHM header preventing ABI drift between producers and consumers.
- **LatestWins Overflow Policy**: Non-blocking writer with zero stalls; lagged readers detect lapping (`RecvStatus::Lapped`), track dropped metrics, and jump cleanly to active surviving stream frames.
- **Tear-Read Memory Safety**: Two-phase seqlock validation ensuring zero torn reads during concurrent writer wraps.
- **Wait Strategies**:
  - `BusySpin`: sub-20ns memory spin loop.
  - `YieldBackoff`: adaptive OS thread yielding.
  - `Futex`: Linux kernel sleep/wake with 0% CPU idle usage and atomic `waiting_consumers` guard (0 syscalls during active streaming).
- **Tokio Async Integration (`AsyncRingConsumer`)**:
  - `recv().await` and `recv_batch(&mut [T]).await`.
  - Adaptive spinning $\to$ cooperative `tokio::task::yield_now().await` $\to$ async sleep, eliminating worker thread starvation.
  - Implementation of `futures_core::Stream`.
- **Shared State Blackboard**:
  - $O(1)$ state snapshot table (`BlackboardProducer`, `BlackboardConsumer`) in `/dev/shm`.
  - Per-slot 64-bit seqlock with 64-byte cache line alignment.
  - Benchmarked at **4.09 ns** read and **1.19 ns** write on AMD Ryzen 9 7950X.
- **Polyglot Bindings**:
  - Pure C11 header [`include/ringfire.h`]include/ringfire.h with standalone inline functions and C-ABI export.
  - Zero-dependency Python package [`python/ringfire`]python/ringfire using `mmap` and `ctypes.Structure`.
- **Hardware Benchmarks**: Comprehensive Criterion suite verified on AMD Ryzen 9 7950X (`booster`), achieving 588M msg/s push throughput and 83M msg/s recv throughput.