Files
bvandeusen 876f994904 fix(server/m7-recurring-scans): early-return in TryStartScan reap branch
revive flagged the if/else where the else branch was a return — invert
the condition and return the non-stale skip path early so the reap
flow drops one indent level.
2026-05-06 23:03:21 -04:00

66 lines
2.3 KiB
Go

package library
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"git.fabledsword.com/bvandeusen/minstrel/internal/coverart"
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)
// StuckScanThreshold is the age past which an in-flight scan_runs row is
// reaped on the next TryStartScan attempt. Real scans on realistic libraries
// finish well within this; the threshold is set far above legitimate scan
// duration so a normally-running scan is never reaped accidentally.
const StuckScanThreshold = 1 * time.Hour
// TryStartScan attempts to start a new scan, with built-in handling for
// stuck/stale in-flight rows.
//
// Behavior:
// - No in-flight row → starts RunScan in a detached goroutine; returns (true, nil, nil).
// - In-flight row, started_at > StuckScanThreshold ago → reaps via FinishScanRun
// with error_message="reaped (stale)", then starts new scan; returns (true, nil, nil).
// - In-flight row, started_at within threshold → returns (false, &row, nil); caller
// decides what to do (HTTP handler returns 409; scheduler logs skip).
// - DB error during the in-flight check or reap → returns (false, nil, err).
func TryStartScan(ctx context.Context, pool *pgxpool.Pool,
scanner *Scanner, enricher *coverart.Enricher,
logger *slog.Logger, cfg RunScanConfig,
) (started bool, existing *dbq.GetInFlightScanRunRow, err error) {
q := dbq.New(pool)
row, qerr := q.GetInFlightScanRun(ctx)
if qerr == nil {
// Found an in-flight row. Reap if stale, else skip.
age := time.Since(row.StartedAt.Time)
if age <= StuckScanThreshold {
return false, &row, nil
}
logger.Warn("reaping stale in-flight scan",
"id", row.ID, "started_at", row.StartedAt.Time, "age", age)
if rerr := q.FinishScanRun(ctx, dbq.FinishScanRunParams{
ID: row.ID,
Column2: "reaped (stale)",
}); rerr != nil {
return false, nil, fmt.Errorf("reap stale scan: %w", rerr)
}
// Fall through to start a new scan.
} else if !errors.Is(qerr, pgx.ErrNoRows) {
return false, nil, fmt.Errorf("in-flight check: %w", qerr)
}
// No in-flight (or just reaped). Start RunScan detached.
go func() {
if _, runErr := RunScan(ctx, pool, scanner, enricher, logger, cfg); runErr != nil {
logger.Warn("scan run failed", "err", runErr)
}
}()
return true, nil, nil
}