From 8558b210c361bf10023b962d86b28f6d86c57935 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 15 May 2026 08:26:21 +0300 Subject: [PATCH] Wait for Android VPN runtime before recovery probes --- clients/android/app/build.gradle | 4 +- .../su/cin/rapvpn/RapDiagnosticService.java | 37 +++++++++++++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/clients/android/app/build.gradle b/clients/android/app/build.gradle index 5545f1a..b4f89c3 100644 --- a/clients/android/app/build.gradle +++ b/clients/android/app/build.gradle @@ -30,8 +30,8 @@ android { applicationId "su.cin.rapvpn" minSdk 26 targetSdk 35 - versionCode 186 - versionName "0.2.186" + versionCode 187 + versionName "0.2.187" buildConfigField "String", "DEFAULT_BACKEND_URL", "\"${normalizeGradleString(defaultBackendUrl)}\"" buildConfigField "String", "DEFAULT_CLUSTER_ID", "\"${normalizeGradleString(defaultClusterId)}\"" buildConfigField "String", "DEFAULT_ORGANIZATION_ID", "\"${normalizeGradleString(defaultOrganizationId)}\"" diff --git a/clients/android/app/src/main/java/su/cin/rapvpn/RapDiagnosticService.java b/clients/android/app/src/main/java/su/cin/rapvpn/RapDiagnosticService.java index b66ba0f..801c682 100644 --- a/clients/android/app/src/main/java/su/cin/rapvpn/RapDiagnosticService.java +++ b/clients/android/app/src/main/java/su/cin/rapvpn/RapDiagnosticService.java @@ -73,6 +73,7 @@ public class RapDiagnosticService extends Service { private static final int RECOVERY_PAGE_TIMEOUT_MS = 25000; private static final int RECOVERY_DOWNLOAD_CONNECT_TIMEOUT_MS = 8000; private static final int RECOVERY_DOWNLOAD_READ_TIMEOUT_MS = 12000; + private static final int RECOVERY_RUNTIME_READY_TIMEOUT_MS = 9000; private volatile boolean running; private Thread worker; private Thread supervisor; @@ -483,13 +484,13 @@ public class RapDiagnosticService extends Service { if (isRecoverableVPNProbe(type) && looksLikeVPNStall(result)) { String firstResult = result; String recovery = controlledRestartVPNRuntime(client, clusterId); - Thread.sleep(2500); + String ready = waitForVPNRuntimeReady(RECOVERY_RUNTIME_READY_TIMEOUT_MS); String recoveryRetry = runVPNProbeCommand(type, recoveryProbePayload(type, params), true); if (!looksLikeVPNStall(recoveryRetry)) { - result = firstResult + " | recovery=" + recovery + " | recovery_retry=" + recoveryRetry; + result = firstResult + " | recovery=" + recovery + " | ready=" + ready + " | recovery_retry=" + recoveryRetry; } else { Thread.sleep(1500); - result = firstResult + " | recovery=" + recovery + " | recovery_retry=" + recoveryRetry + " | final_retry=" + runVPNProbeCommand(type, recoveryProbePayload(type, params), true); + result = firstResult + " | recovery=" + recovery + " | ready=" + ready + " | recovery_retry=" + recoveryRetry + " | final_retry=" + runVPNProbeCommand(type, recoveryProbePayload(type, params), true); } } lastCommandType = type; @@ -731,6 +732,36 @@ public class RapDiagnosticService extends Service { } } + private String waitForVPNRuntimeReady(int timeoutMs) { + SharedPreferences runtime = getSharedPreferences(RUNTIME_PREFS, MODE_PRIVATE); + long deadline = System.currentTimeMillis() + Math.max(1000, timeoutMs); + String last = "unknown"; + while (System.currentTimeMillis() < deadline) { + String state = runtime.getString("state", ""); + String senderState = runtime.getString("uplink_sender_state", ""); + boolean senderAlive = runtime.getBoolean("uplink_sender_thread_alive", false); + boolean downlinkAlive = runtime.getBoolean("downlink_thread_alive", false); + long updatedAt = runtime.getLong("updated_at", 0); + long age = updatedAt <= 0 ? Long.MAX_VALUE : System.currentTimeMillis() - updatedAt; + last = state + "/sender=" + senderState + "/age_ms=" + age; + boolean readyState = "downlink".equals(state) + || "downlink_idle".equals(state) + || "uplink_sent".equals(state) + || "uplink_read".equals(state) + || "runtime_recovery".equals(state); + if (readyState && senderAlive && downlinkAlive && age < 3000) { + return "runtime_ready " + last; + } + try { + Thread.sleep(250); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return "runtime_ready interrupted " + last; + } + } + return "runtime_ready timeout " + last; + } + private void maybeRestartVPNAfterAppUpgrade(RapApiClient client, String clusterId, SharedPreferences prefs) { String lastVersion = prefs.getString("vpn_runtime_app_version", ""); if (APP_VERSION.equals(lastVersion)) {