Add cancellation reason tracking with user/system codes and UI
This commit is contained in:
@@ -2,10 +2,12 @@ package api
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
@@ -109,11 +111,23 @@ func (h *JobHandler) Cancel(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
if r.Body != nil && r.ContentLength > 0 {
|
||||
_ = json.NewDecoder(r.Body).Decode(&body)
|
||||
}
|
||||
reason := strings.TrimSpace(body.Reason)
|
||||
if reason == "" {
|
||||
reason = "Job was cancelled by user"
|
||||
}
|
||||
|
||||
if h.engine != nil {
|
||||
h.engine.Cancel(id, j.SyncPairID)
|
||||
h.engine.Cancel(id, j.SyncPairID, true)
|
||||
}
|
||||
|
||||
repo.UpdateStatus(id, "cancelled")
|
||||
repo.SetError(id, "cancelled_user", reason)
|
||||
writeJSON(w, map[string]string{"status": "cancelled"})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/syncserver/internal/config"
|
||||
@@ -61,7 +62,12 @@ func (e *Engine) Run(ctx context.Context, jobID int64, pairID int64) error {
|
||||
}
|
||||
|
||||
jobCtx, cancel := context.WithCancel(ctx)
|
||||
enqueueErr := e.queue.Enqueue(pairID, jobID, cancel)
|
||||
cancelledByUser := atomic.Bool{}
|
||||
wrappedCancel := func() {
|
||||
cancelledByUser.Store(true)
|
||||
cancel()
|
||||
}
|
||||
enqueueErr := e.queue.Enqueue(pairID, jobID, wrappedCancel)
|
||||
if enqueueErr != nil {
|
||||
return enqueueErr
|
||||
}
|
||||
@@ -199,8 +205,15 @@ func (e *Engine) Run(ctx context.Context, jobID int64, pairID int64) error {
|
||||
|
||||
if err != nil {
|
||||
if jobCtx.Err() != nil {
|
||||
code := "cancelled_shutdown"
|
||||
msg := "Job was cancelled due to server shutdown"
|
||||
if cancelledByUser.Load() {
|
||||
code = "cancelled_user"
|
||||
msg = "Job was cancelled by user"
|
||||
}
|
||||
e.setJobError(jobID, code, msg)
|
||||
e.setJobStatus(jobID, "cancelled")
|
||||
e.emit(Event{Type: "status", JobID: jobID, Key: "status", Value: "cancelled"})
|
||||
e.emit(Event{Type: "status", JobID: jobID, Key: "status", Value: "cancelled", Line: msg})
|
||||
return jobCtx.Err()
|
||||
}
|
||||
e.setJobStatus(jobID, "failed")
|
||||
@@ -239,9 +252,9 @@ func (e *Engine) resolveSSHKey(machine *models.Machine) (string, error) {
|
||||
return sshKey.PrivateKeyPath, nil
|
||||
}
|
||||
|
||||
func (e *Engine) Cancel(jobID int64, syncPairID int64) bool {
|
||||
func (e *Engine) Cancel(jobID int64, syncPairID int64, byUser bool) bool {
|
||||
if e.queue.IsRunning(syncPairID) {
|
||||
e.queue.Cancel(syncPairID)
|
||||
e.queue.Cancel(syncPairID, byUser)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -13,8 +13,9 @@ type Queue struct {
|
||||
}
|
||||
|
||||
type RunInfo struct {
|
||||
JobID int64
|
||||
Cancel func()
|
||||
JobID int64
|
||||
Cancel func()
|
||||
ByUser bool
|
||||
}
|
||||
|
||||
func NewQueue() *Queue {
|
||||
@@ -54,10 +55,18 @@ func (q *Queue) GetJobID(syncPairID int64) (int64, bool) {
|
||||
return info.JobID, true
|
||||
}
|
||||
|
||||
func (q *Queue) Cancel(syncPairID int64) {
|
||||
func (q *Queue) Cancel(syncPairID int64, byUser bool) {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
if info, exists := q.runs[syncPairID]; exists && info.Cancel != nil {
|
||||
info.ByUser = byUser
|
||||
info.Cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Queue) IsCancelledByUser(syncPairID int64) bool {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
info, exists := q.runs[syncPairID]
|
||||
return exists && info.ByUser
|
||||
}
|
||||
|
||||
@@ -33,10 +33,15 @@ func TestQueue(t *testing.T) {
|
||||
t.Errorf("Enqueue(1) again = %v, want ErrAlreadyRunning", err)
|
||||
}
|
||||
|
||||
q.Cancel(1)
|
||||
q.Cancel(1, true)
|
||||
if !cancelCalled {
|
||||
t.Error("Cancel should have called the cancel func")
|
||||
}
|
||||
if !q.IsCancelledByUser(1) {
|
||||
t.Error("IsCancelledByUser should return true after Cancel(1, true)")
|
||||
}
|
||||
|
||||
q.Dequeue(1)
|
||||
|
||||
q.Dequeue(1)
|
||||
if q.IsRunning(1) {
|
||||
|
||||
Reference in New Issue
Block a user