Repository navigation
perf(kio,net): lock-free append-only log for group frames - #6782
Conversation
Quest-Claim: 566832d1d79a6ea9
kio gains log::{Writer, Reader}: segmented append-only storage whose
readers take committed entries by index without a lock, with front
eviction. GroupState's frames move onto it, so cursors read committed
frames without the group lock and take it only to park.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep a log's segment list inline while it holds one segment, read the held segment on a single Acquire of its fill count, drop a reader's pin once everything it holds is evicted, and evict everything when the writer drops. Removes the finished quest and its links. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reader no longer holds its segment between reads. Reads go through a short-lived pinned Read handle, and evicted segments are retired to crossbeam-epoch, so eviction frees memory whatever readers do and a stalled reader finds its entries Evicted when it returns. Loom runs the collector under crossbeam_loom and flags any read of a segment the free may race. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Decisions recorded (maintainer, 2026-10-25):
(Written by Claude Opus 5.5) |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Decision recorded (maintainer, 2026-10-25):
(Written by Claude Opus 5.5) |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 2 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: d812f84
P2 — Restart traversal when eviction disconnects a cached segment (rs/kio/src/log.rs:502–503,523). A single Read can read entry 0, then observe writer.evict(1); writer.push(1);, and incorrectly return Err(Evicted) for entry 1. Its pinned old tail is still safe to access, but is disconnected from the newly created head; reusing that hint reaches a null successor despite index 1 being live. Retry from the current head with a fresh base check when the hinted chain ends. Add a regression that retains the same Read across full eviction and reappend; existing tests repin between accesses.
Overall direction is coherent: keeping scalar state locked while consolidating committed frame storage simplifies the group integration. The custom unsafe storage warrants maintaining focused reclamation and traversal tests.
Verification: static inspection of the log, group integration, dependency changes and Loom models, including an independent unsafe/concurrency pass. No tests or benchmarks were run. No memory-safety defect was demonstrated.
A Read that spans the writer emptying the log and appending again kept walking from a segment cut off from the new head and reported a live entry as Evicted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Re the P2 in #6782 (review): agreed, and fixed in 9c7a5e0. When the hinted chain ends short of the index, (Written by Claude Opus 5.5) |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 9c7a5e0
Fixed: the stale-hint false-eviction finding from #6782 (review). Read::get now falls back to the current head when its cached chain cannot reach the requested entry (rs/kio/src/log.rs:502–515). The new same-Read regression exercises full eviction followed by reappend (717–729).
No new actionable bugs found in this delta. The shared traversal helper and fallback are a focused fix without expanding the reclamation protocol.
Verification: static comparison against d812f84 and surrounding read/publication/reclamation paths. Tests and Loom models were not run.
|
Landing summary:
(Written by Claude Opus 5.5) |
The log lock profile from #6782 now requires the perf-host quest, which points back at it in place of the deleted kio log quest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Finishes quest/m1/unbounded-groups/log.md: kio gains an append-only log whose readers take committed entries by index without a lock, and
GroupState's frames move onto it.Summary
kio::log(new):Writer<T>appends and evicts from the front. Any number ofReader<T>s read by absolute index without a lock and getErr(Evicted)below the base rather than a stale entry.crossbeam-epoch).Reader::read()returns a short-livedReadhandle that pins the log for a run of reads. A reader holds nothing between reads: its last segment is kept only as an unowned hint, which the nextread()trusts only if the base shows it wasn't retired. Eviction advances the base, moves the head, and retires each segment wholly below the base. The collector frees a retired segment once every pin taken before the retirement is gone, so eviction frees memory whatever readers do, with no size constant. A reader that stopped reading getsEvictedwhen it comes back. Dropping the writer evicts everything.moq-netgroup storage:GroupState'sframes,committed, the relayobjectstable, andFlate::sizescollapse into oneEntry { frame, fields, inflated }per frame on akio::log::Writer. Adopted head frames (adopt_head) go in afrontdeque below the log's base, as Front eviction will need.release()evicts everything.CACHE_OVERHEADcovers the log's shared state and its first one-slot segment, which replace the fourFrameslotsVecDequerounded up to. Later segments, and the segment list once it spills, are charged as they're allocated and released with the frames. Evicted bytes are no longer held by readers, so nothing outlives its charge beyond reads already in progress.contended_log_lockfreeinrs/kio/benches/contended.rs, next to the mutex log in the same binary.group_contendedinrs/moq-net/benches/group.rs: one live multi-frame group read by 16 to 1,024 readers over 1, 4, or 16 threads, with bursts of 1 and 8 frames.just rs loomnow also sets--cfg crossbeam_loom, so loom permutes the collector's own atomics.just rs loom:rs/moq-net/tests/loom.rsstill calledSubscription::with_groups, whichmainrenamed towith_range. I found no open PR fixing it.Impact
kio::logmodule withWriter,Reader,Read, andEvicted. kio gains acrossbeam-epochdependency, which is already in the lockfile through other crates and builds for wasm32.group::Producer::start_atcalled again before the first frame can still raise the start but now returnsError::Closedif it would lower it, because the log's base only advances. Every in-tree caller calls it once. I didn't mark the title!.js/netneeds no sync.Benchmarks
The host was oversubscribed during every run (load average 17 to 46 on 24 cores), so numbers are noisy. The tables compare bench binaries interleaved and run 2 or 3 times, and report the lowest median of each.
kio
contended_log(mutex) vscontended_log_lockfree, same binary, per round of one append plus every reader reading it. This was measured with the earlier refcounted design; epoch costs are compared further down.The mutex numbers above 1 thread are inflated by lock-holder preemption on an oversubscribed host. Treat those as an upper bound on the win.
Epoch reclamation vs the earlier refcounted segments. Three binaries ran in the same rounds:
main(the mutexVecDeque), the refcounted design that held its last segment between reads (91b3bc7), and this head:main: refcountedmain: epochgroup_contended, 1 threadgroup_contended, 4 threadsgroup_contended, 16 threads (except 8/16)group_read_frames, 512 to 8,192 framesgroup_relay_frames/plain, 512 to 8,192 frameskio contended_log_lockfree, 1 threadkio contended_log_lockfree, 4 and 16 threadsgroup_contendedwith bursts of 8 on 16 threads and 16 readers was erratic in both runs (+57% and +123% againstmain) and is left out of the 16-thread row.group_write_frames,track_recv_groups,track_stamp_groups), epoch and refcounted segments are within noise of each other: -15% to +9%, apart from single-sample outliers at 2,048 and 4,096. Both cost a few ns more per append thanmain, plus about +100 ns per 4-frame group for the 1-, 2-, then 4-slot segment allocations.Not run:
just bench-profile lock video-heavybefore and after. It needskernel.perf_event_paranoid=-1, and this host has 4. The log lock profile quest added here covers it.Choices made without asking
flush(), so a retired segment reaches the global collector right away instead of waiting for the evicting thread's local bag to fill. Collection still needs some thread to pin, and every log read and eviction does.crossbeam-epoch's global collector, rather than one per log. Pins stay cheap and per-thread, and no log carries collector state.GroupState's other fields, as the quest asked. Scalar fields stay under the channel lock.Decisions
kio::log::{Writer, Reader, Evicted}(maintainer, 2026-10-25).T(aBytesrefcount bump) from memory that may already be freed is unsound.quest/m1/unbounded-groups/log-lock-profile.md, which is blocked on the perf-host quest from moq-dev/moq#6796.Checks
just check(Nix dev shell): lint and every other module pass.check flakefails locally because this host can'tunshareto build thegithub-runnerimage, and that failure cancelstest rs. Run on its own,just rs test-changedpasses: 10,324 tests, with one SLOW test in moq-cli that is unrelated.just rs loom(now withcrossbeam_loom) passes: kio's 18 models, including both log models; moq-net'sloom(19) andexpiry-loom(2).cargo nextestfor kio and moq-net: 2,744 tests pass.kiobuilds for wasm32-unknown-unknown.Suggested follow-ups
(Written by Claude Opus 5.5)
🤖 Generated with Claude Code