如果会话被tcp连接或peer关闭,则会抛出CancellationException,这对协程的流程有非常显着的影响,并且可能非常危险。
我期望抛出 ChannelClosedException 或类似的异常。
据我了解,当用户有明确意图时,通常会抛出 CancellationException:
现在,普通的发送函数会抛出它,那么是否还有更多这样的函数可能会抛出 CancellationException ?这会使程序的异常检查变得非常复杂。更有可能的是问题没有被认识到。
@Test
fun testChannelSendCatchCancellationException(): Unit = runBlocking {
val server = MockWebServer()
server.start()
val serverUrl = server.url("/").toString().replaceFirst("http", "ws")
println(serverUrl)
val response = MockResponse().withWebSocketUpgrade(object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
val request = server.takeRequest()
println(request)
webSocket.close(3000, "goodbye")
}
})
server.enqueue(response)
val session = HttpClient { install(WebSockets) }.webSocketSession(serverUrl)
launch {
try {
delay(500L)
try {
session.send(Frame.Text("Hello, world"))
// Some other codes need to call when success
} catch (e: Exception) {
// MY QUESTION: how to differentiate between a user's cancellation and a send failure?
// For withTimeout, it throws TimeoutCancellationException, differed from CancellationException
println("session tx caught: $e")
// Some other codes need to call when failed
}
} catch (e: CancellationException) {
println("tx coroutine caught cancellation: $e")
throw e
} catch (e: Exception) {
println("tx coroutine caught exception: $e")
// it prints: tx coroutine caught: java.util.concurrent.CancellationException: Channel was cancelled
}
}
delay(200L)
server.close()
delay(200L)
}
如果send抛出与CancellationException不同的异常,则更容易处理,否则代码非常冗余。因为我需要区分是用户取消,还是正常发送失败。
我将这个问题解释为如何处理抛出不属于协程框架一部分的 CancellationException 的情况。这是很有可能的,因为在 JVM 上,
kotlin.coroutines.cancellation.CancellationException
只是 java.util.concurrent.CancellationException
的类型别名。这可能被与协程无关的各种其他框架使用。
关于您的具体情况,请跳至最后一部分。
一般建议是将 Java 代码的 CancellationException 的错误处理与协程相关的错误处理隔离开来。这是可能的,因为 Kotlin 中的协程是合作的:如果您不希望您的协程被取消,没有什么可以阻止您忽略取消请求。
这意味着您必须主动检查取消请求,并且只有当您想满足取消请求时才应该抛出 CancellationException。例如,
delay
就是这样做的。因此,当您调用其他 suspend 函数时,它们可能会抛出 CancellationException ,表明它们想要配合并取消当前协程的执行。这是一个例外 - 本着合作的精神 - 你应该重新抛出。协程框架正常运行所必需的
另一方面,当 Java 函数(不挂起)抛出 CancellationException 时,该异常具有其他含义。您不需要重新抛出它,因为它并不意味着控制协程的流程。您甚至可能不会在协程中“成为”,因为 Java 函数无法通过将自身声明为挂起来强制执行这一点。这意味着您可以安全地捕获源自非挂起 Java 代码的 CancellationException。您甚至可以在协程中执行此操作,而不会冒捕获与协程相关的 CancellationException 的风险,因为后者只能从“挂起”函数中抛出,而您的非挂起 Java 函数则不然。
在 Ktor 的 WebSocketSession::send
ensureActive()
来配合协程取消,也可能是由关闭的 websocket 连接或完全不同的原因引起的。
关键是,你不知道。由于该函数被明确标记为挂起,因此唯一安全的途径是将异常解释为控制协程的方法。这意味着您不应该在不确定原因是什么的情况下捕获它(或者至少重新抛出它)。您唯一知道的是您无法发送帧并且当前的协程应该停止。 您可能不同意 Ktor 团队在连接关闭时抛出 CancellationException 的决定,但也有一些论据支持它。这是文档对此的说明: 如果传出通道已经关闭,则可能会抛出异常,因此无法传输任何消息。关闭帧后发送的帧可以被默默忽略。
如果将 websocket 调用封装在协程中,那么协程被取消应该没什么关系,重要的是要知道消息无法发送。当 websocket 连接仍然打开时,您可以简单地重试,启动一个新的协程。