fix(stability): 코드베이스 진단 HIGH 이슈 6건 fix
병렬 진단 (BLE/thread + Compose lifecycle + null safety) 결과 HIGH
심각도 12건 중 crash 유발 가능성 높고 저비용 고효과인 것 우선 처리.
**H4 — coroutine leak**
- PiezoMonitoringView.kt BleDebugPanel: `while(true) { delay/refresh }` →
`while(isActive)`. LaunchedEffect 취소 후에도 refreshTick++ 이 계속
돌아 GC 방해.
- BladdyRiveView.kt 2곳: 동일 패턴 → isActive 로 통일.
**H1 — BleManager postDelayed 미취소**
- `fwFallbackTimer` (4초 mfv? fallback) + `cccdRetryTimer` (500ms CCCD
재시도) 를 Runnable 참조로 저장. disconnect / GATT_ERROR / watchdog
timeout 3곳에서 handler.removeCallbacks 로 명시적 취소.
- 이전에는 disconnect 후에도 4초 후 sendFirmwareVersionQuery() 가 발동
→ 이미 close 된 GATT 에 write → silent exception → state 오염.
**H2 — MeasurementService 콜백 leak**
- performNirsMeasurement/performPiezoMeasurement 는 singleton 에서
콜백 대입만 하고 정리 안 함. Self-clearing lambda 로 응답 1회 처리
후 자동 null. 시작 시 이전 stale 콜백도 clear.
- stop() 에서도 대기 중 콜백 취소.
**H3 — Watchdog race condition**
- watchdog thread 가 GATT disconnect/close + characteristic null 을
binder thread 에서 직접 실행 → UI thread 의 sendRaw 와 race.
- 정리 전부를 handler.post 로 UI thread 에 위임 → single-threaded.
- fwFallback/cccdRetry timer 도 여기서 함께 취소.
**H6 — PiezoMonitoringView measure() closure leak**
- DisposableEffect 에 piezoCollector.onMultiChannelComplete 정리 추가.
measure() 함수 안에서 이 콜백에 measureScope + channels + outer
state 를 다수 capture → 화면 이탈 후에도 GC 방해 + 재진입 시 stale
closure 가 새 상태 오염 위험.
**H9 — UrineCameraScreen NPE 위험**
- LaunchedEffect 안 while 루프에서 `currentDetection!!` 이 다른 recompose
가 detection 을 null 로 만들면 NPE. Local val snapshot 으로 fix.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -27,8 +27,13 @@ class MeasurementService private constructor(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 2026-07-07 fix: MeasurementService 는 singleton. 콜백을 대입만 하고 정리하지 않으면
|
||||
// BleManager 에 stale closure 가 계속 살아있어 이후 응답 이벤트가 이미 완료된
|
||||
// measurement session 의 completion() 을 다시 호출. Self-clearing lambda 로 처리.
|
||||
private fun performNirsMeasurement(completion: (Boolean) -> Unit) {
|
||||
bleManager.onNirsMcjReceived = null // 이전 stale 콜백 해제
|
||||
bleManager.onNirsMcjReceived = { _ ->
|
||||
bleManager.onNirsMcjReceived = null // 응답 1회 처리 후 자동 해제
|
||||
isMeasuring.value = false
|
||||
completion(true)
|
||||
}
|
||||
@@ -36,7 +41,9 @@ class MeasurementService private constructor(context: Context) {
|
||||
}
|
||||
|
||||
private fun performPiezoMeasurement(completion: (Boolean) -> Unit) {
|
||||
bleManager.onPiezoDataReceived = null // 이전 stale 콜백 해제
|
||||
bleManager.onPiezoDataReceived = { _ ->
|
||||
bleManager.onPiezoDataReceived = null // 응답 1회 처리 후 자동 해제
|
||||
isMeasuring.value = false
|
||||
completion(true)
|
||||
}
|
||||
@@ -45,6 +52,9 @@ class MeasurementService private constructor(context: Context) {
|
||||
|
||||
fun stop() {
|
||||
isMeasuring.value = false
|
||||
// 대기 중이던 콜백 취소.
|
||||
bleManager.onNirsMcjReceived = null
|
||||
bleManager.onPiezoDataReceived = null
|
||||
bleManager.sendPiezoStop()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user