This commit is contained in:
2026-08-16 17:38:15 +02:00
parent d161b28a10
commit b19373e2a4
9 changed files with 365 additions and 15 deletions
+18 -5
View File
@@ -548,11 +548,13 @@ func (p *Pipeline) RetryJob(ctx context.Context, companyID, id uuid.UUID) (Job,
firstStep = progress[0].Step
}
itemStatuses := []string{"failed", "cancelled"}
// Include "processing": ProcessJob can fail after claim (hydrate/etc.) and leave
// rows stuck in processing — RetryJob must reclaim them or the job never drains.
itemStatuses := []string{"failed", "cancelled", "processing"}
resetProcessed := false
if job.Status == "completed" {
// Completed retries should rerun the whole job, not immediately no-op.
itemStatuses = []string{"processed", "failed", "cancelled"}
itemStatuses = []string{"processed", "failed", "cancelled", "processing"}
resetProcessed = true
}
@@ -677,6 +679,7 @@ func (p *Pipeline) ProcessJob(ctx context.Context, jobID uuid.UUID) error {
items, err := p.loadPendingItems(ctx, jobID, batch)
if err != nil {
flushProgress(true)
_ = p.reclaimOrphanedProcessingItems(ctx, jobID)
return err
}
if len(items) == 0 {
@@ -687,24 +690,34 @@ func (p *Pipeline) ProcessJob(ctx context.Context, jobID uuid.UUID) error {
if err := p.Pool.QueryRow(ctx, `
SELECT COUNT(*) FROM processing_job_products
WHERE job_id = $1 AND processed_product_id IS NULL
AND status IN ('pending', 'processing')`, jobID).Scan(&open); err != nil {
AND status IN ('pending', 'processing')`, jobID).Scan(&open); err != nil {
flushProgress(true)
_ = p.reclaimOrphanedProcessingItems(ctx, jobID)
return fmt.Errorf("processing: count open items job=%s: %w", jobID, err)
}
if open > 0 {
if _, err := p.Pool.Exec(ctx, `
ct, err := p.Pool.Exec(ctx, `
UPDATE processing_job_products
SET status = 'pending', error = NULL, updated_at = now()
WHERE job_id = $1 AND status = 'processing' AND processed_product_id IS NULL`, jobID); err != nil {
WHERE job_id = $1 AND status = 'processing' AND processed_product_id IS NULL`, jobID)
if err != nil {
flushProgress(true)
_ = p.reclaimOrphanedProcessingItems(ctx, jobID)
return fmt.Errorf("processing: reclaim fresh processing job=%s: %w", jobID, err)
}
// Fail closed: pending-only open that claim skipped must not spin forever.
if ct.RowsAffected() == 0 {
flushProgress(true)
_ = p.reclaimOrphanedProcessingItems(ctx, jobID)
return fmt.Errorf("processing: open items unclaimable job=%s open=%d", jobID, open)
}
continue
}
break
}
if err := p.hydrateJobItems(ctx, companyID, items); err != nil {
flushProgress(true)
_ = p.reclaimOrphanedProcessingItems(ctx, jobID)
return fmt.Errorf("processing: hydrate batch job=%s: %w", jobID, err)
}
creditsStop := false