合并任务排队串行,不再互相顶掉进度和杀错 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
-2
@@ -63,6 +63,16 @@ Dialogue: 0,0:00:03.50,0:00:13.50,Danmu,丙(303),20,20,2,,{\move(400,24,-40,24)}
|
||||
|
||||
// 测试用的 ffmpeg,找不到就跳过
|
||||
// 依次找 PATH 和源码目录(以及上一级目录)里的 ffmpeg,不写死某台机器上的路径
|
||||
// 按真实链路跑一次手动合并:独占任务位,跑完一定要收尾。
|
||||
// 不能直接调 runManualMerge —— 上一个用例如果没收尾,jobStart 会拒掉这一场
|
||||
// (正是它的职责),测试就会莫名其妙失败
|
||||
func execManualMerge(plan mergePlan) error {
|
||||
jobFinish("")
|
||||
err := runMergeExclusive(func() error { return runManualMerge(plan) })
|
||||
jobFinish(jobErrText(err))
|
||||
return err
|
||||
}
|
||||
|
||||
func testFFmpeg(t *testing.T) string {
|
||||
t.Helper()
|
||||
cands := []string{"ffmpeg"}
|
||||
@@ -641,7 +651,7 @@ Dialogue: 0,0:00:05.00,0:00:15.00,Danmu,甲(101),20,20,2,,{\move(400,72,-40,72)}
|
||||
}
|
||||
|
||||
// 真正合并
|
||||
if err := runManualMerge(plan); err != nil {
|
||||
if err := execManualMerge(plan); err != nil {
|
||||
t.Fatalf("手动合并失败:%v", err)
|
||||
}
|
||||
if _, err := os.Stat(plan.out); err != nil {
|
||||
@@ -708,7 +718,7 @@ Dialogue: 0,0:00:05.00,0:00:15.00,Danmu,乙(202),0,0,0,,{\move(400,48,-40,48)}
|
||||
if filepath.Base(plan.videos[0]) != filepath.Base(v1) {
|
||||
t.Fatalf("分段顺序应纠正为第一段在前,实际 %q", filepath.Base(plan.videos[0]))
|
||||
}
|
||||
if err := runManualMerge(plan); err != nil {
|
||||
if err := execManualMerge(plan); err != nil {
|
||||
t.Fatalf("多段手动合并失败:%v", err)
|
||||
}
|
||||
d := probeDuration(ffmpeg, plan.out)
|
||||
|
||||
Reference in New Issue
Block a user