Commit 577aaa43a625946fa096e70cef829f7c7861ab67
1 parent
e5d6614f
fix(lifelog/summary): 修 worker 并发把 llama-server 搞 502
实测:periodic + one-shot + RETRY 死循环并发打同一个 2b 上游 → llama-swap proxy context-canceled → 502,纪要永远生成不出来(console 卡「运行中」)。 - 进程内 AtomicBoolean 互斥:同时只跑一个 worker body(enqueueUniqueWork 只能去重同名 one-shot,挡不住 periodic)。 - 单轮时间预算 7min:超了停、剩余会话下轮继续,避开 WorkManager ~10min 执行上限被中途 kill(→ context canceled → 502)。 - 失败不再 Result.retry():未处理会话靠下一轮 periodic / 手动重跑捡, 不再 hammering。 验证:单 worker 跑 56 段会话,llama-swap 返 200,本地 2B 纪要落库。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Showing
1 changed file
with
28 additions
and
1 deletions
lifelog-recorder/app/src/main/kotlin/com/lemon/lifelog/summary/SummaryWorker.kt
| ... | ... | @@ -51,6 +51,13 @@ class SummaryWorker( |
| 51 | 51 | * 端点/model 由 ChatConfig 解析(与对话共用配置)。 */ |
| 52 | 52 | const val KEY_BACKEND = "summary_backend" |
| 53 | 53 | |
| 54 | + /** 防并发:periodic + one-shot + RETRY 可能同时触发,多个 worker 并发打同一个 | |
| 55 | + * llama-server 会把上游搞 502(实测)。进程内 flag 保证同时只跑一个 worker body。 */ | |
| 56 | + private val isRunning = java.util.concurrent.atomic.AtomicBoolean(false) | |
| 57 | + | |
| 58 | + /** 单轮时间预算:超了就停、剩余会话下轮继续,避开 WorkManager ~10min 执行上限。 */ | |
| 59 | + const val RUN_BUDGET_MS = 7 * 60 * 1000L | |
| 60 | + | |
| 54 | 61 | /** 纯函数:把按时间排好序的段切成若干会话。 */ |
| 55 | 62 | fun groupSessions(segments: List<Segment>, gapMs: Long): List<List<Segment>> { |
| 56 | 63 | val out = mutableListOf<MutableList<Segment>>() |
| ... | ... | @@ -71,6 +78,19 @@ class SummaryWorker( |
| 71 | 78 | private val shortFmt = SimpleDateFormat("MM-dd HH:mm", Locale.US) |
| 72 | 79 | |
| 73 | 80 | override suspend fun doWork(): Result { |
| 81 | + if (!isRunning.compareAndSet(false, true)) { | |
| 82 | + Log.i(TAG, "another run in progress, skip") | |
| 83 | + return Result.success() | |
| 84 | + } | |
| 85 | + return try { | |
| 86 | + runSessions() | |
| 87 | + } finally { | |
| 88 | + isRunning.set(false) | |
| 89 | + } | |
| 90 | + } | |
| 91 | + | |
| 92 | + private suspend fun runSessions(): Result { | |
| 93 | + val runStart = System.currentTimeMillis() | |
| 74 | 94 | val now = inputData.getLong(INPUT_NOW_TS, System.currentTimeMillis()) |
| 75 | 95 | val prefs = applicationContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE) |
| 76 | 96 | val gapMs = inputData.getLong(INPUT_GAP_MS, 0L).takeIf { it > 0 } |
| ... | ... | @@ -111,6 +131,11 @@ class SummaryWorker( |
| 111 | 131 | // 已总结过(时间范围重叠)跳过。 |
| 112 | 132 | if (existing.any { it.windowStartTs <= end && it.windowEndTs >= start }) { skipped++; continue } |
| 113 | 133 | |
| 134 | + if (System.currentTimeMillis() - runStart > RUN_BUDGET_MS) { | |
| 135 | + SummaryProgress.line("⏸ 本轮时间到,剩余会话下次继续") | |
| 136 | + break | |
| 137 | + } | |
| 138 | + | |
| 114 | 139 | val userPrompt = buildPrompt(repo, session, speakerNames, start, end) |
| 115 | 140 | if (userPrompt == null) { skipped++; continue } // 整段无有效转写 |
| 116 | 141 | |
| ... | ... | @@ -142,7 +167,9 @@ class SummaryWorker( |
| 142 | 167 | |
| 143 | 168 | SummaryProgress.finish("完成:$made 篇生成,$skipped 个会话跳过(共 ${sessions.size})") |
| 144 | 169 | Log.i(TAG, "done: ${sessions.size} sessions, $made summarized (gap=${gapMs / 60_000}min)") |
| 145 | - return if (anyFailed && made == 0) Result.retry() else Result.success() | |
| 170 | + // 不 retry:失败/未处理的会话下一轮 periodic 或手动「现在跑一次」会重新捡, | |
| 171 | + // 避免 502 后 WorkManager 死循环 hammering。 | |
| 172 | + return Result.success() | |
| 146 | 173 | } |
| 147 | 174 | |
| 148 | 175 | /** 拼 prompt;若会话内没有任何有效转写返回 null。 */ | ... | ... |