fix(tasks): 任务取消 - 禁止取消已开始的生成任务
This commit is contained in:
@@ -691,16 +691,18 @@ func (s *Service) RetryTask(userID string, id string) (*model.Task, error) {
|
||||
return taskForOutput(*task), nil
|
||||
}
|
||||
|
||||
func (s *Service) CancelTask(ctx context.Context, userID string, id string) (*model.Task, error) {
|
||||
func (s *Service) CancelTask(_ context.Context, userID string, id string) (*model.Task, error) {
|
||||
task, err := s.repo.TaskForUser(userID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if task.Status == model.TaskStatusRunning {
|
||||
return nil, errors.New("任务已开始生成,无法取消")
|
||||
}
|
||||
if task.Status == model.TaskStatusSucceeded {
|
||||
return nil, errors.New("completed task cannot be cancelled")
|
||||
}
|
||||
now := time.Now()
|
||||
cancelledRunningTask := false
|
||||
if task.Status == model.TaskStatusQueued {
|
||||
cancelled, err := s.repo.CancelTaskIfStatus(userID, task.ID, model.TaskStatusQueued, now)
|
||||
if err != nil {
|
||||
@@ -721,31 +723,9 @@ func (s *Service) CancelTask(ctx context.Context, userID string, id string) (*mo
|
||||
}
|
||||
}
|
||||
}
|
||||
// 排队任务可能在条件更新前被 worker 抢占,重新读取后必须再次阻止取消。
|
||||
if task.Status == model.TaskStatusRunning {
|
||||
cancelled, err := s.repo.CancelTaskIfStatus(userID, task.ID, model.TaskStatusRunning, now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !cancelled {
|
||||
latest, latestErr := s.repo.TaskForUser(userID, id)
|
||||
if latestErr != nil {
|
||||
return nil, latestErr
|
||||
}
|
||||
if latest.Status == model.TaskStatusSucceeded {
|
||||
return nil, errors.New("completed task cannot be cancelled")
|
||||
}
|
||||
task = latest
|
||||
} else {
|
||||
cancelledRunningTask = true
|
||||
s.cancelActiveTask(task.ID)
|
||||
if err := s.MarkBillingUncertain(task.BillingOrderID, "运行中的上游请求被用户取消,费用状态待核对"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
task, err = s.repo.TaskForUser(userID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil, errors.New("任务已开始生成,无法取消")
|
||||
}
|
||||
if task.Status != model.TaskStatusCancelled {
|
||||
return nil, errors.New("task cannot be cancelled in its current state")
|
||||
@@ -753,15 +733,6 @@ func (s *Service) CancelTask(ctx context.Context, userID string, id string) (*mo
|
||||
if task.SessionID != "" {
|
||||
_ = s.markSessionFailed(*task, "会话任务已取消。")
|
||||
}
|
||||
if cancelledRunningTask {
|
||||
if err := s.requestProviderCancellation(ctx, task); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
task, err = s.repo.TaskForUser(userID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := s.finalizeTaskTextReplay(task.ID, model.TaskStatusCancelled); err != nil {
|
||||
_ = s.log(userID, task.ID, "error", "文本回放草稿归并失败", err.Error())
|
||||
}
|
||||
|
||||
47
backend/internal/service/task_cancellation_test.go
Normal file
47
backend/internal/service/task_cancellation_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"infinite-canvas/backend/internal/model"
|
||||
"infinite-canvas/backend/internal/repository"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestCancelTaskRejectsRunningTask(t *testing.T) {
|
||||
db, err := gorm.Open(sqlite.Open("file:"+newID()+"?mode=memory&cache=shared"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.Task{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startedAt := time.Now()
|
||||
task := model.Task{
|
||||
ID: "running-task",
|
||||
UserID: "user-1",
|
||||
Status: model.TaskStatusRunning,
|
||||
Stage: "调用生成模型",
|
||||
StartedAt: &startedAt,
|
||||
}
|
||||
if err := db.Create(&task).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
svc := &Service{repo: repository.New(db)}
|
||||
if _, err := svc.CancelTask(context.Background(), task.UserID, task.ID); err == nil || err.Error() != "任务已开始生成,无法取消" {
|
||||
t.Fatalf("CancelTask() error = %v", err)
|
||||
}
|
||||
|
||||
stored, err := svc.repo.TaskForUser(task.UserID, task.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stored.Status != model.TaskStatusRunning || stored.CompletedAt != nil {
|
||||
t.Fatalf("running task changed after cancellation attempt: status=%s completedAt=%v", stored.Status, stored.CompletedAt)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user