mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-06 03:18:23 +00:00
Reduce battery drain from relay reconnects and websocket pings
Stop retrying permanently dead relays: track per-relay consecutive connection failures and stop scheduling reconnects once a relay exceeds MAX_RECONNECT_ATTEMPTS. The streak resets on a successful connection, and dead relays still get a fresh chance on the next network change since the ConnectivityService callback calls client.connect() directly. Previously a single unreachable relay kept re-triggering the global reconnect every 60s forever, waking the radio. Increase the OkHttp websocket ping interval from 30s to 90s so the always-on relay connection wakes the radio less often. https://claude.ai/code/session_01SLv8ZEooFiXErAvpARtz2Y
This commit is contained in:
@@ -46,8 +46,13 @@ object HttpClientManager {
|
||||
val DEFAULT_TIMEOUT_ON_WIFI: Duration = Duration.ofSeconds(10L)
|
||||
val DEFAULT_TIMEOUT_ON_MOBILE: Duration = Duration.ofSeconds(30L)
|
||||
|
||||
/** How often OkHttp should send WebSocket pings to detect half-closed connections. */
|
||||
private val PING_INTERVAL: Duration = Duration.ofSeconds(30L)
|
||||
/**
|
||||
* How often OkHttp should send WebSocket pings to detect half-closed connections.
|
||||
* Kept relatively long to avoid waking the radio every few seconds on the
|
||||
* always-on relay connection; it only needs to be short enough to surface a
|
||||
* silent half-close as an explicit failure within a reasonable window.
|
||||
*/
|
||||
private val PING_INTERVAL: Duration = Duration.ofSeconds(90L)
|
||||
|
||||
private var defaultTimeout = DEFAULT_TIMEOUT_ON_WIFI
|
||||
private var defaultHttpClient: OkHttpClient? = null
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.OkMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.EventCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -55,6 +56,28 @@ class NostrClientLoggerListener(
|
||||
private var reconnectDelay = 5_000L
|
||||
private var lastDisconnectTime = 0L
|
||||
|
||||
// Per-relay consecutive connection-failure counts. A relay that fails to
|
||||
// connect MAX_RECONNECT_ATTEMPTS times in a row is treated as permanently
|
||||
// dead and stops scheduling reconnects, so an unreachable relay can't keep
|
||||
// waking the radio every minute forever and draining the battery. The count
|
||||
// is reset whenever the relay connects successfully (onConnected), and a dead
|
||||
// relay still gets a fresh chance on the next OS network change, because the
|
||||
// ConnectivityService network callback calls client.connect(), which retries
|
||||
// every relay regardless of this backoff state.
|
||||
private val failureCounts = ConcurrentHashMap<String, Int>()
|
||||
|
||||
private fun scheduleReconnect(relayUrl: String) {
|
||||
val failures = (failureCounts[relayUrl] ?: 0) + 1
|
||||
failureCounts[relayUrl] = failures
|
||||
if (failures > MAX_RECONNECT_ATTEMPTS) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(Amber.TAG, "Relay $relayUrl marked dead after $failures failed attempts; skipping reconnect")
|
||||
}
|
||||
return
|
||||
}
|
||||
reconnectWithBackoff()
|
||||
}
|
||||
|
||||
private fun reconnectWithBackoff() {
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastDisconnectTime > 60_000) {
|
||||
@@ -115,7 +138,7 @@ class NostrClientLoggerListener(
|
||||
return
|
||||
}
|
||||
|
||||
reconnectWithBackoff()
|
||||
scheduleReconnect(relay.url.url)
|
||||
super.onCannotConnect(relay, errorMessage)
|
||||
}
|
||||
|
||||
@@ -166,7 +189,7 @@ class NostrClientLoggerListener(
|
||||
return
|
||||
}
|
||||
|
||||
reconnectWithBackoff()
|
||||
scheduleReconnect(relay.url.url)
|
||||
super.onDisconnected(relay)
|
||||
}
|
||||
|
||||
@@ -179,6 +202,17 @@ class NostrClientLoggerListener(
|
||||
override fun onConnected(relay: IRelayClient, pingMillis: Int, compressed: Boolean) {
|
||||
if (BuildConfig.DEBUG) Log.d(Amber.TAG, "onConnected: ${relay.url.url} ping: ${pingMillis}ms compressed: $compressed")
|
||||
saveLog(relay.url.url, "onConnected", "Connected")
|
||||
// Relay recovered: clear its failure streak so it is eligible for the
|
||||
// normal reconnect-with-backoff path again.
|
||||
failureCounts.remove(relay.url.url)
|
||||
super.onConnected(relay, pingMillis, compressed)
|
||||
}
|
||||
|
||||
companion object {
|
||||
// After this many consecutive failures a relay is considered permanently
|
||||
// dead and is no longer scheduled for reconnection until it recovers on a
|
||||
// network change. With the 60s backoff cap this is roughly 10 minutes of
|
||||
// retrying before giving up.
|
||||
private const val MAX_RECONNECT_ATTEMPTS = 10
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user