Skip to content

fix!: harden lifecycle, dispatchers, back-pressure#778

Open
devcrocod wants to merge 1 commit into
mainfrom
devcrocod/stdio-server-rework
Open

fix!: harden lifecycle, dispatchers, back-pressure#778
devcrocod wants to merge 1 commit into
mainfrom
devcrocod/stdio-server-rework

Conversation

@devcrocod
Copy link
Copy Markdown
Contributor

@devcrocod devcrocod commented May 20, 2026

fixes #573
fixes #574
fixes #708

  • Handlers ran on Dispatchers.IO → now Dispatchers.Default, configurable via handlerDispatcher.
  • Reader/writer dispatcher exposed as ioDispatcher (default IODispatcher.limitedParallelism(2)).
  • Optional caller-supplied CoroutineScope so the pipeline isn't detached from the caller's tree.
  • CancellationException consistently rethrown across all pumps.
  • Unbounded channels → bounded with graceful drain on close (in-flight outbound flushed before onClose).
  • Input Source now closed on natural EOF / reader error (was leaking for non-System.in sources).
  • Race between start() and close() fixed via 3-state CAS + setupComplete barrier replacing AtomicBoolean.

How Has This Been Tested?

unit and integration tests

Breaking Changes

  • Handlers default to Dispatchers.Default (was Dispatchers.IO). Override with handlerDispatcher = Dispatchers.IO if your handlers perform blocking I/O.
  • send() suspends under back-pressure instead of buffering unbounded.
  • StdioServerTransport(input, output) ctor is @Deprecated (WARNING) in favor of StdioServerTransport(input, output) { ... }. Still binary-compatible.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

…currency. Added support for custom `CoroutineScope` and I/O dispatchers. Updated test cases for extended functionality.
Copilot AI review requested due to automatic review settings May 20, 2026 15:41
@devcrocod devcrocod requested a review from e5l May 20, 2026 15:41
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 hardens StdioServerTransport’s coroutine lifecycle and threading model to address handler-dispatcher misuse and scope detachment, while also adding bounded-channel back-pressure and more robust start/close race handling.

Changes:

  • Introduces a builder-based constructor that allows configuring handlerDispatcher, ioDispatcher, and an optional caller-provided CoroutineScope.
  • Reworks the internal pumps (reader/processor/writer) to use bounded channels, apply back-pressure to send(), and coordinate shutdown via a 3-state CAS + setup barrier.
  • Expands JVM integration tests to cover back-pressure, concurrency races, lifecycle error cases, EOF resource closing, and dispatcher/scope configuration.

Reviewed changes

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

File Description
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt Major lifecycle/dispatcher/scope refactor; introduces bounded channels + builder configuration.
kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt Adds/updates integration tests for new lifecycle semantics, back-pressure, and configurability.
kotlin-sdk-server/api/kotlin-sdk-server.api API surface update reflecting the new builder constructor and nested Builder type.
.gitignore Adjusts ignore patterns related to SWE agent tooling files/directories.

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

override suspend fun start() {
if (!initialized.compareAndSet(expectedValue = false, newValue = true)) {
if (!state.compareAndSet(State.New, State.Operational)) {
error("StdioServerTransport already started!")
}
if (previous == State.New) {
setupComplete.complete(Unit)
return
logger.debug(cause) { "$jobName job completed exceptionally" }
runCatching { input.close() }.onFailure { logger.warn(it) { "Failed to close stdin" } }
readerJob?.cancel()
readChannel.close()
if (state.load() == State.Stopped) {
logger.debug(e) { "Reader interrupted by close()" }
} else {
logger.error(e) { "Error reading from stdin" }
Comment on lines +239 to +242
} catch (e: Throwable) {
logger.error(e) { "Error writing to stdout" }
_onError(e)
} finally {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants