如何确保ktor websocket客户端创建的所有Kotlin协同路由都已清除?
我正试着去思考Kotlin协同程序和Ktors websocket支持。我的理解是,如何确保ktor websocket客户端创建的所有Kotlin协同路由都已清除?,kotlin,kotlin-coroutines,ktor,Kotlin,Kotlin Coroutines,Ktor,我正试着去思考Kotlin协同程序和Ktors websocket支持。我的理解是,runBlocking将创建一个作用域,并且只要该作用域(或子作用域)中存在协同路由,它就会阻塞,但是我在下面的测试中调用runBlocking返回时,仍然有两个协同路由处于活动状态 我为什么在这里泄露协同程序 package dummy import io.ktor.client.HttpClient import io.ktor.client.features.websocket.WebSockets im
runBlocking
将创建一个作用域,并且只要该作用域(或子作用域)中存在协同路由,它就会阻塞,但是我在下面的测试中调用runBlocking
返回时,仍然有两个协同路由处于活动状态
我为什么在这里泄露协同程序
package dummy
import io.ktor.client.HttpClient
import io.ktor.client.features.websocket.WebSockets
import io.ktor.client.features.websocket.wss
import io.ktor.http.HttpMethod
import io.ktor.http.cio.websocket.Frame
import io.ktor.http.cio.websocket.readBytes
import io.ktor.http.cio.websocket.readText
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.*
import kotlinx.coroutines.debug.DebugProbes
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
@ExperimentalCoroutinesApi
@KtorExperimentalAPI
class WebsocketTest {
@Test
fun tidy() {
DebugProbes.install()
runBlocking {
val socketJob = Job()
launch(CoroutineName("Websocket") + socketJob) {
println("Connecting to websocket")
connectWebsocket(socketJob)
println("Websocket dead?")
}
launch(CoroutineName("Ninja socket killer")) {
delay(3500)
println("Killing websocket client")
socketJob.cancel(message = "Time to die..")
}
}
println("\n\n-------")
DebugProbes.dumpCoroutines(System.err)
Assertions.assertEquals(0, DebugProbes.dumpCoroutinesInfo().size, "It would be nice if all coroutines had been cleared up by now..")
}
}
@KtorExperimentalAPI
private suspend fun connectWebsocket(socketJob: CompletableJob) {
val client = HttpClient {
install(WebSockets)
}
socketJob.invokeOnCompletion {
println("Shutting down ktor http client")
client.close()
}
client.wss(
method = HttpMethod.Get,
host = "echo.websocket.org",
port = 443,
path = "/"
) {
send(Frame.Text("Hello World"))
for (frame in incoming) {
when (frame) {
is Frame.Text -> println(frame.readText())
is Frame.Binary -> println(frame.readBytes())
}
delay(1000)
send(Frame.Text("Hello World"))
}
}
}
build.gradle.kts
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
plugins {
kotlin("jvm") version "1.3.41" apply true
}
repositories {
mavenCentral()
}
val ktorVersion = "1.2.3"
val junitVersion = "5.5.1"
dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation("io.ktor:ktor-client-websockets:$ktorVersion")
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.0-RC2")
testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion")
}
tasks.withType<Test> {
useJUnitPlatform()
testLogging {
showExceptions = true
showStackTraces = true
exceptionFormat = TestExceptionFormat.FULL
events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
}
}
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
导入org.gradle.api.tasks.testing.logging.TestLogEvent
插件{
kotlin(“jvm”)版本“1.3.41”适用
}
存储库{
mavenCentral()
}
val ktorVersion=“1.2.3”
val junitVersion=“5.5.1”
依赖关系{
实施(kotlin(“stdlib-jdk8”))
实现(“io.ktor:ktor客户端WebSocket:$ktorVersion”)
实现(“io.ktor:ktor客户端okhttp:$ktorVersion”)
实现(“org.jetbrains.kotlinx:kotlinx协程调试:1.3.0-RC2”)
测试实现(“org.junit.jupiter:junit-jupiter-api:$junitVersion”)
testRuntimeOnly(“org.junit.jupiter:junit-jupiter引擎:$junitVersion”)
}
tasks.withType{
useJUnitPlatform()
测试记录{
showExceptions=true
showStackTraces=true
exceptionFormat=TestExceptionFormat.FULL
events=setOf(TestLogEvent.PASSED、TestLogEvent.SKIPPED、TestLogEvent.FAILED)
}
}
看来我已经明白了(显然是在我把头发扯下来,开始写这篇文章之后)。当我写这篇文章时,我泄露了两个协同程序,其中一个“自行解决”(我对此不太高兴,但无论我做什么,我都无法复制它)
第二个协同路由泄漏,因为Ktor中的Nonce.kt在GlobalScope中显式启动协同路由
刚刚发现了这个宝石:似乎有一个“泄露”的协同程序被明确地发布到了全局范围。
private val nonceGeneratorJob =
GlobalScope.launch(
context = Dispatchers.IO + NonCancellable + NonceGeneratorCoroutineName,
start = CoroutineStart.LAZY
) { ....