Repository navigation
feat(streaming): build a byte item in place - #157
Conversation
A producer that encodes an item into its own buffer pays a copy when send_bytes puts it into the frame. ByteItemBuilder hands out the frame's buffer with the item header already in it, so an encoder writes the payload straight into the frame. finish writes the length, and StreamSender<Bytes>::send_frame sends the frame with no copy, under the send_bytes rules for a cancel, a full channel and the item limit. The builder needs no sender. The frame decodes as the send_bytes frame does. Its length is always a bin32, so an item under 64 KiB is up to 3 bytes longer. Performance-sensitive: adds a send path beside send_bytes (per item). send and send_bytes do the same work; the bin length check of encode_bytes_item moved into a helper that finish shares. Signed-off-by: Ryan Olson <rolson@nvidia.com>
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Build byte-item frames lib/velo/src/streaming/frame.rs, lib/velo/src/streaming.rs |
Adds ByteItemBuilder and ByteItemFrame, shared payload-length validation, and public re-exports. Tests cover decoding, allocation, header validation, and size limits. |
Send completed frames lib/velo/src/streaming/sender.rs, .claude/skills/perf-review/references/hot-paths.md |
Adds send_frame, which uses the shared item-send path. Tests cover equivalence, cancellation, allocation, and item limits; an ignored benchmark compares sending methods. The producer list now includes send_frame. |
Exercise streaming paths lib/velo/tests/streaming/byte_items.rs, docs/src/concepts/streaming.md |
Adds round-trip and interoperability coverage for in-place frames. The guide describes frame construction, sending behavior, allocation and copy costs, and the bin32 length difference. |
Priority:
Estimated code review effort: 3 (Moderate) | ~25 minutes
Change: Feature
Sequence Diagram(s)
sequenceDiagram
participant Application
participant ByteItemBuilder
participant StreamSender
participant StreamReceiver
Application->>ByteItemBuilder: Write payload into frame buffer
ByteItemBuilder-->>Application: finish returns ByteItemFrame
Application->>StreamSender: send_frame(ByteItemFrame)
StreamSender->>StreamReceiver: Send encoded item through shared item-send path
StreamReceiver-->>Application: Decode byte item
Suggested reviewers: jthomson04
Merge Risk: 
Minimal · up to e892f
-
No merge-blocking risk is established by the available evidence.
Pre-merge checks: 
5
-
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 81.48% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 27 functions across 4 files. (2 skipped: 2 unsupported.)
-
Finishing Touches
-
📝 Generate docstrings
-
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
@coderabbitai full review |
✅ Action performed
|
Summary
A producer that encodes each item into its own buffer pays a copy when
send_bytesputs the payload into the frame. This PR lets the encoder write the payload straight into the frame.ByteItemBuilder::with_capacity(n)makes a frame buffer that already holds the item header.buffer()hands out theVec<u8>, so an encoder that writes to aVecneeds no velo type. The builder needs no sender.finish()writes the payload length, and refuses a frame whose header was changed (a cleared buffer could otherwise hold a terminal sentinel).StreamSender<Bytes>::send_frame(frame)sends it with no copy, under the rules ofsend_bytesfor a cancel, a full channel, and the item limit.bin32, so an item under 64 KiB is up to 3 bytes longer than asend_bytesframe.Additive.
mainis at 0.21.0 and crates.io at 0.20.0, so no version bump.Performance-sensitive (per item, producer). A new opt-in path beside
send_bytes.sendandsend_bytesdo the same work as before.Measured
bench_byte_item_send(ignored lib test) times both arms in one process: encode into an own buffer thensend_bytes, against build in place thensend_frame. Median of 9 rounds of 200,000 items, three runs. gb300 (Neoverse-V2, CPU 9, load 2.3):send_bytessend_frameThe three runs agree to 0.4 ns. The 16 B row was lost in the runner's log extraction; on a busier spark host it was 54.0 against 47.1 ns.
timeout 300 cargo test --release -p velo --lib bench_byte_item_send -- --ignored --nocapture --test-threads=1Tests
built_items_decode_as_encoded_items_do: everybinwidth, through the byte decode and serde; the payload stays in its buffer.building_an_item_that_fits_allocates_once,building_and_sending_an_item_allocates_once: one allocation. Both fail iffinishcopies.finish_refuses_a_changed_header,a_payload_over_the_bin_limit_is_refused.send_frame_sends_the_built_frame_and_follows_the_send_rules,send_frame_refuses_an_item_over_the_limit_as_send_bytes_does.byte_items_round_trip,byte_and_typed_ends_interoperate: built frames over the mux (ticket, attach) and on the same worker, throughnextandnext_bytes.cargo test -p velo --lib: 1151 passed. Clippy with--all-featuresand the book build pass.Review
One adversarial pass by a reviewer from another model family: no findings, with probes over every header byte, per-stream TCP and gRPC, and the 16 MiB limit.
perf-review: no regression onsendorsend_bytes.Summary by CodeRabbit
send_bytes.