Skip to content

fix(ktor): resolve client hang on streaming path with Ktor 3.5.0#18781

Merged
trask merged 10 commits into
open-telemetry:mainfrom
amccague:fix/ktor-client-streaming-hang
May 26, 2026
Merged

fix(ktor): resolve client hang on streaming path with Ktor 3.5.0#18781
trask merged 10 commits into
open-telemetry:mainfrom
amccague:fix/ktor-client-streaming-hang

Conversation

@amccague
Copy link
Copy Markdown
Contributor

@amccague amccague commented May 18, 2026

#18779

Two changes to fix requests hanging when using prepareGet().execute {}:

  1. Request pipeline: replace withContext(asContextElement()) { proceed() } with attribute-based context passing. Ktor 3.5.0 (KTOR-9546) added a responseJobHolder as a child of the call job in DefaultTransform, which delays call job completion. withContext wrapping proceed() at request pipeline scope prevents the call from returning promptly. The parent context is now passed via request attributes to the send pipeline where createSpan consumes it.

  2. Receive pipeline: replace scope.launch { job.join(); endSpan() } with job.invokeOnCompletion { endSpan() }. The launched coroutine waited on the call job which no longer completes promptly due to KTOR-9546's responseJobHolder child. invokeOnCompletion fires synchronously when the job reaches terminal state without suspending or leaking coroutines.

Copilot AI review requested due to automatic review settings May 18, 2026 13:23
@amccague amccague requested a review from a team as a code owner May 18, 2026 13:23
@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla Bot commented May 18, 2026

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

  • ✅ login: amccague / name: Adrian McCague (990ffb4)

Two changes to fix requests hanging when using prepareGet().execute {}:

1. Request pipeline: replace withContext(asContextElement()) { proceed() }
   with attribute-based context passing. Ktor 3.5.0 (KTOR-9546) added a
   responseJobHolder as a child of the call job in DefaultTransform, which
   delays call job completion. Combined with DebugPipelineContext (hardcoded
   on for client pipelines), withContext wrapping proceed() at request
   pipeline scope prevents the call from returning promptly. The parent
   context is now passed via request attributes to the send pipeline where
   createSpan consumes it.

2. Receive pipeline: replace scope.launch { job.join(); endSpan() } with
   job.invokeOnCompletion { endSpan() }. The launched coroutine waited on
   the call job which no longer completes promptly due to KTOR-9546's
   responseJobHolder child. invokeOnCompletion fires synchronously when
   the job reaches terminal state without suspending or leaking coroutines.
@amccague amccague force-pushed the fix/ktor-client-streaming-hang branch from 9f35a4f to 990ffb4 Compare May 18, 2026 13:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses Ktor 3.5.0 client hangs on streaming prepareGet().execute {} calls by avoiding coroutine context wrapping at request-pipeline scope and ending spans via job completion callbacks.

Changes:

  • Pass the parent OpenTelemetry context through Ktor request attributes into span creation.
  • Replace the receive-pipeline launch { job.join() } span-end flow with invokeOnCompletion.
  • Add a streaming regression test and update Ktor 3 test/runtime dependencies to 3.5.0.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
instrumentation/ktor/ktor-common-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/common/v2_0/internal/KtorClientTelemetryUtil.kt Updates context propagation and span-ending logic for Ktor client instrumentation.
instrumentation/ktor/ktor-common-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/common/v2_0/AbstractKtorClientTelemetry.kt Adds a createSpan overload accepting an explicit parent context.
instrumentation/ktor/ktor-3.0/testing/build.gradle.kts Raises shared Ktor 3 testing dependencies to 3.5.0.
instrumentation/ktor/ktor-3.0/library/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/KtorHttpClientStreamingTest.kt Adds a regression test for streaming client requests with telemetry installed.
instrumentation/ktor/ktor-3.0/library/build.gradle.kts Raises Ktor 3 library/test dependency version to 3.5.0.
instrumentation/ktor/ktor-3.0/javaagent/build.gradle.kts Raises Ktor 3 javaagent library/test dependency version to 3.5.0.
Comments suppressed due to low confidence (1)

instrumentation/ktor/ktor-3.0/library/src/test/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/KtorHttpClientStreamingTest.kt:75

  • [Testing] This regression test only verifies that the streaming request returns; it does not assert that the client span is actually ended/exported after the invokeOnCompletion path runs. Since this PR changes span-ending behavior for this exact path, the test would still pass if the request completed but telemetry was silently dropped.
          client.prepareGet("http://localhost:$serverPort/success").execute { response ->
            response.bodyAsText()
          }

Comment thread instrumentation/ktor/ktor-3.0/library/build.gradle.kts Outdated
Comment thread instrumentation/ktor/ktor-3.0/javaagent/build.gradle.kts Outdated
Comment thread instrumentation/ktor/ktor-3.0/testing/build.gradle.kts Outdated
@amccague amccague force-pushed the fix/ktor-client-streaming-hang branch from 83d8249 to 0926472 Compare May 18, 2026 13:58
@amccague amccague requested a review from Copilot May 18, 2026 13:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread instrumentation/ktor/ktor-3.0/library/build.gradle.kts Outdated
import kotlin.time.Duration.Companion.nanoseconds
import kotlin.time.Duration.Companion.seconds

class KtorHttpClientStreamingTest {
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.

did you consider adding this test to AbstractKtorHttpClientTest?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done this, I prefer it a bit more self-contained because of the pre-requisites but have a look at what I've done with comments to explain themselves.

@laurit laurit added this to the v2.29.0 milestone May 26, 2026
@trask trask merged commit 3f1fd39 into open-telemetry:main May 26, 2026
95 checks passed
@otelbot
Copy link
Copy Markdown
Contributor

otelbot Bot commented May 26, 2026

Thank you for your contribution @amccague! 🎉 We would like to hear from you about your experience contributing to OpenTelemetry by taking a few minutes to fill out this survey.

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.

4 participants