Fix Netty HTTP span lifecycle for chunked/streaming responses#10734
Fix Netty HTTP span lifecycle for chunked/streaming responses#10734gtukmachev wants to merge 2 commits into
Conversation
|
|
c06a783 to
bca88d1
Compare
New JUnit 5 test class NettyChunkedResponseTest with a real Netty server that writes chunked responses manually (HttpResponse + HttpContent* + LastHttpContent), exercising the code path that HttpObjectAggregator-based tests never reach. Four test cases: - chunkedResponseSpanIncludesFullStreamDuration: span covers full stream time (~1s for 5 chunks x 200ms), not just header-send time (~0ms) - fullResponseStillFinishesSpanImmediately: FullHttpResponse regression - keepAliveSequentialChunkedRequestsGetCorrectSpans: STREAMING_CONTEXT_KEY lifecycle across back-to-back keep-alive requests - connectionDropDuringChunkedResponseFinishesSpan: span finished with error when client disconnects mid-stream
HttpServerResponseTracingHandler: route by message type (FullHttpResponse, HttpResponse, LastHttpContent) instead of finishing every span on HttpResponse. FullHttpResponse finishes immediately; HttpResponse defers to LastHttpContent via STREAMING_CONTEXT_KEY to avoid keep-alive race. WebSocket upgrades and bodyless responses (204, 205, 304) finish immediately since they never produce LastHttpContent. HttpServerRequestTracingHandler: channelInactive now checks STREAMING_CONTEXT_KEY and finishes leaked spans when channel closes mid-stream. AttributeKeys: added STREAMING_CONTEXT_KEY for chunked response context.
bca88d1 to
77cc414
Compare
|
Hi @ygree, this PR has been updated with all the changes since your initial review on #10656 (now closed): Tests added — new JUnit 5 test class Span leak fix — Additional fix — WebSocket upgrades and bodyless responses (204, 205, 304) finish the span immediately instead of deferring to Could you please also add the required labels? As an external contributor I can't assign them:
Ready for review. Thanks! |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 77cc414ea5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| int statusCode = response.status().code(); | ||
| boolean isBodyless = statusCode == 204 || statusCode == 205 || statusCode == 304; | ||
|
|
||
| if (isWebsocketUpgrade || isBodyless) { |
There was a problem hiding this comment.
Handle HEAD responses without waiting for chunks
When a Netty handler answers a HEAD request by writing only a DefaultHttpResponse/headers, no LastHttpContent is necessarily written because the response has no body. This branch only treats 204/205/304 as bodyless, so a 200 HEAD response is moved to STREAMING_CONTEXT_KEY and CONTEXT_ATTRIBUTE_KEY is removed; on a keep-alive connection the span stays open until the channel closes and can be reported late/as an error instead of finishing with the response headers. Consider using the stored request method (or another marker) so HEAD/header-only responses finish immediately.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not an issue — Netty's HttpObjectEncoder.encode() always writes LastHttpContent.EMPTY_LAST_CONTENT after a non-full HttpResponse, including HEAD responses. The handleLastHttpContent path will fire and finish the span correctly. The truly bodyless status codes (204, 205, 304) are already handled with immediate finish.
|
Hey DataDog team! Hope you're doing great! Could we please resume communication on this PR? This fix is very important for my company. We are transitioning more of our backend endpoints to a streaming approach and are currently forced either to lose DD APM tracking or maintain our own custom version of the DD Java agent. Sorry for pinging you directly — I found you as the latest committer today 😄 Would you please recommend the correct way to request a review for this PR, or point me to the right person to contact regarding it? |
@gtukmachev Thank you for your contribution, I will ask the team to take a look. |
|
thanks for the PR, I asked @ygree if he could take a second look since he had the context from the previous PR. |
|
The PR fixes streamed response duration for common keep-alive / read-ahead timing after headers have already been written, but it does not make Netty HTTP/1.1 tracing fully pipeline-correct. A proper solution would need per-request/per-response correlation, likely a FIFO queue on the channel for HTTP/1.1 response order. |
|
@ygree, thanks for coming back to the PR. Regarding your last comment:
What should the next step be to resolve this issue?
If it's the latter, @ygree, could you please provide more specific guidance on the expected implementation? That would help ensure we're aligned on the direction before I continue. P/S - I'm fine with any of the options. |
Thanks for your patience! I opened a pull request (#11937) that addresses the underlying pipelining issue by introducing a dequeue of contexts. The current request context being responded to is at the front of the queue, and the last request context is at the back of the queue. This can be used as a basis for supporting chunked transfer encoding tracing. I hope this helps unblock your change. Please let us know if you need further assistance. |
What Does This Do
Fixes
HttpServerResponseTracingHandlerto correctly handle chunked/streaming HTTP responses. The span now covers the full response duration (from headers toLastHttpContent) instead of closing at header-send time.Four issues addressed:
FullHttpResponsemust be checked beforeHttpResponseandLastHttpContentsince it extends bothSTREAMING_CONTEXT_KEYchannel attribute to prevent the next request'schannelReadfrom overwriting the span context beforeLastHttpContentis writtenchannelInactivenow finishes spans stored inSTREAMING_CONTEXT_KEYwhen the channel closes mid-streamLastHttpContentMotivation
Applications using chunked HTTP responses (e.g. Ktor's
respondOutputStream) report near-zero latency in APM — the span closes when response headers are sent, not when the stream finishes.call.respondOutputStream { repeat(5) { writeSomeChunk(); delay(1000) } // 5s total — DD reports ~0ms }This affects any Netty-backed server that sends
HttpResponse+ multipleHttpContent+LastHttpContentseparately (i.e. chunked transfer encoding).Additional Notes
Files Changed
netty-4.1/.../HttpServerResponseTracingHandler.java— message type routing, streaming context key, websocket + bodyless fixnetty-4.1/.../HttpServerRequestTracingHandler.java— channelInactive streaming span cleanupnetty-common/.../AttributeKeys.java— addedSTREAMING_CONTEXT_KEYnetty-4.1/.../NettyChunkedResponseTest.java— new JUnit 5 test (4 test cases)Tests
New test class
NettyChunkedResponseTestwith a real Netty server that writes chunked responses manually (HttpResponse + HttpContent* + LastHttpContent):chunkedResponseSpanIncludesFullStreamDurationfullResponseStillFinishesSpanImmediatelykeepAliveSequentialChunkedRequestsGetCorrectSpansconnectionDropDuringChunkedResponseFinishesSpanContributor Checklist
type:and (comp:orinst:) labels — external contributor, no permission to add labels; please addtype: bug,inst: netty,tag: ai generated