Skip to content

transport: use an intrusive linked list for per-stream data frame queues - #9490

Open
arjan-bal wants to merge 1 commit into
grpc:masterfrom
arjan-bal:one-buffer
Open

arjan-bal wants to merge 1 commit into
grpc:masterfrom
arjan-bal:one-buffer

Conversation

@arjan-bal

@arjan-bal arjan-bal commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

#9444 replaced the linked lists in the control buffer and in each stream's queue of data frames with ring buffers. This works well for the control buffer, which is long lived and shared by all streams. For per-stream queues, however, the ring buffer allocates a backing array on each stream's first enqueue, which costs one allocation per stream on both the client and the server. This buffer can also pin up to 1KB memory per stream.

This PR replaces the per-stream ring buffer with an intrusive singly linked list: dataFrame gets a next field and outStream keeps dataHead/dataTail pointers. A dataFrame is only ever queued on one stream, and the queue is only accessed by the loopy writer, so no extra synchronization is needed. Adding the field does not change the allocation size class of dataFrame (72 → 80 bytes).

Server trailers, which were previously queued behind the data frames as a *serverHeaders item, are now stored in a separate outStream.trailer field and written once the data queue is empty. As a result, the queue only holds *dataFrames and the type assertions in processData and updateStreamAfterWrite are removed.

The control buffer continues to use the ring buffer from #9444.

Benchmarks

benchmain with -bufconn -networkMode=Local -compression=off -benchtime=60s.

Unary, 100 concurrent calls, 0-byte payloads

Round QPS Avg latency p99 latency Bytes/op Allocs/op
1 +2.14% -2.07% -2.50% -1.66% 137.19 → 135.20
2 +1.57% -1.56% -1.58% -1.69% 137.19 → 135.19
3 -0.15% +0.14% -0.49% -1.64% 137.19 → 135.19
Mean +1.19% -1.16% -1.52% -1.66% (~158 B) -2.00

The 2 fewer allocations per RPC are the per-stream ring buffer arrays (one on the client, one on the server).

Streaming, 200 concurrent calls, 100-byte payloads

Round QPS Avg latency p99 latency Bytes/op Allocs/op
1 +1.32% -1.17% -0.99% 0.00% 0.00%
2 +1.72% -1.61% -1.04% 0.00% 0.00%
3 +1.24% -1.33% -0.69% 0.00% 0.00%
Mean +1.43% -1.37% -0.91% 0.00% 0.00%

Streams are long lived in this workload, so the ring buffer's per-stream allocation was already negligible.

RELEASE NOTES: N/A

@arjan-bal arjan-bal added this to the 1.85 Release milestone Oct 8, 2026
@arjan-bal arjan-bal added Type: Performance Performance improvements (CPU, network, memory, etc) Area: Transport Includes HTTP/2 client/server and HTTP server handler transports and advanced transport features. labels Oct 8, 2026
@arjan-bal

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request replaces the itemList queue in outStream with an intrusive singly linked list of dataFrames (dataHead and dataTail) to avoid allocating a separate queue per stream. It also introduces a trailer field to outStream to store server trailers, which are processed after all data frames are written. The loopyWriter methods have been updated to utilize these new fields, and a new unit test TestOutStream_DataQueue has been added to verify the correctness of the intrusive queue operations. There are no review comments, and we have no further feedback to provide.

@codecov

codecov Bot commented Oct 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.60%. Comparing base (39ebc82) to head (691e569).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9490      +/-   ##
==========================================
- Coverage   87.71%   87.60%   -0.12%     
==========================================
  Files         436      436              
  Lines       30822    30837      +15     
==========================================
- Hits        27036    27015      -21     
- Misses       3785     3822      +37     
+ Partials        1        0       -1     
Files with missing lines Coverage Δ
internal/transport/controlbuf.go 94.90% <100.00%> (-0.57%) ⬇️

... and 31 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Transport Includes HTTP/2 client/server and HTTP server handler transports and advanced transport features. Type: Performance Performance improvements (CPU, network, memory, etc)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants