fix(subtitles): guard AI job state transitions against terminal rows

UpdateProgress/CompleteJob/FailJob now only update a job that is still
pending/running, so a job cancelled or reaped as stale can't be resurrected
by a late write from its own worker goroutine. Defense-in-depth alongside the
queued-job heartbeat fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Quick
2026-05-30 00:23:16 -04:00
co-authored by Claude Opus 4.8
parent 669050d025
commit 4f2e94c8c5
+7 -3
View File
@@ -101,11 +101,15 @@ func (r *PgJobRepository) ListJobsByMediaFile(ctx context.Context, mediaFileID i
return jobs, rows.Err()
}
// UpdateProgress, CompleteJob, and FailJob only transition a job that is still
// active ("pending"/"running"). The guard makes them no-ops on an already
// terminal row, so a job that was cancelled or reaped as stale can never be
// resurrected by a late write from its own worker goroutine.
func (r *PgJobRepository) UpdateProgress(ctx context.Context, id int64, status JobStatus, progress float64, message string) error {
_, err := r.pool.Exec(ctx,
`UPDATE subtitle_ai_jobs
SET status = $2, progress = $3, progress_message = $4, updated_at = now(), heartbeat_at = now()
WHERE id = $1`, id, status, progress, message)
WHERE id = $1 AND status IN ('pending', 'running')`, id, status, progress, message)
if err != nil {
return fmt.Errorf("update subtitle ai job progress: %w", err)
}
@@ -117,7 +121,7 @@ func (r *PgJobRepository) CompleteJob(ctx context.Context, id int64, subtitleID
`UPDATE subtitle_ai_jobs
SET status = 'completed', progress = 1, result_subtitle_id = $2,
error_message = '', updated_at = now(), heartbeat_at = now()
WHERE id = $1`, id, subtitleID)
WHERE id = $1 AND status IN ('pending', 'running')`, id, subtitleID)
if err != nil {
return fmt.Errorf("complete subtitle ai job: %w", err)
}
@@ -128,7 +132,7 @@ func (r *PgJobRepository) FailJob(ctx context.Context, id int64, status JobStatu
_, err := r.pool.Exec(ctx,
`UPDATE subtitle_ai_jobs
SET status = $2, error_message = $3, updated_at = now(), heartbeat_at = now()
WHERE id = $1`, id, status, message)
WHERE id = $1 AND status IN ('pending', 'running')`, id, status, message)
if err != nil {
return fmt.Errorf("fail subtitle ai job: %w", err)
}