fix: watchdog을 별도 Thread로 분리 (handler 블로킹 방지)

기존: handler.postDelayed로 watchdog 실행 → BLE 스택이 handler를
블로킹하면 watchdog도 멈춤 (좀비 상태 감지 불가)

변경: 별도 daemon Thread에서 5초마다 lastRxTimestamp 체크
→ BLE handler가 블로킹되어도 독립적으로 15초 타임아웃 감지
→ 감지 시 handler.post로 메인 스레드에서 forceDisconnectAndReconnect 실행

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 14:09:16 +09:00
parent 9d32d13452
commit 988fbd9dbd
@@ -352,31 +352,39 @@ class BleManager private constructor(private val context: Context) {
batteryTimer = null batteryTimer = null
} }
// ── Watchdog (zombie connection detection) ── // ── Watchdog (zombie connection detection, runs on separate thread) ──
private var watchdogJob: Thread? = null
@Volatile private var watchdogRunning = false
private fun startWatchdog() { private fun startWatchdog() {
stopWatchdog() stopWatchdog()
lastRxTimestamp = System.currentTimeMillis() lastRxTimestamp = System.currentTimeMillis()
val check = object : Runnable { watchdogRunning = true
override fun run() {
if (!isConnected.value) return watchdogJob = Thread({
while (watchdogRunning) {
try { Thread.sleep(5000) } catch (_: InterruptedException) { break }
if (!watchdogRunning) break
if (!isConnected.value) continue
val silenceMs = System.currentTimeMillis() - lastRxTimestamp val silenceMs = System.currentTimeMillis() - lastRxTimestamp
if (silenceMs > watchdogTimeoutMs) { if (silenceMs > watchdogTimeoutMs) {
logw { "Watchdog: no RX for ${silenceMs}ms — forcing disconnect + reconnect" } handler.post {
debugLogger.error("WATCHDOG_TIMEOUT silence=${silenceMs}ms — forcing reconnect") logw { "Watchdog: no RX for ${silenceMs}ms — forcing disconnect + reconnect" }
forceDisconnectAndReconnect() debugLogger.error("WATCHDOG_TIMEOUT silence=${silenceMs}ms — forcing reconnect")
return forceDisconnectAndReconnect()
}
break
} }
handler.postDelayed(this, 5000)
} }
} }, "BLE-Watchdog").also { it.isDaemon = true; it.start() }
watchdogTimer = check
handler.postDelayed(check, 5000)
} }
private fun stopWatchdog() { private fun stopWatchdog() {
watchdogTimer?.let { handler.removeCallbacks(it) } watchdogRunning = false
watchdogTimer = null watchdogJob?.interrupt()
watchdogJob = null
} }
private fun forceDisconnectAndReconnect() { private fun forceDisconnectAndReconnect() {