Tags: GoodNotes/dd-trace-java
Tags
Fix Netty HTTP span lifecycle for chunked/streaming responses
The original HttpServerResponseTracingHandler had a single dispatch branch
that always closed the span when HttpResponse (headers) was written.
LastHttpContent — the actual end of a chunked stream — was silently passed
through with no span involvement, causing near-zero latency in APM for any
streaming/chunked response.
Two bugs fixed:
Bug 1 — span always closed on HttpResponse, no LastHttpContent handling.
Added explicit routing for all four Netty message types:
- FullHttpResponse → finish span immediately (checked FIRST: extends both
LastHttpContent and HttpResponse)
- HttpResponse → chunked headers only; save context, don't finish span
- LastHttpContent → finish span here to capture full streaming duration
- HttpContent → intermediate chunk; pass through
Bug 2 — keep-alive race condition.
Under HTTP keep-alive, channelRead for the next request can overwrite
CONTEXT_ATTRIBUTE_KEY before the pending LastHttpContent write task runs,
causing handleLastHttpContent to finish the wrong span (~1-chunk duration).
Fix: STREAMING_CONTEXT_KEY, a separate channel attribute set at chunked
header time and consumed (getAndRemove) by LastHttpContent — immune to
overwrite by the next request's span.
Verified: concurrent load test (48 requests, 8 threads):
streaming/slow → ~5020ms, streaming/fast → ~61ms in DataDog APM. No outliers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PreviousNext