Skip to content

fix(client): close stdio read sources during shutdown - #798

Merged
devcrocod merged 1 commit into
modelcontextprotocol:mainfrom
jstar0:fix/stdio-client-close-blocking-read
Jun 30, 2026
Merged

fix(client): close stdio read sources during shutdown#798
devcrocod merged 1 commit into
modelcontextprotocol:mainfrom
jstar0:fix/stdio-client-close-blocking-read

Conversation

@jstar0

@jstar0 jstar0 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes StdioClientTransport.close() so shutdown does not hang when the stdin read loop is blocked waiting for input.

Root Cause / Context

StdioClientTransport.closeResources() stopped the transport coroutine scope and then waited for the scope job to finish. On the JVM, a Source.readAtMostTo() call backed by a blocking Java stream does not necessarily return when the coroutine is cancelled. If the read loop stayed blocked, close() could wait indefinitely.

Changes

  • Close the stdin source during stdio client shutdown before waiting for transport coroutines to finish.
  • Also close the optional stderr source during shutdown.
  • Keep source-close failures non-fatal and log them at debug level.
  • Add a JVM regression test with a RawSource that blocks in readAtMostTo() until close() is called.
  • Assert the close path completes, emits onClose, and does not invoke onError.

Scope / Risk

This is limited to StdioClientTransport shutdown behavior. It does not change the public API or server transports. The regression coverage is JVM-focused because the reported hang is caused by a blocking Java stream read.

Verification

./gradlew :integration-test:jvmTest --tests 'io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransportTest'
./gradlew :kotlin-sdk-client:jvmTest
./gradlew :kotlin-sdk-client:ktlintCheck :integration-test:ktlintCheck :kotlin-sdk-client:detekt :integration-test:detekt :kotlin-sdk-client:apiCheck

Closes #514

Copilot AI 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.

Pull request overview

Fixes a JVM shutdown hang in StdioClientTransport.close() by explicitly closing the read-side sources so a blocking readAtMostTo() can unblock and the transport’s coroutine scope can finish cleanly.

Changes:

  • Close stdin Source during shutdown before waiting for transport coroutines to complete.
  • Also close the optional stderr Source during shutdown; failures to close are non-fatal and logged at debug level.
  • Add a JVM regression test using a RawSource that blocks in readAtMostTo() until close() is called, asserting close() completes and triggers onClose without onError.

Reviewed changes

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

File Description
kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt Close stdin/stderr Sources during shutdown to prevent blocked read loops from hanging close().
integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt Add JVM regression test covering blocked stdin read shutdown behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +148 to +149
input.close()
closeJob.await()

@devcrocod devcrocod 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.

lgtm!

@devcrocod
devcrocod force-pushed the fix/stdio-client-close-blocking-read branch from f972631 to fb3a1e3 Compare June 29, 2026 15:20
@devcrocod
devcrocod merged commit 0221b8d into modelcontextprotocol:main Jun 30, 2026
22 of 23 checks passed
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.

StdioClientTransport fails to close gracefully when Java stream hangs in blocking read

3 participants