Skip to content

fix: gRPC framing correctness and robustness follow-up - #650

Merged
strawgate merged 2 commits into
masterfrom
fix/grpc-framing-followup
Apr 2, 2026
Merged

strawgate merged 2 commits into
masterfrom
fix/grpc-framing-followup

Conversation

@strawgate

Copy link
Copy Markdown
Owner

Follow-up to #628 (gRPC framing).

Changes

  • Compressed flag semantics: The flag was set from self.compression == Compression::Zstd, but it should reflect whether the payload is actually compressed. If the compressor wasn't initialized, the payload falls back to uncompressed and the flag must be 0x00. Fixed to Zstd && compressor.is_some().
  • Pre-allocate grpc_buf: Now initialized at 64 KiB to match encoder_buf / compress_buf and avoid reallocation on first use.
  • u32::try_from for payload length: Replaces silent as u32 truncation with a clear panic for pathological >4 GiB payloads.
  • New test grpc_send_batch_frames_payload: Covers the full encode_batch → write_grpc_frame path, validating the frame header against the actual encoded protobuf bytes.

Test plan

  • cargo test -p logfwd-output — 59 tests pass (includes new test)
  • cargo clippy -p logfwd-output -- -D warnings — clean
  • cargo fmt --check -p logfwd-output — clean

🤖 Generated with Claude Code

- Compressed flag now reflects actual payload state (Zstd configured AND
  compressor initialized), not just configured algorithm. If the compressor
  is absent the payload is uncompressed and flag must be 0x00.
- Pre-allocate grpc_buf at 64 KiB to match encoder_buf/compress_buf pattern
  and avoid reallocation on first use.
- Use u32::try_from for payload length to panic with a clear message instead
  of silently truncating for pathological >4 GiB payloads.
- Add grpc_send_batch_frames_payload test to cover the full encode→frame path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 2, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 98a2d53e-90ac-4750-b7e6-9b14c3334e8f

📥 Commits

Reviewing files that changed from the base of the PR and between a80ddad and 2c54d5b.

📒 Files selected for processing (1)
  • crates/logfwd-output/src/otlp_sink.rs

Walkthrough

Updated gRPC framing and buffer handling in crates/logfwd-output/src/otlp_sink.rs. OtlpSink::new now preallocates grpc_buf with 64 KiB. OutputSink::send_batch computes payload_is_compressed as (self.compression == Compression::Zstd) && self.compressor.is_some() and uses it when framing and when setting the Content-Encoding: zstd header. write_grpc_frame now checks payload length with u32::try_from(payload.len()).expect("gRPC message payload must be < 4 GiB") before writing the 4-byte big-endian length prefix. Added a unit test encode_and_frame_payload verifying the 5-byte gRPC header and exact protobuf payload in uncompressed mode.

Possibly related PRs

  • strawgate/memagent PR 628 — modifies OTLP gRPC framing and send_batch behavior in the same crates/logfwd-output/src/otlp_sink.rs file, including adjustments to the 5-byte length-prefixed frame logic and tests.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/logfwd-output/src/otlp_sink.rs`:
- Around line 824-865: Rename the test and update its doc comment to accurately
reflect that it validates the combination of OtlpSink::encode_batch and
write_grpc_frame rather than calling OtlpSink::send_batch; specifically update
the function name grpc_send_batch_frames_payload to something like
encode_batch_frames_payload or encode_and_frame_payload, and change the top
comment line to state that the test encodes via encode_batch and then frames
with write_grpc_frame (mention OtlpSink::encode_batch and write_grpc_frame in
the comment) so the test intent and coverage are precise and do not imply
send_batch is exercised.
- Around line 217-220: The code computes payload_is_compressed (based on
self.compression == Compression::Zstd && self.compressor.is_some()) but later
builds request headers using self.compression == Compression::Zstd, which can
falsely advertise compression when compressor is None; update header
construction to use payload_is_compressed (the same boolean passed to
write_grpc_frame) so the HTTP compression header matches the actual payload
compression state and ensure any branch that sets the compression-related header
checks payload_is_compressed (and not just self.compression) as well as avoid
setting compression headers when compressor is None.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 108408e6-381a-4bd8-940e-faafbdb12c4e

📥 Commits

Reviewing files that changed from the base of the PR and between bb81ee9 and a80ddad.

📒 Files selected for processing (1)
  • crates/logfwd-output/src/otlp_sink.rs

Comment thread crates/logfwd-output/src/otlp_sink.rs Resolved
Comment thread crates/logfwd-output/src/otlp_sink.rs Resolved
- Use payload_is_compressed for Content-Encoding header (matches actual payload
  state; previously advertised zstd when compressor was None but payload was plain)
- Rename grpc_send_batch_frames_payload -> encode_and_frame_payload and update
  its doc comment to accurately reflect it tests encode_batch + write_grpc_frame,
  not send_batch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant