Close owned WebSocket clients after reconnects

This commit is contained in:
Sergey Chernov 2026-08-11 14:45:37 +04:00
parent 19b349143d
commit 271cdd18e3
2 changed files with 38 additions and 6 deletions

View File

@ -53,16 +53,18 @@ fun <S> websocketClient(
/**
* Create kilopaarsec transport over websocket (ws or wss).
* @param path websocket path (must start with ws:// or wss:// and contain a path part)
* @client use default [HttpClient], it installs [WebSockets] plugin
* @param client optional caller-owned client. When omitted, a client with the
* [WebSockets] plugin is created for this device and closed with it.
*/
fun websocketTransportDevice(
path: String,
useTextFrames: Boolean = false,
client: HttpClient = HttpClient {
install(WebSockets)
},
client: HttpClient? = null,
): Transport.Device {
val ownsClient = client == null
val actualClient = client ?: HttpClient { install(WebSockets) }
val log = LogTag("WSTD")
var u = Url(path)
log.debug { "Creating websocket transport device at $u" }
@ -82,7 +84,7 @@ fun websocketTransportDevice(
globalLaunch {
val log = LogTag("KC:${counter.incrementAndGet()}")
try {
client.webSocket({
actualClient.webSocket({
url.protocol = u.protocol
url.host = u.host
url.port = u.port
@ -148,6 +150,8 @@ fun websocketTransportDevice(
else log.warning { "unexpected IO error $x" }
runCatching { output.close() }
runCatching { input.close() }
} finally {
if (ownsClient) actualClient.close()
}
log.info { "closing connection" }
}
@ -162,4 +166,3 @@ fun websocketTransportDevice(
})
return device
}

View File

@ -0,0 +1,29 @@
package net.sergeych.kiloparsec.adapter
import java.nio.file.Files
import java.nio.file.Path
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlin.test.Test
import kotlin.test.assertTrue
class WebsocketClientResourceTest {
@Test
fun failedReconnectsDoNotLeakFileDescriptors() = runBlocking {
val descriptors = Path.of("/proc/self/fd")
if (!Files.isDirectory(descriptors)) return@runBlocking
val client = websocketClient<Unit>("ws://127.0.0.1:1/kp")
try {
delay(2_500)
val before = descriptorCount(descriptors)
delay(7_000)
val after = descriptorCount(descriptors)
assertTrue(after <= before + 3, "file descriptors grew from $before to $after")
} finally {
client.close()
}
}
private fun descriptorCount(path: Path): Long = Files.list(path).use { it.count() }
}