合并任务排队串行,不再互相顶掉进度和杀错 ffmpeg
审查发现:全局只有一份任务进度状态,但没有任何东西拦住两个合并同时跑。
1. 为什么必须串行
jobStart() 原来是无条件重置全部字段的。多个主播同时下播(很常见),
或者自动合并跑着的时候在页面上点一次手动合并,就会有两个任务同时跑:
后启动的把前一个的输出名、进度、已渲染时长全部清零;更糟的是 jobStop()
只认最后注册的那个 ffmpeg,点「停止」杀的是别人家任务的进程,
而自己那个继续跑;两个任务还会互相读到 stopped 标记而提前退出。
界面上看只是一个进度条乱跳,背后是合并结果可能张冠李戴。
2. 怎么串起来的
加一把 mergeSlot 锁占任务位:
- 手动合并用 TryLock,抢不到直接 409 并告诉用户现在谁在跑 —— 它是一次
HTTP 请求,不能让人干等几十分钟。
- 自动合并用 Lock 排队等待,它本来就是后台任务,丢掉白录一场。
jobStart 改成返回 bool:已经有任务在跑时返回 false 且一个字段都不动。
这样即使以后有调用方忘了先拿任务位,也只是失败而不会互相覆盖。
3. 顺带修「panic 之后卡片一直转」
原来 jobFinish() 写在 autoMerge / mergeHandler 的正常返回路径上,任务内部
一旦 panic(外部 safeGo 的 recover 兜在外层)它就永远不会执行,任务会一直
停在 active,界面上的进度条再也不动,ffmpeg 也失联了。
现在统一走 runMergeExclusive():任务位用 defer 释放,panic 翻译成 error,
保证 jobFinish 一定能跑到。
4. 之前 ban 掉的 jobActive() 接回来了
它本来就该是这道护栏,写了却没人调用,现在用于判断要不要提示「在排队」
以及拒绝新任务时报出正在忙的任务名。jobState 的锁改成 RWMutex 供它读。
已验证:
- 新增 4 个用例:TestJobStartRejectsWhenBusy(被拒时 Must 一个字段都不改,
包括进度必须还是原任务的)、TestMergeSlotTryLockExclusive、
TestMergeSlotSerializes(4 个并发任务必须全跑完且不重不漏)、
TestMergeExclusiveReleasesOnPanic(panic 后必须拿到任务位 + 翻出错误)。
- 反向验证:把 jobStart 的占用判断临时改成 `if false && job.active`,
TestJobStartRejectsWhenBusy 立刻报「应当返回 false」。
- 现有测试跟着走真实链路(progress_test 加了 startJobForTest 统一先收尾、
manualmerge_test 走 execManualMerge 独占执行)—— 改之前这些用例单独跑过、
连着跑会失败,正是因为前一个用例把任务状态留在了 active。
- gofmt / go vet / go build / GOOS=linux go build / go test / go test -race 全过。
This commit is contained in:
+12
-3
@@ -242,9 +242,15 @@ func remapJsonlTimes(srcFile, dstFile string, starts []int64, durs []float64, da
|
||||
return nil
|
||||
}
|
||||
|
||||
// 自动合并入口:把结果写进任务进度;日志措辞仍由内部负责
|
||||
// 自动合并入口:排队跑完一场,把结果写进任务进度;日志措辞仍由内部负责
|
||||
func autoMerge(uid int, segs []recordSegment, jsonlFile string, danmuStartNs int64) {
|
||||
err := autoMergeJob(uid, segs, jsonlFile, danmuStartNs)
|
||||
// 多个主播可能同时下播,而全局只有一份任务状态:先排队,跑到了再登记进度
|
||||
if jobActive() {
|
||||
lPrintf("已有合并任务在跑(%s),本场先排队等待", jobBusyName())
|
||||
}
|
||||
err := runMergeExclusive(func() error {
|
||||
return autoMergeJob(uid, segs, jsonlFile, danmuStartNs)
|
||||
})
|
||||
if err == errJobStopped {
|
||||
// 停止不是故障,内部不打印,这里补一句
|
||||
lPrintWarn("自动合并已被手动停止" + longID(uid))
|
||||
@@ -300,7 +306,10 @@ func autoMergeJob(uid int, segs []recordSegment, jsonlFile string, danmuStartNs
|
||||
inputBytes += fileSizeOrZero(p)
|
||||
}
|
||||
}
|
||||
jobStart("自动合并", filepath.Base(outFile), inputBytes, 0)
|
||||
if !jobStart("自动合并", filepath.Base(outFile), inputBytes, 0) {
|
||||
// 走到这里说明没先拿 mergeSlot,与其把别人的进度冲掉不如直接放弃这一场
|
||||
return errors.New("开始任务失败:还有别的合并任务没结束,本场合并已跳过")
|
||||
}
|
||||
|
||||
video, jsonl, offset, cleanup, err := prepareForMerge(ffmpegFile, segs, jsonlFile, danmuStartNs)
|
||||
defer cleanup()
|
||||
|
||||
Reference in New Issue
Block a user