| 95 | } |
| 96 | |
| 97 | func (tm *TaskManager) Enqueue(task *Task) { |
| 98 | if task == nil { |
| 99 | return |
| 100 | } |
| 101 | |
| 102 | var startTask *Task |
| 103 | |
| 104 | tm.mu.Lock() |
| 105 | if task.ID == "" { |
| 106 | task.ID = uuid.New().String() |
| 107 | } |
| 108 | |
| 109 | // Keep task manager state isolated from caller-owned memory to avoid |
| 110 | // cross-goroutine races when callers inspect their original task pointer. |
| 111 | internalTask := cloneTask(task) |
| 112 | internalTask.Status = StatusQueued |
| 113 | internalTask.CreatedAt = time.Now() |
| 114 | |
| 115 | key := taskKey(internalTask.TargetNode, internalTask.TargetVMID) |
| 116 | |
| 117 | // Check if there is an active task for this VM on this node |
| 118 | if _, active := tm.activeTasks[key]; !active && len(tm.activeTasks) < tm.maxRunning { |
| 119 | // No active task, start immediately |
| 120 | tm.activeTasks[key] = internalTask |
| 121 | startTask = internalTask |
| 122 | } else { |
| 123 | // Active task exists, append to queue |
| 124 | tm.queue[key] = append(tm.queue[key], internalTask) |
| 125 | } |
| 126 | tm.mu.Unlock() |
| 127 | |
| 128 | if startTask != nil { |
| 129 | go tm.runTask(startTask) |
| 130 | } |
| 131 | |
| 132 | if tm.updateNotify != nil { |
| 133 | go tm.updateNotify() |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | func (tm *TaskManager) runTask(task *Task) { |
| 138 | // Update status to running |