René's URL Explorer Experiment


Title: Fix Netty HTTP span lifecycle for chunked/streaming responses by gtukmachev · Pull Request #1 · GoodNotes/dd-trace-java · GitHub

Open Graph Title: Fix Netty HTTP span lifecycle for chunked/streaming responses by gtukmachev · Pull Request #1 · GoodNotes/dd-trace-java

X Title: Fix Netty HTTP span lifecycle for chunked/streaming responses by gtukmachev · Pull Request #1 · GoodNotes/dd-trace-java

Description: Problem 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. // Ktor — respondOutputStream sends headers immediately, streams body async call.respondOutputStream { repeat(5) { writeSomeChunk(); delay(1000) } // 5s total — DD reports ~0ms } This affects any Netty-backed server that sends HttpResponse + multiple HttpContent + LastHttpContent separately (i.e. chunked transfer encoding). Root Cause Bug 1 — span always closed on HttpResponse, no LastHttpContent handling The original handler had a single dispatch branch. Every HttpResponse (headers-only or full) immediately finished the span. LastHttpContent — the actual end of a chunked stream — was never inspected; it fell through to ctx.write(msg, prm) silently. // Before: only one branch, span always closed at header-send time if (span == null || !(msg instanceof HttpResponse)) { ctx.write(msg, prm); // LastHttpContent passes through here — span untouched return; } // ... span.finish() always called here, even for chunked headers The fix adds explicit routing for all four Netty message types. FullHttpResponse must be checked first because it extends both LastHttpContent and HttpResponse; without that ordering it would be caught by the wrong branch. // After: three explicit branches; everything else (chunks, unrelated messages) falls through if (msg instanceof FullHttpResponse) { handleFullHttpResponse(...); return; } // finish immediately if (msg instanceof HttpResponse) { handleHttpResponse(...); return; } // headers only — don't finish if (msg instanceof LastHttpContent) { handleLastHttpContent(...); return; } // finish here ctx.write(msg, prm); // intermediate HttpContent chunks + unrelated messages — pass through Bug 2 — keep-alive race condition Under HTTP keep-alive, Netty's event loop can process channelRead for the next request (overwriting CONTEXT_ATTRIBUTE_KEY with the new span) before the pending write task for the previous response's LastHttpContent runs. Result: handleLastHttpContent finishes the new request's span with ~1-chunk duration. Fix: a dedicated STREAMING_CONTEXT_KEY channel attribute, set when chunked headers are sent and read (then cleared) by LastHttpContent — immune to overwrite by the next request. // AttributeKeys.java public static final AttributeKey STREAMING_CONTEXT_KEY = attributeKey("datadog.server.streaming.context"); // Set when chunked headers go out ctx.channel().attr(STREAMING_CONTEXT_KEY).set(storedContext); // Read in LastHttpContent — safe from keep-alive overwrite Context streamingCtx = ctx.channel().attr(STREAMING_CONTEXT_KEY).getAndRemove(); Files Changed netty-4.1/.../HttpServerResponseTracingHandler.java — routing by message type, streaming context key netty-common/.../AttributeKeys.java — added STREAMING_CONTEXT_KEY Verification Concurrent load test (48 requests, 8 threads): streaming/slow → ~5020ms, streaming/fast → ~61ms in DataDog APM. No outliers. Error capturing for streaming responses The agent cannot change the HTTP status code once headers are sent. However, application code can mark the span as an error before LastHttpContent closes it, and the agent will preserve those tags. The window is: after the exception is thrown inside the streaming lambda, before the framework closes the OutputStream (which writes LastHttpContent and triggers span.finish()). Mark the span during that catch block and it will be captured correctly. Required pattern (application side) Add io.opentracing:opentracing-api and io.opentracing:opentracing-util as dependencies (the DD agent auto-registers as the GlobalTracer). Then wrap the streaming body in a try/catch: // build.gradle / pom.xml implementation("io.opentracing:opentracing-api:0.33.0") implementation("io.opentracing:opentracing-util:0.33.0") import io.opentracing.util.GlobalTracer call.respondOutputStream { try { // ... write chunks ... if (someErrorCondition) { throw RuntimeException("stream failed mid-way") } } catch (e: Exception) { // Mark the active DD span as error BEFORE LastHttpContent closes it. // HTTP status code cannot be changed (headers already sent), but the // span will be tagged as an error and appear correctly in APM. GlobalTracer.get().activeSpan()?.let { span -> span.setTag("error", true) span.setTag("error.type", e::class.java.name) span.setTag("error.message", e.message ?: "unknown") } throw e // re-throw so the framework closes the stream } } Why this works The DD agent instruments Kotlin coroutines, so GlobalTracer.get().activeSpan() returns the HTTP server span even inside respondOutputStream's lambda on Dispatchers.IO. The catch block runs before Ktor closes the OutputStream and emits LastHttpContent, giving the agent time to record the error before span.finish() is called. Verified result (DataDog APM) Endpoint Duration Status error.type error.message POST /testing/streaming/slow (error) ~5025ms error java.lang.RuntimeException Streaming slow: error after all chunks sent POST /testing/streaming/slow (success) ~5024ms ok — — POST /testing/streaming/fast (error) ~67ms error java.lang.RuntimeException Streaming fast: error after all chunks sent POST /testing/streaming/fast (success) ~64ms ok — — Full streaming duration is preserved for both success and error cases. Note: This is a demo PR showing the fix applied to the GoodNotes org fork. The same fix was submitted upstream: DataDog#10656

