Skip to content

fix(connect): the minors of #111 and #117's reviews - #118

Merged
antoinetoussaint-byte merged 2 commits into
mainfrom
fix/connect-minors
Oct 26, 2026
Merged

antoinetoussaint-byte merged 2 commits into
mainfrom
fix/connect-minors

Conversation

@antoinetoussaint-byte

@antoinetoussaint-byte antoinetoussaint-byte commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Fixes the minor findings from the reviews of #111 (r106) and #117 (r107). Both of those PRs have merged.

#111

  • m1: a service's refusal could still be misread when its bound equalled the face's. The face's channel receive bound is now one byte past both answer bounds.
    • The face already checks every answer against its own bounds before converting, so the channel only refuses answers past both.
    • When grpcio refuses at the channel, the number in its text is that bound plus one, which no service's client would have by chance.
    • So a refusal the service passes on from a client whose bound equals the face's (4 MiB here) keeps its own code and wording.
    • The reviewer tested the same fix.
  • m2/m3: the docstring understated two costs. Corrected.
    • A list of enums costs memory in proportion to the length of the enum names, since each one-byte value becomes its name in JSON. I measured process memory with protobuf 6.33.6: 130x the proto at 22-character names, 373x at 100, 687x at 200.
    • So the 4 MiB JSON default's worst case depends on the service's enum names: about 520 MiB at roughly 20 characters, 2.7 GiB at 200.
    • The docstring now gives the reviewer's measured process memory for a proto answer: 54–98 MiB for a 15 MiB answer, depending on protocol.

#117

  • m1: a server that waits for the client's SETTINGS could stall the face. The face now forwards the client's SETTINGS frame, which completes the client's preface, before it waits for the server's. The two prefaces cross instead of the face holding the client's back.
  • m2: the no-SETTINGS timeout closed silently. If the server sends no SETTINGS within read_timeout, the client now gets a GOAWAY (INTERNAL_ERROR) whose reason reads "the gRPC server sent no SETTINGS within N s".
  • m3: the face only read the first 256 bytes of SETTINGS. A SETTINGS frame is now scanned whole as it streams, holding one entry at a time, so the server's limit is found wherever it sits in the frame. A peer's first frame is also limited to 16 KiB, the most any peer may send before it has the other's SETTINGS.

Classification: fixes, in the face.

Tests

These fail on main:

  • test_a_refusal_naming_the_faces_own_bound_is_still_the_services
  • test_a_server_that_waits_for_the_clients_settings_is_not_stalled
  • test_a_server_that_sends_no_settings_gets_the_client_a_goaway
  • test_the_servers_limit_is_found_anywhere_in_its_settings: the limit sits past 360 bytes of other settings, and the frame is also fed one byte at a time.

The face suite passes (56 cases). go vet and the Connect Go tests pass, including TestConnectOnlyServiceSyncs, now that Buf no longer rate-limits this machine.

Review r121 (commit 4ecf543)

  • B1 (blocker): the face's GOAWAY reached the client before any SETTINGS, so gRPC clients discarded the reason. Fixed.
    • Cause: when the server sent no SETTINGS, the face's GOAWAY was its first frame to the client. RFC 9113 §3.4 requires a server's first frame to be SETTINGS, so grpcio threw it away ("Expected SETTINGS frame as the first frame").
    • Now: every refusal of an opening is an empty SETTINGS frame followed by the GOAWAY.
    • Proof with a real client: test_a_grpc_client_hears_why_the_server_never_answered runs a grpcio client in its own process. Its transport logs "Got goaway [2] … Debug Text: the gRPC server sent no SETTINGS within 0.5 s".
  • m1: a bound of 231 − 1 overflowed grpcio's channel argument. Fixed.** The channel's receive bound is clamped to 2**31 − 1, so a bound at gRPC's largest value starts again (overflow_probe.py).
  • m2: one reason covered every failure. Fixed. The reason now says what happened: no SETTINGS within N s, closed before its SETTINGS, a first frame of N bytes past the 16384 allowed, or a first frame that is not SETTINGS.
  • m3: the 16 KiB first-frame cap had no test. Fixed. The cap is tested in both directions. A client's oversized first frame gets SETTINGS + GOAWAY (FRAME_SIZE_ERROR).
  • m4: client SETTINGS were scanned for nothing. Fixed. Only the server's direction scans SETTINGS; the client's direction skips them, as on main. scan_bench.py: 14 GB/s, against 15 GB/s on main.
  • m5: the client's first frame was not checked. Fixed. A first frame that is not a SETTINGS frame on stream 0 without the ACK flag is refused, from either peer, with a GOAWAY (PROTOCOL_ERROR) that names it. A bad client first frame never reaches the server.
  • Checks:
    • The face suite passes (64 cases), and every new test fails on 43edffd.
    • go vet and the full go test ./... pass.

