Skip to content

grpc-js: support grpc-node.flow_control_window on the server#3058

Open
imperatormk wants to merge 1 commit into
grpc:masterfrom
imperatormk:server-flow-control-window
Open

grpc-js: support grpc-node.flow_control_window on the server#3058
imperatormk wants to merge 1 commit into
grpc:masterfrom
imperatormk:server-flow-control-window

Conversation

@imperatormk
Copy link
Copy Markdown

Problem

grpc-node.flow_control_window was added for the client transport in #2864, but the server never reads it. As a result a server's HTTP/2 receive window is always pinned to Node's default of 64 KB, with no channel option to raise it.

On high-RTT links this throttles large uploads. With a 64 KB window, every 64 KB of data requires a full round-trip for a WINDOW_UPDATE before more can be sent, so throughput is bounded by window / RTT. A ~19 MB unary upload to a server one or two hops away can take tens of seconds, and the data arrives as a long sequence of 16 KB frames with periodic multi-second stalls. The same option already fixes this on the client side; the server side had no equivalent.

grpc.max_send_message_length / grpc.max_receive_message_length do not help here, since they cap message size, not the flow-control window.

Change

Make the server honor grpc-node.flow_control_window, mirroring the existing client implementation in transport.ts:

  1. On server construction, set settings.initialWindowSize from the option. This advertises a larger per-stream window in the SETTINGS frame for new sessions.
  2. Per session, raise the connection-level window via session.setLocalWindowSize(...), with an incrementWindowSize fallback for older Node versions. This is guarded by flowControlWindow > defaultWindow so it is a no-op when the option is unset or not larger than the default.

Testing

Added a test in test/test-server.ts that sets grpc-node.flow_control_window on the server and round-trips a message larger than the default window. The full test-server suite passes.

I reproduced the original problem and verified the fix out of cluster using tc netem to inject RTT on loopback: a 19 MB unary call at ~20 ms RTT took ~15 s with the default window and ~3.4 s with a 4 MB window, with the remaining time being application work. The effect was identical across grpc-js 1.10, 1.12, and 1.14, confirming this is the default-window behavior rather than a regression.

Note for reviewers

setLocalWindowSize is present in current @types/node, but incrementWindowSize is not, so the fallback references it through a small local interface rather than any. The client code in transport.ts uses as any for the same call; happy to match that style if preferred.

The grpc-node.flow_control_window channel option was added for the client
transport in grpc#2864, but the server never read it, so a server's HTTP/2
receive window stayed at the Node default of 64 KB with no way to raise it.
On high-RTT links this throttles large uploads: each 64 KB requires one
round-trip for a WINDOW_UPDATE, so a 19 MB message can take tens of seconds.

This sets settings.initialWindowSize from the option on server construction
(per-stream window) and raises the connection-level window per session via
setLocalWindowSize, with an incrementWindowSize fallback for older Node.
This mirrors the existing client implementation in transport.ts.

Adds a test that round-trips a large message with the option set.
@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla Bot commented May 20, 2026

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: imperatormk / name: imperatormk (873a1ba)

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