fix: relay connectivity degradation with Tor by adding WebSocket pings, pool eviction, and reducing backoff

Dead WebSocket connections through Tor were going undetected because no
ping interval was set, leaving zombie connections that appeared connected
but carried no traffic. Additionally, relay error backoff was set to
ONE_DAY making recovery impossible without toggling Tor, and the shared
OkHttp connection pool retained stale connections across proxy changes.

- Add 120s WebSocket ping interval to detect dead Tor connections
- Reduce dontTryAgainForALongTime from ONE_DAY to FIVE_MINUTES
- Evict shared connection pool when proxy settings change

https://claude.ai/code/session_01VFAypytKGzdmuoJAXrb72G
This commit is contained in:
Claude
2026-04-11 02:03:23 +00:00
parent 9bf628b823
commit 8cc198d161
3 changed files with 15 additions and 1 deletions
@@ -46,10 +46,16 @@ class OkHttpClientFactory(
.addNetworkInterceptor(keyDecryptor) .addNetworkInterceptor(keyDecryptor)
.build() .build()
private var lastProxy: Proxy? = null
fun buildHttpClient( fun buildHttpClient(
proxy: Proxy?, proxy: Proxy?,
timeoutSeconds: Int, timeoutSeconds: Int,
): OkHttpClient { ): OkHttpClient {
if (proxy != lastProxy) {
rootClient.connectionPool.evictAll()
lastProxy = proxy
}
val seconds = if (proxy != null) timeoutSeconds * 3 else timeoutSeconds val seconds = if (proxy != null) timeoutSeconds * 3 else timeoutSeconds
return rootClient return rootClient
.newBuilder() .newBuilder()
@@ -37,6 +37,7 @@ class OkHttpClientFactoryForRelays(
const val DEFAULT_IS_MOBILE: Boolean = false const val DEFAULT_IS_MOBILE: Boolean = false
const val DEFAULT_TIMEOUT_ON_WIFI_SECS: Int = 10 const val DEFAULT_TIMEOUT_ON_WIFI_SECS: Int = 10
const val DEFAULT_TIMEOUT_ON_MOBILE_SECS: Int = 30 const val DEFAULT_TIMEOUT_ON_MOBILE_SECS: Int = 30
const val WEBSOCKET_PING_INTERVAL_SECS: Long = 120
private fun isEmulator(): Boolean = private fun isEmulator(): Boolean =
Build.FINGERPRINT.startsWith("generic") || Build.FINGERPRINT.startsWith("generic") ||
@@ -76,10 +77,16 @@ class OkHttpClientFactoryForRelays(
.addInterceptor(DefaultContentTypeInterceptor(userAgent)) .addInterceptor(DefaultContentTypeInterceptor(userAgent))
.build() .build()
private var lastProxy: Proxy? = null
fun buildHttpClient( fun buildHttpClient(
proxy: Proxy?, proxy: Proxy?,
timeoutSeconds: Int, timeoutSeconds: Int,
): OkHttpClient { ): OkHttpClient {
if (proxy != lastProxy) {
rootClient.connectionPool.evictAll()
lastProxy = proxy
}
val seconds = if (proxy != null) timeoutSeconds * 3 else timeoutSeconds val seconds = if (proxy != null) timeoutSeconds * 3 else timeoutSeconds
return rootClient return rootClient
.newBuilder() .newBuilder()
@@ -87,6 +94,7 @@ class OkHttpClientFactoryForRelays(
.connectTimeout(Duration.ofSeconds(seconds.toLong())) .connectTimeout(Duration.ofSeconds(seconds.toLong()))
.readTimeout(Duration.ofSeconds(seconds.toLong() * 3)) .readTimeout(Duration.ofSeconds(seconds.toLong() * 3))
.writeTimeout(Duration.ofSeconds(seconds.toLong() * 3)) .writeTimeout(Duration.ofSeconds(seconds.toLong() * 3))
.pingInterval(Duration.ofSeconds(WEBSOCKET_PING_INTERVAL_SECS))
.build() .build()
} }
@@ -228,7 +228,7 @@ open class BasicRelayClient(
} }
fun dontTryAgainForALongTime() { fun dontTryAgainForALongTime() {
delayToConnectInSeconds = TimeUtils.ONE_DAY delayToConnectInSeconds = TimeUtils.FIVE_MINUTES
} }
override fun sendOrConnectAndSync(cmd: Command) { override fun sendOrConnectAndSync(cmd: Command) {