From a5f9238183bdbbd4b57c8346b1d76d1351f37320 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Fri, 19 Jun 2026 13:23:37 -0500 Subject: [PATCH] fix(network): retry transient connection/IO failures to api.meshtastic.org (#5870) Co-authored-by: Claude Opus 4.8 --- .../org/meshtastic/app/di/NetworkModule.kt | 6 ++---- .../core/network/HttpClientDefaults.kt | 19 +++++++++++++++++-- .../desktop/di/DesktopKoinModule.kt | 6 ++---- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/androidApp/src/main/kotlin/org/meshtastic/app/di/NetworkModule.kt b/androidApp/src/main/kotlin/org/meshtastic/app/di/NetworkModule.kt index f4d90ec13..bd88dafb8 100644 --- a/androidApp/src/main/kotlin/org/meshtastic/app/di/NetworkModule.kt +++ b/androidApp/src/main/kotlin/org/meshtastic/app/di/NetworkModule.kt @@ -48,6 +48,7 @@ import org.koin.core.annotation.Single import org.meshtastic.core.common.BuildConfigProvider import org.meshtastic.core.network.HttpClientDefaults import org.meshtastic.core.network.KermitHttpLogger +import org.meshtastic.core.network.configureDefaultRetry private const val DISK_CACHE_PERCENT = 0.02 private const val MEMORY_CACHE_PERCENT = 0.25 @@ -104,10 +105,7 @@ class NetworkModule { connectTimeoutMillis = HttpClientDefaults.TIMEOUT_MS socketTimeoutMillis = HttpClientDefaults.TIMEOUT_MS } - install(plugin = HttpRequestRetry) { - retryOnServerErrors(maxRetries = HttpClientDefaults.MAX_RETRIES) - exponentialDelay() - } + install(plugin = HttpRequestRetry) { configureDefaultRetry() } if (buildConfigProvider.isDebug) { install(plugin = Logging) { logger = KermitHttpLogger diff --git a/core/network/src/commonMain/kotlin/org/meshtastic/core/network/HttpClientDefaults.kt b/core/network/src/commonMain/kotlin/org/meshtastic/core/network/HttpClientDefaults.kt index 87c317024..35694f87f 100644 --- a/core/network/src/commonMain/kotlin/org/meshtastic/core/network/HttpClientDefaults.kt +++ b/core/network/src/commonMain/kotlin/org/meshtastic/core/network/HttpClientDefaults.kt @@ -16,8 +16,10 @@ */ package org.meshtastic.core.network +import io.ktor.client.plugins.HttpRequestRetryConfig + /** - * Shared HTTP client configuration constants used by both Android and Desktop Ktor `HttpClient` setups. + * Shared HTTP client configuration used by both Android and Desktop Ktor `HttpClient` setups. * * These values are consumed by the platform-specific Koin modules (`NetworkModule` on Android, `DesktopKoinModule` on * Desktop) when installing [io.ktor.client.plugins.HttpTimeout] and [io.ktor.client.plugins.HttpRequestRetry]. @@ -26,9 +28,22 @@ object HttpClientDefaults { /** Timeout in milliseconds for connect, request, and socket operations. */ const val TIMEOUT_MS = 30_000L - /** Maximum number of automatic retries on server errors (5xx). */ + /** Maximum number of automatic retries on server errors (5xx) and transient connection/IO failures. */ const val MAX_RETRIES = 3 /** Base URL for the Meshtastic public API. Installed via the `DefaultRequest` plugin. */ const val API_BASE_URL = "https://api.meshtastic.org/" } + +/** + * Shared [io.ktor.client.plugins.HttpRequestRetry] policy for both engines. + * + * Retries on 5xx server errors and on transient connection/IO failures (dropped sockets, DNS blips, read timeouts) — + * the common failure mode on flaky cellular — with exponential backoff. [HttpClientDefaults.MAX_RETRIES] applies to + * both rules. + */ +fun HttpRequestRetryConfig.configureDefaultRetry() { + retryOnServerErrors(maxRetries = HttpClientDefaults.MAX_RETRIES) + retryOnException(maxRetries = HttpClientDefaults.MAX_RETRIES, retryOnTimeout = true) + exponentialDelay() +} diff --git a/desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt b/desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt index 3e7826074..06f70f534 100644 --- a/desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt +++ b/desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt @@ -44,6 +44,7 @@ import org.meshtastic.core.model.NetworkDeviceLink import org.meshtastic.core.model.NetworkFirmwareReleases import org.meshtastic.core.network.HttpClientDefaults import org.meshtastic.core.network.KermitHttpLogger +import org.meshtastic.core.network.configureDefaultRetry import org.meshtastic.core.network.repository.MQTTRepository import org.meshtastic.core.network.service.ApiService import org.meshtastic.core.network.service.ApiServiceImpl @@ -245,10 +246,7 @@ private fun desktopPlatformStubsModule() = module { connectTimeoutMillis = HttpClientDefaults.TIMEOUT_MS socketTimeoutMillis = HttpClientDefaults.TIMEOUT_MS } - install(HttpRequestRetry) { - retryOnServerErrors(maxRetries = HttpClientDefaults.MAX_RETRIES) - exponentialDelay() - } + install(HttpRequestRetry) { configureDefaultRetry() } if (DesktopBuildConfig.IS_DEBUG) { install(Logging) { logger = KermitHttpLogger