Open Graph Description: Problem 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 finishe...

X Description: Problem 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 fin...

Opengraph URL: https://github.com/GoodNotes/dd-trace-java/pull/1

X: @github

direct link

Domain: github.com

route-pattern/:user_id/:repository/pull/:id/files(.:format)
route-controllerpull_requests
route-actionfiles
fetch-noncev2:415ead19-401b-f349-9215-cf16a6a98207
current-catalog-service-hashae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b
request-id8402:3A8185:6C2F71:8FADAC:6A5BDC2E
html-safe-nonceaff1166a03e1ea74b0445b22537da69eb916008a7c319c1650763fe2f93e2d97
visitor-payloadeyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI4NDAyOjNBODE4NTo2QzJGNzE6OEZBREFDOjZBNUJEQzJFIiwidmlzaXRvcl9pZCI6IjIzMzIxNTE5ODc3ODg1Njk2NDYiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ==
visitor-hmac0d5c17328a4e4a3bd1bf186c54532c2234bdf54f9cf93315717f5976f4bb6810
hovercard-subject-tagpull_request:3321056987
github-keyboard-shortcutsrepository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot
google-site-verificationApib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I
octolytics-urlhttps://collector.github.com/github/collect
analytics-location///pull_requests/show/files
fb:app_id1401488693436528
apple-itunes-appapp-id=1477376905, app-argument=https://github.com/GoodNotes/dd-trace-java/pull/1/files
twitter:imagehttps://avatars.githubusercontent.com/u/8056762?s=400&v=4
twitter:cardsummary_large_image
og:imagehttps://avatars.githubusercontent.com/u/8056762?s=400&v=4
og:image:altProblem 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 finishe...
og:site_nameGitHub
og:typeobject
hostnamegithub.com
expected-hostnamegithub.com
None5290d7e14309ad1e76106a9c4237bd1041517e83ea182c8ab756752cb0c6940b
turbo-cache-controlno-preview
diff-viewunified
go-importgithub.com/GoodNotes/dd-trace-java git https://github.com/GoodNotes/dd-trace-java.git
octolytics-dimension-user_id20379375
octolytics-dimension-user_loginGoodNotes
octolytics-dimension-repository_id1165766701
octolytics-dimension-repository_nwoGoodNotes/dd-trace-java
octolytics-dimension-repository_publictrue
octolytics-dimension-repository_is_forktrue
octolytics-dimension-repository_parent_id89221572
octolytics-dimension-repository_parent_nwoDataDog/dd-trace-java
octolytics-dimension-repository_network_root_id89221572
octolytics-dimension-repository_network_root_nwoDataDog/dd-trace-java
turbo-body-classeslogged-out env-production page-responsive full-width
disable-turbotrue
browser-stats-urlhttps://api.github.com/_private/browser/stats
browser-errors-urlhttps://api.github.com/_private/browser/errors
release9c975978430e9ad293956f2bbdaf153b1bd84a99
ui-targetfull
theme-color#1e2327
color-schemelight dark

