fix(agent): 图片审批 - 修改生成参数时不再因容量检查自锁

审批落库时已经持有存储锁,干跑创建任务又申请同一把锁。互斥锁不能重入,修改图片参数会一直停住,后端测试也会在 20 分钟后超时。
This commit is contained in:
ddcat666
2026-10-01 13:18:06 +08:00
parent 6195d8d4f7
commit 839db1c319
4 changed files with 33 additions and 14 deletions

View File

@@ -49,8 +49,10 @@ func (s *Service) updateCloudAgentMediaApproval(repo *repository.Repository, run
}
// Dry admission uses the same model, ownership and option checks as task
// submission, without reserving credits or creating a generation task.
// DecideCloudAgentApproval already holds storageMu for this mutation.
preparation := &creationTaskPreparation{}
req.creationPrepare = preparation
req.callerHoldsStorageMu = true
task, err := s.CreateTask(run.UserID, req)
if err != nil {
return err

View File

@@ -101,16 +101,19 @@ const taskLogPayloadLimit = 4000
type CreateTaskRequest struct {
creationPrepare *creationTaskPreparation
admission *taskAdmission
ProjectID string `json:"projectId"`
Type string `json:"type"`
Operation string `json:"operation"`
Prompt string `json:"prompt"`
Provider string `json:"provider"`
Model string `json:"model"`
LogicalModelID string `json:"logicalModelId"`
Input map[string]any `json:"input"`
TraceID string `json:"-"`
RequestID string `json:"-"`
// callerHoldsStorageMu 只给已经持有 Service.storageMu 的内部调用设置。
// 文件容量检查不能再锁一次,否则审批改参数会自锁。
callerHoldsStorageMu bool
ProjectID string `json:"projectId"`
Type string `json:"type"`
Operation string `json:"operation"`
Prompt string `json:"prompt"`
Provider string `json:"provider"`
Model string `json:"model"`
LogicalModelID string `json:"logicalModelId"`
Input map[string]any `json:"input"`
TraceID string `json:"-"`
RequestID string `json:"-"`
}
type TaskListOptions struct {

View File

@@ -119,8 +119,15 @@ func (s *Service) CreateTask(userID string, req CreateTaskRequest) (*model.Task,
return nil, BadAuthRequest(fmt.Sprintf("同时排队或运行的任务最多 %d 个,请等待已有任务完成", policy.Task.ActiveTaskLimit))
}
// 媒体任务在扣费和调用上游前确认账号文件容量,避免容量已满仍然计费却无法回存产物。
if err := s.requireStoredFileCapacityForTask(userID, taskType, policy); err != nil {
return nil, err
// 调用方已持锁时走不加锁的版本;这里不能再抢同一把 storageMu。
var capacityErr error
if req.callerHoldsStorageMu {
capacityErr = s.requireStoredFileCapacityWhileLocked(userID, taskType, policy)
} else {
capacityErr = s.requireStoredFileCapacityForTask(userID, taskType, policy)
}
if capacityErr != nil {
return nil, capacityErr
}
task := model.Task{ID: newID(), UserID: userID, TraceID: req.TraceID, RequestID: req.RequestID, ProjectID: req.ProjectID, Type: taskType, Status: model.TaskStatusQueued, Stage: "等待队列调度", Progress: 5, Prompt: prompt, Operation: req.Operation, Provider: req.Provider, Model: req.Model}
if req.admission != nil {

View File

@@ -20,6 +20,15 @@ func taskTypeProducesStoredFile(taskType string) bool {
// 账号文件容量此前只在产物回存时校验,上游调用已经发出并扣费,失败只能表现为
// 媒体无法入库。生成任务必须在扣费前确认账号还有可用容量,容量已满时直接拒绝。
func (s *Service) requireStoredFileCapacityForTask(userID string, taskType string, policy RuntimePolicySetting) error {
s.storageMu.Lock()
defer s.storageMu.Unlock()
return s.requireStoredFileCapacityWhileLocked(userID, taskType, policy)
}
// requireStoredFileCapacityWhileLocked 与上面相同,但调用方必须已经持有 storageMu。
// sync.Mutex 不可重入;审批改参数的干跑 admission 发生在 DecideCloudAgentApproval
// 的临界区内,再次加锁会把测试和线上审批一起卡死。
func (s *Service) requireStoredFileCapacityWhileLocked(userID string, taskType string, policy RuntimePolicySetting) error {
if !taskTypeProducesStoredFile(taskType) {
return nil
}
@@ -27,8 +36,6 @@ func (s *Service) requireStoredFileCapacityForTask(userID string, taskType strin
if storedLimit <= 0 {
return nil
}
s.storageMu.Lock()
defer s.storageMu.Unlock()
storedBytes, err := s.repo.UserStoredFileBytes(userID)
if err != nil {
return err