Skip to content

fix: propagate CancellationException instead of logging as error#706

Merged
devcrocod merged 2 commits intomainfrom
devcrocod/cancellation-exception-handling
Apr 15, 2026
Merged

fix: propagate CancellationException instead of logging as error#706
devcrocod merged 2 commits intomainfrom
devcrocod/cancellation-exception-handling

Conversation

@devcrocod
Copy link
Copy Markdown
Contributor

fixes #242

How Has This Been Tested?

new and existing tests pass

Breaking Changes

none

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

Copilot AI review requested due to automatic review settings April 15, 2026 00:01
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 issue #242 by ensuring coroutine cancellations (CancellationException and subclasses) are propagated rather than treated as errors, reducing noisy/incorrect error logging in normal cancellation scenarios.

Changes:

  • Rethrow CancellationException in multiple server/client transports and protocol message handling paths to avoid logging/reporting cancellations as errors.
  • Make shutdown/cleanup paths non-cancellable in a few transports via withContext(NonCancellable).
  • Add protocol tests ensuring cancellation from notification handlers is propagated and does not trigger onError.

Reviewed changes

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

Show a summary per file
File Description
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt Propagates CancellationException in several SSE/HTTP paths; makes close() and SSE stream closing non-cancellable.
kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt Propagates CancellationException in request parsing/handling paths.
kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt Propagates CancellationException from notification handling and from method-not-found response sending.
kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt Propagates CancellationException from message decoding/dispatch loop.
kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt Makes close non-cancellable and propagates CancellationException during message processing.
kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt Makes close non-cancellable; adjusts shutdown behavior for the collector job.
kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt Adds tests asserting cancellation propagation and correct onError behavior for notifications.

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

Comment on lines 174 to +183
override suspend fun closeResources() {
job?.cancelAndJoin()
try {
if (::session.isInitialized) session.cancel()
if (::scope.isInitialized) scope.cancel()
endpoint.cancel()
} catch (e: Throwable) {
_onError(e)
withContext(NonCancellable) {
job?.cancel()
try {
if (::session.isInitialized) session.cancel()
if (::scope.isInitialized) scope.cancel()
endpoint.cancel()
} catch (e: Throwable) {
_onError(e)
}
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

closeResources() now only calls job?.cancel() and does not wait for the collector coroutine to finish. This can make close() return while the transport is still processing events / running cleanup, and can race with session.cancel()/scope.cancel(). Consider cancelling and then join()ing the job when closeResources() is invoked from an external coroutine, while avoiding self-join when called from the collector’s finally block.

Copilot uses AI. Check for mistakes.
@devcrocod devcrocod requested a review from e5l April 15, 2026 00:05
Copy link
Copy Markdown
Contributor

@e5l e5l left a comment

Choose a reason for hiding this comment

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

lgtm. should we introduce this inspection as part of the code review skill? it is frequent error

@devcrocod
Copy link
Copy Markdown
Contributor Author

should we introduce this inspection as part of the code review skill? it is frequent error

This is already documented in agents.md
We could also add a dedicated skill, I'd suggest considering a general-purpose skill for working with coroutines in kotlin-agents-skill

@devcrocod devcrocod merged commit e7b09d8 into main Apr 15, 2026
20 checks passed
@devcrocod devcrocod deleted the devcrocod/cancellation-exception-handling branch April 15, 2026 12:59
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.

Cancellation exceptions cause an error to be logged

3 participants