🤖 Generated with Claude Code

antoinetoussaint-byte and others added 2 commits October 25, 2026 22:26
#111 (review r106):
- m1: the channel's own receive bound is one byte past both answer bounds, so
  a refusal a service passes on from a client whose bound equals the face's
  is never taken for the face's own (grpcio names the client's bound).
- m2/m3: the docstring says a list of enums costs with the length of their
  names (measured 130x at 22 characters, 373x at 100, 687x at 200), so the
  JSON default's worst case depends on the service's enum names, and gives
  the measured process memory of a proto answer (54 to 98 MiB for 15 MiB).

#117 (review r107):
- m1: the client's SETTINGS, which completes its preface, goes to the server
  before the face waits for the server's, so a server that waits for the
  client's first is not stalled.
- m2: a server that sends no SETTINGS within read_timeout gets the client a
  GOAWAY (INTERNAL_ERROR) naming why.
- m3: a SETTINGS frame is scanned whole as it streams, holding one entry, so
  the server's limit is found wherever it is in the frame (not only in its
  first 256 bytes). A first frame is held to the 16 KiB any peer may send
  before it has the other's SETTINGS.

Tests (each fails on main): test_a_refusal_naming_the_faces_own_bound_is_still_the_services,
test_a_server_that_waits_for_the_clients_settings_is_not_stalled,
test_a_server_that_sends_no_settings_gets_the_client_a_goaway,
test_the_servers_limit_is_found_anywhere_in_its_settings (also fed one byte at a time).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mes what happened

Review r121 of #118:

- B1: the GOAWAY for a server that sent no SETTINGS was the face's first
  frame to the client; RFC 9113 3.4 makes a server's first frame SETTINGS,
  and grpcio discarded the reason ("Expected SETTINGS frame as the first
  frame"). Every refusal of an opening is now an empty SETTINGS, then the
  GOAWAY. A real grpcio client logs "Got goaway [2] ... Debug Text: the gRPC
  server sent no SETTINGS within 0.5 s".
- m1: the channel's receive bound is clamped to 2**31 - 1 (gRPC's channel
  arguments are C ints), so a bound at gRPC's largest starts again.
- m2: the reason says what happened: no SETTINGS within N s, closed before
  its SETTINGS, a first frame of N bytes past the 16384 allowed, or a first
  frame that is not SETTINGS.
- m3: the 16 KiB first-frame cap is tested in both directions; a client's
  oversized first frame gets SETTINGS + GOAWAY(FRAME_SIZE_ERROR).
- m4: only the server's direction scans SETTINGS; the client's skips them as
  before (scan_bench: 14 GB/s, main 15 GB/s).
- m5: a first frame that is not a SETTINGS frame on stream 0 without ACK is
  refused, from either peer, with GOAWAY(PROTOCOL_ERROR) naming it; the
  client's never reaches the server.

Tests (each fails on 43edffd): test_a_server_that_sends_no_settings_gets_the_client_a_goaway
(silent, closed, oversized, not-settings), test_a_grpc_client_hears_why_the_server_never_answered
(a grpcio client in its own process), test_a_client_that_sends_no_settings_is_told_why,
test_a_client_whose_first_frame_is_not_its_settings_is_refused (oversized,
headers, settings-ack), test_a_bound_at_grpcs_largest_starts, and the client
direction in test_the_servers_limit_is_found_anywhere_in_its_settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@antoinetoussaint-byte
antoinetoussaint-byte merged commit bfee337 into main Oct 26, 2026
4 checks passed
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