From 56b5d3f0b0d53849fe7e5bb7046d0c6effd0dc0b Mon Sep 17 00:00:00 2001 From: be-nj Date: Wed, 26 Aug 2026 10:33:39 +0200 Subject: [PATCH] Stop the socket timeout from killing the remote mid-session MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #1's fix gave accepted sockets a read deadline so idle connections could no longer pin threads — but NanoHTTPD's 5 s default also applies to the long-lived WebSocket, and pings only ran every 8 s. The remote was therefore dropped roughly five seconds into every session, right after a command or two, with no close frame. The deadline stays (that was the point) but is now 40 s, comfortably above the ping interval, so pongs keep an active session alive while a truly idle socket still gets reaped. Co-Authored-By: Claude Fable 5 --- app/build.gradle.kts | 4 ++-- app/src/main/java/dev/castarr/tv/server/ControlServer.kt | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8e58283..4a2303b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -12,8 +12,8 @@ android { applicationId = "dev.castarr.tv" minSdk = 26 targetSdk = 35 - versionCode = 34 - versionName = "0.11.1" + versionCode = 35 + versionName = "0.11.2" } // Release signing from environment (see ~/.keys/castarr-release.env on the diff --git a/app/src/main/java/dev/castarr/tv/server/ControlServer.kt b/app/src/main/java/dev/castarr/tv/server/ControlServer.kt index 42c2b16..4c4d99c 100644 --- a/app/src/main/java/dev/castarr/tv/server/ControlServer.kt +++ b/app/src/main/java/dev/castarr/tv/server/ControlServer.kt @@ -97,7 +97,12 @@ class ControlServer( fun startServer() { // A busy port must not take the whole app down — the remote is // optional, everything else keeps working. - running = runCatching { start(NanoHTTPD.SOCKET_READ_TIMEOUT, true) } + // NanoHTTPD's 5 s default also applies to the long-lived WebSocket: + // with pings only every 8 s the socket timed out mid-session and the + // remote was thrown out after a few seconds. The timeout still has to + // exist (idle connections must not pin threads), it just has to be + // comfortably longer than the ping interval. + running = runCatching { start(SOCKET_TIMEOUT_MS, true) } .onFailure { Log.w(TAG, "control server unavailable: ${it.javaClass.simpleName}") } .isSuccess if (!running) return @@ -354,6 +359,7 @@ class ControlServer( private companion object { const val TAG = "ControlServer" const val PING_INTERVAL_MS = 8_000L + const val SOCKET_TIMEOUT_MS = 40_000 const val ATTEMPT_WINDOW_MS = 60_000L const val ATTEMPT_MAX = 5 const val MAX_TRACKED_ADDRESSES = 64