fix: propagate CancellationException instead of logging as error (#706)
fixes #242 ## How Has This Been Tested? new and existing tests pass ## Breaking Changes none ## Types of changes - [x] 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 - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [x] New and existing tests pass locally - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed
This commit is contained in:
parent
bce6811b66
commit
e7b09d84f7
2 changed files with 109 additions and 25 deletions
|
|
@ -84,6 +84,8 @@ public class SseServerTransport(private val endpoint: String, private val sessio
|
|||
}
|
||||
|
||||
call.receiveText()
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
call.respondText("Invalid message: ${e.message}", status = HttpStatusCode.BadRequest)
|
||||
_onError.invoke(e)
|
||||
|
|
@ -92,6 +94,8 @@ public class SseServerTransport(private val endpoint: String, private val sessio
|
|||
|
||||
try {
|
||||
handleMessage(body)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
call.respondText("Error handling message $body: ${e.message}", status = HttpStatusCode.BadRequest)
|
||||
return
|
||||
|
|
@ -108,6 +112,8 @@ public class SseServerTransport(private val endpoint: String, private val sessio
|
|||
try {
|
||||
val parsedMessage = McpJson.decodeFromString<JSONRPCMessage>(message)
|
||||
_onMessage.invoke(parsedMessage)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
_onError.invoke(e)
|
||||
throw e
|
||||
|
|
|
|||
|
|
@ -28,15 +28,18 @@ import io.modelcontextprotocol.kotlin.sdk.types.RPCError
|
|||
import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.REQUEST_TIMEOUT
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.RequestId
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.job
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.decodeFromJsonElement
|
||||
import kotlin.concurrent.atomics.AtomicBoolean
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
|
|
@ -283,17 +286,19 @@ public class StreamableHttpServerTransport(private val configuration: Configurat
|
|||
}
|
||||
|
||||
override suspend fun close() {
|
||||
streamMutex.withLock {
|
||||
streamsMapping.values.forEach {
|
||||
try {
|
||||
it.session?.close()
|
||||
} catch (_: Exception) {
|
||||
withContext(NonCancellable) {
|
||||
streamMutex.withLock {
|
||||
streamsMapping.values.forEach {
|
||||
try {
|
||||
it.session?.close()
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
streamsMapping.clear()
|
||||
requestToStreamMapping.clear()
|
||||
requestToResponseMapping.clear()
|
||||
invokeOnCloseCallback()
|
||||
}
|
||||
streamsMapping.clear()
|
||||
requestToStreamMapping.clear()
|
||||
requestToResponseMapping.clear()
|
||||
invokeOnCloseCallback()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -420,6 +425,8 @@ public class StreamableHttpServerTransport(private val configuration: Configurat
|
|||
call.coroutineContext.job.invokeOnCompletion { streamsMapping.remove(streamId) }
|
||||
|
||||
messages.forEach { message -> _onMessage(message) }
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
call.reject(
|
||||
HttpStatusCode.BadRequest,
|
||||
|
|
@ -498,12 +505,14 @@ public class StreamableHttpServerTransport(private val configuration: Configurat
|
|||
val streamId = requestToStreamMapping[requestId] ?: return
|
||||
val sessionContext = streamsMapping[streamId] ?: return
|
||||
|
||||
try {
|
||||
sessionContext.session?.close()
|
||||
} catch (e: Exception) {
|
||||
_onError(e)
|
||||
} finally {
|
||||
streamsMapping.remove(streamId)
|
||||
withContext(NonCancellable) {
|
||||
try {
|
||||
sessionContext.session?.close()
|
||||
} catch (e: Exception) {
|
||||
_onError(e)
|
||||
} finally {
|
||||
streamsMapping.remove(streamId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -554,6 +563,8 @@ public class StreamableHttpServerTransport(private val configuration: Configurat
|
|||
id = eventId,
|
||||
data = McpJson.encodeToString(message),
|
||||
)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
_onError(IllegalStateException("Failed to replay event: ${e.message}", e))
|
||||
}
|
||||
|
|
@ -565,6 +576,8 @@ public class StreamableHttpServerTransport(private val configuration: Configurat
|
|||
streamsMapping.remove(streamId)
|
||||
throwable?.let { _onError(it) }
|
||||
}
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
_onError(e)
|
||||
}
|
||||
|
|
@ -658,6 +671,8 @@ public class StreamableHttpServerTransport(private val configuration: Configurat
|
|||
private suspend fun flushSse(session: ServerSSESession?) {
|
||||
try {
|
||||
session?.send(data = "")
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
_onError(e)
|
||||
}
|
||||
|
|
@ -709,6 +724,9 @@ public class StreamableHttpServerTransport(private val configuration: Configurat
|
|||
val eventId = configuration.eventStore?.storeEvent(streamId, message)
|
||||
try {
|
||||
session?.send(event = "message", id = eventId, data = McpJson.encodeToString(message))
|
||||
} catch (e: CancellationException) {
|
||||
streamsMapping.remove(streamId)
|
||||
throw e
|
||||
} catch (_: Exception) {
|
||||
streamsMapping.remove(streamId)
|
||||
}
|
||||
|
|
@ -733,6 +751,8 @@ public class StreamableHttpServerTransport(private val configuration: Configurat
|
|||
retry = configuration.retryInterval?.inWholeMilliseconds,
|
||||
data = "",
|
||||
)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
_onError(e)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue