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.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.withTimeoutOrNull
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import org.strongswan.android.logic.DiagLog
import org.strongswan.android.logic.VpnStateService
import org.strongswan.android.logic.events.Events
import java.util.concurrent.ConcurrentHashMap
@@ -351,6 +354,23 @@ class WhiteListManager private constructor(private val context: Context) : VpnSt
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()
{
handler.post { onStateChanged() }
@@ -434,8 +454,11 @@ class WhiteListManager private constructor(private val context: Context) : VpnSt
val foreignLog = round.foreignOks.mapIndexed { index, ok ->
"${targets.getOrNull(index)?.host ?: index}=${ok ?: "?"}/${round.foreignRttMs.getOrNull(index) ?: "-"}ms"
}
Log.i(TAG, "Direct round: ${foreignLog.joinToString(" ")}, " +
"domestic=${round.domesticOk}/${round.domesticRttMs ?: "-"}ms -> $candidate")
val roundLog = "Direct round: ${foreignLog.joinToString(" ")}, " +
"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
machine = before.advance(candidate)
publish(round)
@@ -128,8 +128,8 @@ class ReportViewModel(app: Application) : AndroidViewModel(app)
val note = comment
val diag = attachDiag
job = viewModelScope.launch {
/* the same checks as the Diagnostics screen (~3 s); a hang must not block the report */
val checks = if (!diag) null else withTimeoutOrNull(10_000) {
/* the same checks as the Diagnostics screen (1-8 s); a hang must not block the report */
val checks = if (!diag) null else withTimeoutOrNull(15_000) {
runCatching { runDiagnostics(app) }.getOrNull()?.steps?.map { ReportContent.Check(it.title, it.value, it.ok) }
}
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 gateway = (vpn?.profile)?.gateway
val whiteList = WhiteListManager.getInstance(context)
whiteList.checkNow()
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 network = withContext(Dispatchers.Default) { networkLabel(context) }
/* the whitelist check runs its own direct round; give it a moment */
delay(2500)
val wl = whiteList.state.value
/* the whitelist check runs its own direct round: wait for that very round, a
* verdict left from an earlier one (say, a network outage) is not shown.
* 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 foreignResult = foreign.await()
val steps = listOf(