Commit adc79e54c5f65914c668d6d2e7f0ea6f1b4b7cd4

Authored by yibo
1 parent 0fb3a95c

fix(lifelog): BlockProcessor 崩溃自隔离,防毒块死循环崩 app

某些块的音频会让 sherpa-onnx 在 native 层 SIGSEGV(实测重处理历史时一个 12.8s
正常音频块触发,原因在 native 不可控),把整个进程(含 UI)带崩——表现为「一打开
录音页就闪退」,且重启后 listPending 又排到同一毒块,死循环崩。

native SIGSEGV 无法 try/catch,改用文件标记防御:处理前写 .attempt;若启动时标记
仍在(= 上次崩在这块、没走到删目录),把该块移到 blocks_quarantine/ 跳过。一块最多
崩一次即被隔离,其余正常处理,app 不再死循环。隔离块留存可后续排查。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
lifelog-recorder/app/src/main/kotlin/com/lemon/lifelog/service/BlockProcessor.kt
@@ -34,6 +34,22 @@ class BlockProcessor( @@ -34,6 +34,22 @@ class BlockProcessor(
34 private val clusterer: SpeakerClusterer, 34 private val clusterer: SpeakerClusterer,
35 ) { 35 ) {
36 suspend fun process(dir: File) { 36 suspend fun process(dir: File) {
  37 + // 崩溃自隔离:处理前写 .attempt 标记;若启动时发现标记还在(= 上次处理它时进程在
  38 + // native 层崩了,没走到删目录),说明这是「毒块」——移到 blocks_quarantine/ 跳过,
  39 + // 避免每次重启都崩在同一块上死循环。native SIGSEGV 无法 try/catch,只能这样防御。
  40 + val marker = File(dir, ".attempt")
  41 + if (marker.exists()) {
  42 + val q = File(dir.parentFile?.parentFile, "blocks_quarantine").apply { mkdirs() }
  43 + val dest = File(q, dir.name)
  44 + Log.w(TAG, "block ${dir.name} crashed last attempt → quarantine")
  45 + withContext(Dispatchers.IO) {
  46 + dest.deleteRecursively()
  47 + if (!dir.renameTo(dest)) { dir.copyRecursively(dest, overwrite = true); dir.deleteRecursively() }
  48 + }
  49 + return
  50 + }
  51 + withContext(Dispatchers.IO) { runCatching { marker.createNewFile() } }
  52 +
37 val loaded = withContext(Dispatchers.IO) { BlockBuffer.load(dir) } 53 val loaded = withContext(Dispatchers.IO) { BlockBuffer.load(dir) }
38 if (loaded == null || loaded.samples.isEmpty()) { 54 if (loaded == null || loaded.samples.isEmpty()) {
39 withContext(Dispatchers.IO) { dir.deleteRecursively() } 55 withContext(Dispatchers.IO) { dir.deleteRecursively() }