package store import "context" // ResetRunningOnStartup clears phantom "running" statuses left behind when the // process died mid-run (crash, container restart). A fresh process has no // in-flight goroutines, so any persisted "running" is stale and would otherwise // wedge the task (the run-guard refuses to start, and accounts can't be edited). // Returns how many task and account rows were reset. func (s *Store) ResetRunningOnStartup(ctx context.Context) (tasks int64, accounts int64, err error) { ct, err := s.Pool.Exec(ctx, `UPDATE accounts SET status='idle' WHERE status='running'`) if err != nil { return 0, 0, err } accounts = ct.RowsAffected() ct, err = s.Pool.Exec(ctx, `UPDATE tasks SET status='idle' WHERE status='running'`) if err != nil { return 0, accounts, err } tasks = ct.RowsAffected() return tasks, accounts, nil } // ClearStuckAccount resets an account stuck in "running" (no live goroutine) to // "idle". Returns true if a stuck row was actually cleared. func (s *Store) ClearStuckAccount(ctx context.Context, accountID int64) (bool, error) { ct, err := s.Pool.Exec(ctx, `UPDATE accounts SET status='idle' WHERE id=$1 AND status='running'`, accountID) if err != nil { return false, err } return ct.RowsAffected() == 1, nil } // ReconcileTaskStatus moves a task out of "running" once none of its accounts // are still running — used after clearing a stuck account so the task can be // re-run or deleted again. func (s *Store) ReconcileTaskStatus(ctx context.Context, taskID int64) error { _, err := s.Pool.Exec(ctx, `UPDATE tasks SET status='idle' WHERE id=$1 AND status='running' AND NOT EXISTS (SELECT 1 FROM accounts WHERE task_id=$1 AND status='running')`, taskID) return err }