Links:

Skip to contenthttps://github.com/GoodNotes/dd-trace-java/pull/1/files#start-of-content
https://github.com/
Sign in https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2FGoodNotes%2Fdd-trace-java%2Fpull%2F1%2Ffiles
GitHub CopilotWrite better code with AIhttps://github.com/features/copilot
GitHub Copilot appDirect agents from issue to mergehttps://github.com/features/ai/github-app
MCP RegistryNewIntegrate external toolshttps://github.com/mcp
ActionsAutomate any workflowhttps://github.com/features/actions
CodespacesInstant dev environmentshttps://github.com/features/codespaces
IssuesPlan and track workhttps://github.com/features/issues
Code ReviewManage code changeshttps://github.com/features/code-review
GitHub Advanced SecurityFind and fix vulnerabilitieshttps://github.com/security/advanced-security
Code securitySecure your code as you buildhttps://github.com/security/advanced-security/code-security
Secret protectionStop leaks before they starthttps://github.com/security/advanced-security/secret-protection
Why GitHubhttps://github.com/why-github
Documentationhttps://docs.github.com
Bloghttps://github.blog
Changeloghttps://github.blog/changelog
Marketplacehttps://github.com/marketplace
View all featureshttps://github.com/features
Enterpriseshttps://github.com/enterprise
Small and medium teamshttps://github.com/team
Startupshttps://github.com/enterprise/startups
Nonprofitshttps://github.com/solutions/industry/nonprofits
App Modernizationhttps://github.com/solutions/use-case/app-modernization
DevSecOpshttps://github.com/solutions/use-case/devsecops
DevOpshttps://github.com/solutions/use-case/devops
CI/CDhttps://github.com/solutions/use-case/ci-cd
View all use caseshttps://github.com/solutions/use-case
Healthcarehttps://github.com/solutions/industry/healthcare
Financial serviceshttps://github.com/solutions/industry/financial-services
Manufacturinghttps://github.com/solutions/industry/manufacturing
Governmenthttps://github.com/solutions/industry/government
View all industrieshttps://github.com/solutions/industry
View all solutionshttps://github.com/solutions
AIhttps://github.com/resources/articles?topic=ai
Software Developmenthttps://github.com/resources/articles?topic=software-development
DevOpshttps://github.com/resources/articles?topic=devops
Securityhttps://github.com/resources/articles?topic=security
View all topicshttps://github.com/resources/articles
Customer storieshttps://github.com/customer-stories
Events & webinarshttps://github.com/resources/events
Ebooks & reportshttps://github.com/resources/whitepapers
Business insightshttps://github.com/solutions/executive-insights
GitHub Skillshttps://skills.github.com
Documentationhttps://docs.github.com
Customer supporthttps://support.github.com
Community forumhttps://github.com/orgs/community/discussions
Trust centerhttps://github.com/trust-center
Partnershttps://github.com/partners
View all resourceshttps://github.com/resources
GitHub SponsorsFund open source developershttps://github.com/open-source/sponsors
Security Labhttps://securitylab.github.com
Maintainer Communityhttps://maintainers.github.com
Acceleratorhttps://github.com/open-source/accelerator
GitHub Starshttps://stars.github.com
Archive Programhttps://archiveprogram.github.com
Topicshttps://github.com/topics
Trendinghttps://github.com/trending
Collectionshttps://github.com/collections
Enterprise platformAI-powered developer platformhttps://github.com/enterprise
GitHub Advanced SecurityEnterprise-grade security featureshttps://github.com/security/advanced-security
Copilot for BusinessEnterprise-grade AI featureshttps://github.com/features/copilot/copilot-business
Premium SupportEnterprise-grade 24/7 supporthttps://github.com/enterprise/premium-support
Pricinghttps://github.com/pricing
Search syntax tipshttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
documentationhttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
Sign in https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2FGoodNotes%2Fdd-trace-java%2Fpull%2F1%2Ffiles
Sign up https://github.com/signup?ref_cta=Sign+up&ref_loc=header+logged+out&ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fpull_requests%2Fshow%2Ffiles&source=header-repo&source_repo=GoodNotes%2Fdd-trace-java
Reloadhttps://github.com/GoodNotes/dd-trace-java/pull/1/files
Reloadhttps://github.com/GoodNotes/dd-trace-java/pull/1/files
Reloadhttps://github.com/GoodNotes/dd-trace-java/pull/1/files
Please reload this pagehttps://github.com/GoodNotes/dd-trace-java/pull/1/files
GoodNotes https://github.com/GoodNotes
dd-trace-javahttps://github.com/GoodNotes/dd-trace-java
DataDog/dd-trace-javahttps://github.com/DataDog/dd-trace-java
Notifications https://github.com/login?return_to=%2FGoodNotes%2Fdd-trace-java
Fork 0 https://github.com/login?return_to=%2FGoodNotes%2Fdd-trace-java
Star 0 https://github.com/login?return_to=%2FGoodNotes%2Fdd-trace-java
Code https://github.com/GoodNotes/dd-trace-java
Pull requests 1 https://github.com/GoodNotes/dd-trace-java/pulls
Actions https://github.com/GoodNotes/dd-trace-java/actions
Projects https://github.com/GoodNotes/dd-trace-java/projects
Security and quality 0 https://github.com/GoodNotes/dd-trace-java/security
Insights https://github.com/GoodNotes/dd-trace-java/pulse
Code https://github.com/GoodNotes/dd-trace-java
Pull requests https://github.com/GoodNotes/dd-trace-java/pulls
Actions https://github.com/GoodNotes/dd-trace-java/actions
Projects https://github.com/GoodNotes/dd-trace-java/projects
Security and quality https://github.com/GoodNotes/dd-trace-java/security
Insights https://github.com/GoodNotes/dd-trace-java/pulse
Sign up for GitHub https://github.com/signup?return_to=%2FGoodNotes%2Fdd-trace-java%2Fissues%2Fnew%2Fchoose
terms of servicehttps://docs.github.com/terms
privacy statementhttps://docs.github.com/privacy
Sign inhttps://github.com/login?return_to=%2FGoodNotes%2Fdd-trace-java%2Fissues%2Fnew%2Fchoose
gtukmachevhttps://github.com/gtukmachev
masterhttps://github.com/GoodNotes/dd-trace-java/tree/master
gsc-577-fix-ktor-streaming-instrumentationhttps://github.com/GoodNotes/dd-trace-java/tree/gsc-577-fix-ktor-streaming-instrumentation
Conversation 1 https://github.com/GoodNotes/dd-trace-java/pull/1
Commits 2 https://github.com/GoodNotes/dd-trace-java/pull/1/commits
Checks 7 https://github.com/GoodNotes/dd-trace-java/pull/1/checks
Files changed https://github.com/GoodNotes/dd-trace-java/pull/1/files
Please reload this pagehttps://github.com/GoodNotes/dd-trace-java/pull/1/files
Fix Netty HTTP span lifecycle for chunked/streaming responses https://github.com/GoodNotes/dd-trace-java/pull/1/files#top
Show all changes 2 commits https://github.com/GoodNotes/dd-trace-java/pull/1/files
d3bb33d Add instrumentation tests for chunked/streaming HTTP response handling gtukmachev May 13, 2026 https://github.com/GoodNotes/dd-trace-java/pull/1/commits/d3bb33d69699e5b575280d23b42af4a063d374cd
77cc414 Fix Netty HTTP span lifecycle for chunked/streaming responses gtukmachev May 13, 2026 https://github.com/GoodNotes/dd-trace-java/pull/1/commits/77cc414ea5747e842950dd68be65e3e82ced37bd
Clear filters https://github.com/GoodNotes/dd-trace-java/pull/1/files
Please reload this pagehttps://github.com/GoodNotes/dd-trace-java/pull/1/files
Please reload this pagehttps://github.com/GoodNotes/dd-trace-java/pull/1/files
HttpServerRequestTracingHandler.java https://github.com/GoodNotes/dd-trace-java/pull/1/files#diff-ca5529445e5bcaa8fedf5b6799afb117f77427a33d9b32715b8735ba8c91a0ee
HttpServerResponseTracingHandler.java https://github.com/GoodNotes/dd-trace-java/pull/1/files#diff-acca217619d049ab3b8bce817c283e8fd6cb22cf5dca826b2454fb66b22599f8
NettyChunkedResponseTest.java https://github.com/GoodNotes/dd-trace-java/pull/1/files#diff-203e0be6bc3a150225726ad973011e474c37f3dd8d4b4ddc668797a966a3c9cf
AttributeKeys.java https://github.com/GoodNotes/dd-trace-java/pull/1/files#diff-b57e506c3b5643d5a59586c4e7d4a484c517a267d80b591c87de6aa29608cc50
...in/java/datadog/trace/instrumentation/netty41/server/HttpServerRequestTracingHandler.javahttps://github.com/GoodNotes/dd-trace-java/pull/1/files#diff-ca5529445e5bcaa8fedf5b6799afb117f77427a33d9b32715b8735ba8c91a0ee
View file https://github.com/GoodNotes/dd-trace-java/blob/77cc414ea5747e842950dd68be65e3e82ced37bd/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/HttpServerRequestTracingHandler.java
Open in desktop https://desktop.github.com
https://github.co/hiddenchars
https://github.com/GoodNotes/dd-trace-java/pull/1/{{ revealButtonHref }}
https://github.com/GoodNotes/dd-trace-java/pull/1/files#diff-ca5529445e5bcaa8fedf5b6799afb117f77427a33d9b32715b8735ba8c91a0ee
https://github.com/GoodNotes/dd-trace-java/pull/1/files#diff-ca5529445e5bcaa8fedf5b6799afb117f77427a33d9b32715b8735ba8c91a0ee
https://github.com/GoodNotes/dd-trace-java/pull/1/files#diff-ca5529445e5bcaa8fedf5b6799afb117f77427a33d9b32715b8735ba8c91a0ee
https://github.com/GoodNotes/dd-trace-java/pull/1/files#diff-ca5529445e5bcaa8fedf5b6799afb117f77427a33d9b32715b8735ba8c91a0ee
...n/java/datadog/trace/instrumentation/netty41/server/HttpServerResponseTracingHandler.javahttps://github.com/GoodNotes/dd-trace-java/pull/1/files#diff-acca217619d049ab3b8bce817c283e8fd6cb22cf5dca826b2454fb66b22599f8
View file https://github.com/GoodNotes/dd-trace-java/blob/77cc414ea5747e842950dd68be65e3e82ced37bd/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/HttpServerResponseTracingHandler.java
Open in desktop https://desktop.github.com
https://github.co/hiddenchars
https://github.com/GoodNotes/dd-trace-java/pull/1/{{ revealButtonHref }}
https://github.com/GoodNotes/dd-trace-java/pull/1/files#diff-acca217619d049ab3b8bce817c283e8fd6cb22cf5dca826b2454fb66b22599f8
https://github.com/GoodNotes/dd-trace-java/pull/1/files#diff-acca217619d049ab3b8bce817c283e8fd6cb22cf5dca826b2454fb66b22599f8
https://github.com/GoodNotes/dd-trace-java/pull/1/files#diff-acca217619d049ab3b8bce817c283e8fd6cb22cf5dca826b2454fb66b22599f8
https://github.com/GoodNotes/dd-trace-java/pull/1/files#diff-acca217619d049ab3b8bce817c283e8fd6cb22cf5dca826b2454fb66b22599f8
Please reload this pagehttps://github.com/GoodNotes/dd-trace-java/pull/1/files
Please reload this pagehttps://github.com/GoodNotes/dd-trace-java/pull/1/files
https://github.com
Termshttps://docs.github.com/site-policy/github-terms/github-terms-of-service
Privacyhttps://docs.github.com/site-policy/privacy-policies/github-privacy-statement
Securityhttps://github.com/security
Statushttps://www.githubstatus.com/
Communityhttps://github.community/
Docshttps://docs.github.com/
Contacthttps://support.github.com?tags=dotcom-footer

Viewport: width=device-width


URLs of crawlers that visited me.