Add pause and cancel to a running migration
A live run could only be stopped one account at a time, and stopping it at all meant losing the queue: the accounts that had not started yet stayed idle with no record that they were meant to run. A run now carries a handle holding the context that stops every account under it plus the reason it was stopped. Pause and cancel take the same path and differ only in the status left behind — paused accounts are what Resume re-runs, and the migration journal makes each one continue where it stopped instead of re-copying. Accounts still queued when the stop lands get the same status as the interrupted ones, so the whole remainder is resumable after a pause and cancelled after a cancel. Database writes keep using the uncancellable context, so statuses and counters survive the stop. The scheduler skips paused tasks: auto-starting a full run would defeat the pause. An operator stopping a run no longer trips the schedule breaker either — that is for failures, not for intent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -127,6 +127,56 @@ func (s *Server) handleRun(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusAccepted, map[string]int64{"run_id": runID})
|
||||
}
|
||||
|
||||
// handlePauseRun stops the live run but keeps the unfinished accounts
|
||||
// resumable; handleCancelRun stops it for good. Both are no-ops (409) when the
|
||||
// task has no run in flight.
|
||||
func (s *Server) handlePauseRun(w http.ResponseWriter, r *http.Request) {
|
||||
s.stopRun(w, r, s.orch.PauseTask)
|
||||
}
|
||||
|
||||
func (s *Server) handleCancelRun(w http.ResponseWriter, r *http.Request) {
|
||||
s.stopRun(w, r, s.orch.CancelTask)
|
||||
}
|
||||
|
||||
func (s *Server) stopRun(w http.ResponseWriter, r *http.Request, stop func(int64) bool) {
|
||||
taskID, err := pathID(r, "id")
|
||||
if err != nil {
|
||||
http.Error(w, "bad id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if !stop(taskID) {
|
||||
http.Error(w, "task is not running", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
// handleResumeRun restarts a paused task with the accounts its pause left
|
||||
// unfinished; already-copied messages are skipped by the migration journal.
|
||||
func (s *Server) handleResumeRun(w http.ResponseWriter, r *http.Request) {
|
||||
taskID, err := pathID(r, "id")
|
||||
if err != nil {
|
||||
http.Error(w, "bad id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
runID, err := s.orch.ResumeTask(r.Context(), taskID)
|
||||
switch {
|
||||
case errors.Is(err, orchestrator.ErrNothingToResume):
|
||||
http.Error(w, "no paused accounts to resume", http.StatusConflict)
|
||||
return
|
||||
case errors.Is(err, orchestrator.ErrNotTested):
|
||||
http.Error(w, "accounts must pass connection tests first", http.StatusConflict)
|
||||
return
|
||||
case errors.Is(err, orchestrator.ErrAlreadyRunning):
|
||||
http.Error(w, "task is already running", http.StatusConflict)
|
||||
return
|
||||
case err != nil:
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusAccepted, map[string]int64{"run_id": runID})
|
||||
}
|
||||
|
||||
func (s *Server) handleCancelAccount(w http.ResponseWriter, r *http.Request) {
|
||||
taskID, err := pathID(r, "id")
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user