Skip to content

THRIFT-6142: Enforce Ruby unframed HeaderTransport limits - #3706

Open
kpumuk wants to merge 1 commit into
apache:masterfrom
kpumuk:rb-header-unframed-limit
Open

THRIFT-6142: Enforce Ruby unframed HeaderTransport limits#3706
kpumuk wants to merge 1 commit into
apache:masterfrom
kpumuk:rb-header-unframed-limit

Conversation

@kpumuk

@kpumuk kpumuk commented Aug 5, 2026

Copy link
Copy Markdown
Member

Ruby HeaderTransport enforced max_frame_size for Header and framed clients but passed unframed Binary and Compact reads directly to the underlying transport without applying the configured limit.

This change counts bytes consumed by each unframed protocol message, including the initial protocol signature, and raises TransportException::SIZE_LIMIT before a read would exceed the configured maximum. Pure-Ruby and native protocol readers notify compatible transports at message boundaries so sequential messages receive independent budgets. Exact-limit messages and partial underlying reads remain supported.

Benchmarks

Ruby 4.0.6 on aarch64 Linux, seven warmed trials per revision.

The repository protocol benchmark was run with and without the native extension:

ruby test/rb/benchmarks/protocol_benchmark.rb \
  --small-runs 10000 \
  --scenarios hdr-bin-read-small,hdr-cmp-read-small \
  --json

THRIFT_BENCHMARK_SKIP_NATIVE=1 ruby \
  test/rb/benchmarks/protocol_benchmark.rb \
  --small-runs 10000 \
  --scenarios hdr-bin-read-small,hdr-cmp-read-small \
  --json
Scenario master median (range) this change median (range) Delta
Pure Header Binary 0.292063 s (0.289148–0.294775) 0.293295 s (0.287210–0.299563) +0.42%
Pure Header Compact 0.283662 s (0.279885–0.296288) 0.282497 s (0.277990–0.292476) -0.41%
Native-loaded Header Binary 0.241813 s (0.238484–0.245131) 0.242277 s (0.238585–0.254646) +0.19%
Native-loaded Header Compact 0.185282 s (0.184773–0.189132) 0.183141 s (0.182184–0.193690) -1.16%

A focused worst-case control decoded 100,000 sequential minimal unframed messages per trial through HeaderTransport, using 19-byte Binary and 11-byte Compact messages. Pure mode used ruby -Ilib -rthrift; native mode added -Iext and used BinaryProtocolAccelerated for the Binary reader.

Scenario master median (range) this change median (range) Delta
Pure unframed Binary 0.517964 s (0.510047–0.532928) 0.544925 s (0.535836–0.550106) +5.21%
Pure unframed Compact 0.482962 s (0.481741–0.491921) 0.522609 s (0.517260–0.526999) +8.21%
Native accelerated unframed Binary 0.462682 s (0.459623–0.478828) 0.488974 s (0.486336–0.506783) +5.68%
Native unframed Compact 0.413511 s (0.408578–0.423356) 0.452232 s (0.447174–0.465880) +9.36%

The focused control intentionally maximizes fixed per-message accounting overhead and uses an in-memory transport, so it does not include network or application work. The repository Header scenarios remain within trial noise.

  • Did you create an Apache Jira ticket? (Request account here, not required for trivial changes)
  • If a ticket exists: Does your pull request title follow the pattern "THRIFT-NNNN: describe my issue"?
  • Did you squash your changes to a single commit? (not required, but preferred)
  • Did you do your best to avoid breaking changes? If one was needed, did you label the Jira ticket with "Breaking-Change"?
  • If your change does not involve any code, include [skip ci] anywhere in the commit message to free up build resources.

Client: rb

Co-Authored-By: OpenAI Codex (GPT-5.6) <codex@openai.com>
Copilot AI lite review requested due to automatic review settings August 5, 2026 15:38
@mergeable mergeable Bot added the ruby Pull requests that update Ruby code label Aug 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

This PR fixes a gap in Ruby HeaderTransport where unframed Binary/Compact reads bypassed max_frame_size, adding byte accounting and size-limit enforcement for unframed messages (including protocol signatures) and resetting budgets at message boundaries.

Changes:

  • Track and enforce unframed message byte budgets in HeaderTransport, raising TransportException::SIZE_LIMIT before exceeding max_frame_size.
  • Reset unframed size budgets at message boundaries by notifying transports from pure-Ruby and native protocol read_message_begin.
  • Add specs covering exact-limit acceptance, over-limit rejection, signature handling, partial reads, and sequential message budgeting (incl. accelerated binary).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
lib/rb/spec/header_transport_spec.rb Adds tests validating unframed size-limit enforcement and per-message budgeting.
lib/rb/lib/thrift/transport/header_transport.rb Implements unframed byte accounting, limit checks, and message-boundary resets.
lib/rb/lib/thrift/protocol/compact_protocol.rb Notifies transports of new-message boundaries to reset unframed budgets.
lib/rb/lib/thrift/protocol/binary_protocol.rb Notifies transports of new-message boundaries to reset unframed budgets.
lib/rb/ext/thrift_native.c Interns reset_message_size method ID for native protocol boundary notifications.
lib/rb/ext/constants.h Exposes reset_message_size method ID for native extension usage.
lib/rb/ext/compact_protocol.c Calls reset_message_size at message start in native compact reader.
lib/rb/ext/binary_protocol_accelerated.c Calls reset_message_size at message start in accelerated binary reader.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +360 to +366
def read_unframed(size)
raise_unframed_size_limit if @unframed_bytes_read + size > @max_frame_size

data = @transport.read(size)
@unframed_bytes_read += data.bytesize
data
end
Comment on lines +214 to +218
expect { read_unframed_message(protocol) }.to raise_error(
Thrift::TransportException,
"Unframed message size exceeds maximum #{payload.bytesize - 1}"
) do |error|
expect(error.type).to eq(Thrift::TransportException::SIZE_LIMIT)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ruby Pull requests that update Ruby code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants