diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/network/DesktopHttpClient.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/network/DesktopHttpClient.kt index 6f4c290668..ea31213db2 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/network/DesktopHttpClient.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/network/DesktopHttpClient.kt @@ -154,6 +154,12 @@ class DesktopHttpClient( private val simpleClient: OkHttpClient by lazy { OkHttpClient .Builder() + // Direct relay sockets opened before setInstance() should + // get the same TCP_NODELAY as directClient — see + // TcpNoDelaySocketFactory. (failClosedClient is SOCKS, and + // OkHttp bypasses the socket factory for SOCKS proxies, so + // it doesn't need this.) + .socketFactory(TcpNoDelaySocketFactory) .connectTimeout(BASE_TIMEOUT_SECONDS, TimeUnit.SECONDS) .readTimeout(BASE_TIMEOUT_SECONDS, TimeUnit.SECONDS) .writeTimeout(BASE_TIMEOUT_SECONDS, TimeUnit.SECONDS) diff --git a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/relay/sockets/okhttp/TcpNoDelaySocketFactory.kt b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/relay/sockets/okhttp/TcpNoDelaySocketFactory.kt index 2f175a3b3b..6f581eb893 100644 --- a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/relay/sockets/okhttp/TcpNoDelaySocketFactory.kt +++ b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/relay/sockets/okhttp/TcpNoDelaySocketFactory.kt @@ -47,12 +47,31 @@ import javax.net.SocketFactory object TcpNoDelaySocketFactory : SocketFactory() { private fun socket() = Socket().apply { tcpNoDelay = true } + /** + * Runs [block] on a fresh NODELAY socket, closing it if the + * bind/connect throws — the JDK's connecting `Socket(...)` + * constructors do this, but our `Socket().apply { connect(...) }` + * form would otherwise leak the descriptor on a failed connect. + * (OkHttp only calls the no-arg overload, so this guards the other + * overloads for any direct caller.) + */ + private inline fun connecting(block: (Socket) -> Unit): Socket { + val s = socket() + try { + block(s) + } catch (t: Throwable) { + runCatching { s.close() } + throw t + } + return s + } + override fun createSocket(): Socket = socket() override fun createSocket( host: String?, port: Int, - ): Socket = socket().apply { connect(InetSocketAddress(host, port)) } + ): Socket = connecting { it.connect(InetSocketAddress(host, port)) } override fun createSocket( host: String?, @@ -60,15 +79,15 @@ object TcpNoDelaySocketFactory : SocketFactory() { localHost: InetAddress?, localPort: Int, ): Socket = - socket().apply { - bind(InetSocketAddress(localHost, localPort)) - connect(InetSocketAddress(host, port)) + connecting { + it.bind(InetSocketAddress(localHost, localPort)) + it.connect(InetSocketAddress(host, port)) } override fun createSocket( host: InetAddress?, port: Int, - ): Socket = socket().apply { connect(InetSocketAddress(host, port)) } + ): Socket = connecting { it.connect(InetSocketAddress(host, port)) } override fun createSocket( address: InetAddress?, @@ -76,8 +95,8 @@ object TcpNoDelaySocketFactory : SocketFactory() { localAddress: InetAddress?, localPort: Int, ): Socket = - socket().apply { - bind(InetSocketAddress(localAddress, localPort)) - connect(InetSocketAddress(address, port)) + connecting { + it.bind(InetSocketAddress(localAddress, localPort)) + it.connect(InetSocketAddress(address, port)) } }