android: the diagnostics row «Russian sites» no longer shows a stale verdict: the screen waited a fixed 2.5 s for the direct probe round and then read whatever the last round had left, and a round with a timed-out foreign probe takes 6.7 s or more, so a «do not open» recorded during a network outage was shown minutes later on a working network; it now waits for a round that finished after the request (up to 8 s) and shows «unknown» otherwise; the «Direct round» line goes to diag.log; the report gives its checks 15 s instead of 10

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
DenozordecandClaude Fable 5.2 committed 2026-11-13 22:41:14 +07:00
1 parent 76000dfc49
commit 72de26d732
3 files changed
+32 -8

No files matched your search

@@ -38,11 +38,14 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.withTimeoutOrNull
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import org.strongswan.android.logic.DiagLog
import org.strongswan.android.logic.VpnStateService import org.strongswan.android.logic.VpnStateService
import org.strongswan.android.logic.events.Events import org.strongswan.android.logic.events.Events
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@@ -351,6 +354,23 @@ class WhiteListManager private constructor(private val context: Context) : VpnSt
scope.launch { runCheck(force = true) } scope.launch { runCheck(force = true) }
} }
/**
* Runs a probe round and waits for its result: the state of a round that
* finished after this call, or null when none did within [timeoutMs]
* (checks disabled, or the round is slower). A round already in flight
* counts, so a busy cycle does not cost the whole timeout for nothing.
*/
suspend fun checkFresh(timeoutMs: Long): WhiteListState?
{
if (!running || !config.enabled)
{
return null
}
val before = _state.value.lastCheckMs
checkNow()
return withTimeoutOrNull(timeoutMs) { _state.first { it.lastCheckMs != before } }
}
override fun stateChanged() override fun stateChanged()
{ {
handler.post { onStateChanged() } handler.post { onStateChanged() }
@@ -434,8 +454,11 @@ class WhiteListManager private constructor(private val context: Context) : VpnSt
val foreignLog = round.foreignOks.mapIndexed { index, ok -> val foreignLog = round.foreignOks.mapIndexed { index, ok ->
"${targets.getOrNull(index)?.host ?: index}=${ok ?: "?"}/${round.foreignRttMs.getOrNull(index) ?: "-"}ms" "${targets.getOrNull(index)?.host ?: index}=${ok ?: "?"}/${round.foreignRttMs.getOrNull(index) ?: "-"}ms"
} }
Log.i(TAG, "Direct round: ${foreignLog.joinToString(" ")}, " + val roundLog = "Direct round: ${foreignLog.joinToString(" ")}, " +
"domestic=${round.domesticOk}/${round.domesticRttMs ?: "-"}ms -> $candidate") "domestic=${round.domesticOk}/${round.domesticRttMs ?: "-"}ms -> $candidate"
Log.i(TAG, roundLog)
/* also in the report: tells a stale verdict from a real failure */
DiagLog.i(TAG, roundLog)
val before = machine val before = machine
machine = before.advance(candidate) machine = before.advance(candidate)
publish(round) publish(round)
@@ -128,8 +128,8 @@ class ReportViewModel(app: Application) : AndroidViewModel(app)
val note = comment val note = comment
val diag = attachDiag val diag = attachDiag
job = viewModelScope.launch { job = viewModelScope.launch {
/* the same checks as the Diagnostics screen (~3 s); a hang must not block the report */ /* the same checks as the Diagnostics screen (1-8 s); a hang must not block the report */
val checks = if (!diag) null else withTimeoutOrNull(10_000) { val checks = if (!diag) null else withTimeoutOrNull(15_000) {
runCatching { runDiagnostics(app) }.getOrNull()?.steps?.map { ReportContent.Check(it.title, it.value, it.ok) } runCatching { runDiagnostics(app) }.getOrNull()?.steps?.map { ReportContent.Check(it.title, it.value, it.ok) }
} }
val result = withContext(Dispatchers.IO) { val result = withContext(Dispatchers.IO) {
@@ -97,13 +97,14 @@ internal suspend fun runDiagnostics(context: Context): DiagReport = coroutineSco
val vpnUp = vpn?.state == VpnStateService.State.CONNECTED val vpnUp = vpn?.state == VpnStateService.State.CONNECTED
val gateway = (vpn?.profile)?.gateway val gateway = (vpn?.profile)?.gateway
val whiteList = WhiteListManager.getInstance(context) val whiteList = WhiteListManager.getInstance(context)
whiteList.checkNow()
val server = async { if (gateway != null) ConnectivityProbe.probe(ProbeTarget.SERVER, 3000, context, gateway) else null } val server = async { if (gateway != null) ConnectivityProbe.probe(ProbeTarget.SERVER, 3000, context, gateway) else null }
val foreign = async { ConnectivityProbe.probe(ProbeTarget.GOOGLE, 3000, context, null) } val foreign = async { ConnectivityProbe.probe(ProbeTarget.GOOGLE, 3000, context, null) }
val network = withContext(Dispatchers.Default) { networkLabel(context) } val network = withContext(Dispatchers.Default) { networkLabel(context) }
/* the whitelist check runs its own direct round; give it a moment */ /* the whitelist check runs its own direct round: wait for that very round, a
delay(2500) * verdict left from an earlier one (say, a network outage) is not shown.
val wl = whiteList.state.value * 8 s covers a round whose foreign probes time out and are retried. */
val fresh = whiteList.checkFresh(8000)
val wl = fresh ?: whiteList.state.value.copy(domesticDirectOk = null, domesticDirectRttMs = null)
val serverResult = server.await() val serverResult = server.await()
val foreignResult = foreign.await() val foreignResult = foreign.await()
val steps = listOf( val steps = listOf(