feat: ping-and-exit for server RTT, remove broken UDP ping
Some checks failed
Mirror to GitHub / mirror (push) Failing after 38s
Build Release Binaries / build-amd64 (push) Failing after 3m38s

- Ping button: pings all servers via native QUIC, saves RTT + fingerprint
  to SharedPreferences, then exits process (System.exit)
- On restart: loads saved ping results (no native .so loading needed)
- Avoids jemalloc crash: native lib only loaded once per process lifetime
- Removed broken UDP probe (QUIC servers don't respond to it)
- SettingsRepository: savePingRtt/loadPingRtt for cached results
- PingResult: added reachable field

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-07 09:31:02 +04:00
parent 00b405aa87
commit eeb85aeac2
4 changed files with 80 additions and 81 deletions

View File

@@ -31,7 +31,8 @@ data class ServerEntry(val address: String, val label: String)
data class PingResult(
val rttMs: Int,
val serverFingerprint: String,
val serverFingerprint: String = "",
val reachable: Boolean = rttMs > 0,
)
enum class LockStatus { UNKNOWN, OFFLINE, NEW, VERIFIED, CHANGED }
@@ -207,56 +208,61 @@ class CallViewModel : ViewModel(), WzpCallback {
settings?.saveSelectedServer(_selectedServer.value)
}
private var pingJob: Job? = null
/** Start periodic ping every 5 seconds. Safe to call multiple times. */
fun startPeriodicPing() {
if (pingJob?.isActive == true) return
pingJob = viewModelScope.launch {
while (isActive) {
pingAllServersOnce()
delay(5000)
/** Load saved ping results from last ping-and-exit cycle. */
fun loadSavedPingResults() {
val s = settings ?: return
val results = mutableMapOf<String, PingResult>()
val known = mutableMapOf<String, String>()
_servers.value.forEach { server ->
val rtt = s.loadPingRtt(server.address)
val fp = s.loadServerFingerprint(server.address)
if (rtt >= 0) {
results[server.address] = PingResult(rttMs = rtt, serverFingerprint = fp ?: "")
}
fp?.let { known[server.address] = it }
}
_pingResults.value = results
_knownFingerprints.value = known
}
/** Stop periodic ping. */
fun stopPeriodicPing() {
pingJob?.cancel()
pingJob = null
}
/** Ping all servers once (pure Kotlin UDP, no JNI). */
fun pingAllServersOnce() {
/**
* Ping all servers via native QUIC, save results, then exit process.
* On restart, saved results are loaded. This avoids the jemalloc crash
* by ensuring the native .so is only loaded once per process lifetime.
*/
fun pingAndExit() {
viewModelScope.launch {
val results = mutableMapOf<String, PingResult>()
_servers.value.forEach { server ->
val udpResult = withContext(Dispatchers.IO) {
val pr = withContext(Dispatchers.IO) {
com.wzp.net.RelayPinger.ping(server.address)
}
results[server.address] = PingResult(
rttMs = udpResult.rttMs,
serverFingerprint = "", // filled lazily on first real connection
rttMs = pr.rttMs,
serverFingerprint = pr.serverFingerprint,
)
}
_pingResults.value = results
// Load saved TOFU fingerprints
val known = mutableMapOf<String, String>()
_servers.value.forEach { server ->
settings?.loadServerFingerprint(server.address)?.let {
known[server.address] = it
// Save results
settings?.savePingRtt(server.address, pr.rttMs)
if (pr.serverFingerprint.isNotEmpty()) {
val saved = settings?.loadServerFingerprint(server.address)
if (saved == null) {
settings?.saveServerFingerprint(server.address, pr.serverFingerprint)
}
}
}
_knownFingerprints.value = known
_pingResults.value = results
// Exit process — next launch loads saved results, native .so reinits cleanly
delay(300) // let UI update
System.exit(0)
}
}
/** Get lock status for a server. */
fun lockStatus(address: String): LockStatus {
val pr = _pingResults.value[address] ?: return LockStatus.UNKNOWN
if (pr.rttMs <= 0 && pr.serverFingerprint.isEmpty()) return LockStatus.OFFLINE
if (!pr.reachable) return LockStatus.OFFLINE
val known = _knownFingerprints.value[address] ?: return LockStatus.NEW
if (pr.serverFingerprint.isEmpty()) return LockStatus.NEW // no fingerprint yet
if (pr.serverFingerprint.isEmpty()) return LockStatus.NEW
return if (pr.serverFingerprint == known) LockStatus.VERIFIED else LockStatus.CHANGED
}

View File

@@ -90,10 +90,8 @@ fun InCallScreen(
var showManageRelays by remember { mutableStateOf(false) }
// Pure Kotlin UDP ping — no native .so loading, safe on Android 16 MTE
LaunchedEffect(Unit) {
viewModel.startPeriodicPing()
}
// Load saved ping results from last ping-and-exit cycle
LaunchedEffect(Unit) { viewModel.loadSavedPingResults() }
Surface(
modifier = Modifier.fillMaxSize(),
@@ -229,6 +227,21 @@ fun InCallScreen(
)
}
Spacer(modifier = Modifier.height(8.dp))
// Ping button — pings all servers via native QUIC, saves results, exits app
OutlinedButton(
onClick = { viewModel.pingAndExit() },
modifier = Modifier.fillMaxWidth().height(40.dp),
shape = RoundedCornerShape(8.dp),
) {
Text(
"Ping Servers (restarts app)",
style = MaterialTheme.typography.labelSmall,
color = TextDim
)
}
errorMessage?.let { err ->
Spacer(modifier = Modifier.height(8.dp))
Text(text = err, color = Red, style = MaterialTheme.typography.bodySmall)