nexus-logbuf 2.2.0

Lock-free SPSC and MPSC byte ring buffers for logging and archival
Documentation
# Caveats

## 1. `mem::forget` deadlocks the buffer

Covered in detail in [claim-api.md](./claim-api.md). **Do not
leak `WriteClaim` or `ReadClaim` via `mem::forget`, `ManuallyDrop`,
or any other mechanism that skips the destructor.**

Consequences:

- Forgotten `WriteClaim`: the slot is reserved but never
  committed (len stays zero). The consumer blocks at this slot
  forever. All future producers eventually fill the buffer and
  return `Full` permanently.
- Forgotten `ReadClaim`: the consumer never advances `head`.
  Subsequent reads see the same record. The producer fills the
  buffer and returns `Full`.

There is no recovery. This is not undefined behavior, but it's
an unrecoverable logical deadlock.

**Normal drop handles everything correctly**, including drops
triggered by panic unwinding. The only way to hit this is to
actively call `mem::forget` or use unsafe tricks — which is
always a smell, and here it's a bug.

## 2. Capacity is in bytes, not records

`spsc::new(64 * 1024)` gives you a 64 KiB buffer, not a
64-record buffer. Size for peak burst in bytes:

- Measure your worst observed second of traffic in bytes.
- Multiply by at least 2x (consumer lag budget).
- Round up to the next power of two.

For WebSocket feeds, 1-4 MiB is typical. For in-process
telemetry, 256 KiB is usually plenty.

## 3. Zero-length payloads panic

`try_claim(0)` panics. `len == 0` is the "not yet committed"
sentinel in the record header; allowing zero-length records
would silently hang the consumer waiting for the producer to
commit. The check is structural, not ergonomic — passing zero
is a contract violation, not a runtime error you handle.

If you need a "marker" record, write at least one byte. Aborting
a non-zero claim (drop the `WriteClaim` without committing) is
fully supported and writes a skip marker the consumer handles
correctly.

## 4. Skip markers waste space near buffer wrap

When a claim would cross the end of the buffer, the producer
writes a skip marker for the remaining space and advances to
the beginning. This is transparent to the caller but means:

- A 4 KiB record near the wrap boundary of a 16 KiB buffer can
  produce up to 4 KiB of skip padding.
- Worst case: ~50% space efficiency for records that are half
  the buffer size.

For typical configurations (records << buffer size), skip
overhead is well under 1%. If you see significant skip overhead,
your buffer is sized wrong (too small) — bump it up.

## 5. Record alignment is 8 bytes

Each record's start offset is aligned to 8 bytes so the `len`
field is word-aligned for atomic access. This means:

- A 7-byte payload plus 8-byte header takes 16 bytes on the wire.
- A 13-byte payload plus 8-byte header takes 24 bytes.

Most workloads don't care about this. If you're packing tiny
records (<8 bytes each), you'll see ~50% overhead and should
probably use a typed [`nexus-queue`](../../nexus-queue/)
instead.

## 6. MPSC: one slow producer stalls the consumer

MPSC commits can arrive out of `tail` order. The consumer reads
in `head` order, so if producer A claims first and commits last,
the consumer waits on A's slot even if B has already committed.

**Never hold a `WriteClaim` across:**
- yield points
- blocking I/O
- long computations
- lock acquisitions

Claim, write, commit, all within a few microseconds. If you
can't, use SPSC per producer and merge.

## 7. No priorities, no ordering guarantees beyond FIFO

logbuf is strictly FIFO by `tail` order. There is no priority
queue, no out-of-band channel, no "urgent" marker. If you need
priority, run two logbufs (urgent + normal) and poll urgent
first.

## 8. Consumer drop does not disconnect producer (raw queue)

In the raw `queue::spsc` / `queue::mpsc` API, there is no
built-in disconnect tracking. If you need producer-side
disconnect detection (`ChannelClosed`), use the
[`channel`](./channels.md) wrapper. The raw API assumes both
sides live for the duration of the program, which is the common
case for archival and journaling loops.

## 9. The consumer zeros. That's O(n) in record size.

The consumer pays `O(len)` work to zero each record before
advancing. For tiny records (~64 bytes) this is negligible; for
large records (>64 KiB) it can dominate consumer cost. If your
records are huge and you trust the next producer to fully
overwrite, you might want a different primitive — talk to me
about it first.

## 10. Don't share a `Consumer` across threads

`Consumer` is `Send` but not `Sync`. Only one thread at a time
may consume. If you need fan-out consumption, either:

- Duplicate the producer side and feed two logbufs.
- Have the single consumer forward records via
  [`nexus-queue::spmc`]../../nexus-queue/ to a worker pool.