@@ -557,32 +557,53 @@ func (p *TaskPool) StopTask(targetTask db.Task, forceStop bool) error {
557557// the specified project and template. If forceStop is true, tasks are marked as
558558// stopped immediately and running tasks are killed; otherwise tasks are marked
559559// as stopping and will gracefully transition to stopped.
560+ //
561+ // Waiting tasks (which have no running process) are dequeued and bulk-updated in
562+ // the database in a single query, avoiding expensive per-task hydration.
563+ // Non-waiting tasks go through the regular per-task SetStatus path.
560564func (p * TaskPool ) StopTasksByTemplate (projectID int , templateID int , forceStop bool ) {
561565
562566 stoppedTasks := map [int ]struct {}{}
563567
564- // Handle queued tasks
565- for _ , t := range p .state .QueueRange () {
568+ // Bulk-update all waiting tasks in DB in a single query.
569+ // This is the fast path -- waiting tasks have no running process.
570+ if err := p .store .SetWaitingTasksToStopped (projectID , templateID ); err != nil {
571+ log .Error (err )
572+ }
573+
574+ // Dequeue waiting tasks from the in-memory queue.
575+ i := 0
576+ for i < p .state .QueueLen () {
577+ t := p .state .QueueGet (i )
566578 if t == nil {
579+ i ++
567580 continue
568581 }
569582 if t .Task .ProjectID != projectID || t .Task .TemplateID != templateID {
583+ i ++
570584 continue
571585 }
572586 if t .Task .Status .IsFinished () {
587+ i ++
588+ continue
589+ }
590+
591+ if t .Task .Status == task_logger .TaskWaitingStatus {
592+ stoppedTasks [t .Task .ID ] = struct {}{}
593+ _ = p .state .DequeueAt (i )
573594 continue
574595 }
596+
575597 if forceStop {
576598 t .SetStatus (task_logger .TaskStoppedStatus )
577599 } else {
578600 t .SetStatus (task_logger .TaskStoppingStatus )
579601 }
580-
581602 stoppedTasks [t .Task .ID ] = struct {}{}
582- // Queued tasks will be dequeued and immediately finalize to Stopped in run()
603+ i ++
583604 }
584605
585- // Handle running tasks
606+ // Handle running tasks -- these need per-task SetStatus and kill.
586607 for _ , t := range p .state .RunningRange () {
587608 if t == nil {
588609 continue
@@ -606,9 +627,8 @@ func (p *TaskPool) StopTasksByTemplate(projectID int, templateID int, forceStop
606627 stoppedTasks [t .Task .ID ] = struct {}{}
607628 }
608629
609- // Update tasks in DB that are neither queued nor running but still active
610- // (e.g., created but not present in this instance's memory state).
611-
630+ // Handle non-waiting tasks in DB that are neither queued nor running locally
631+ // (e.g., HA mode or tasks created but not present in this instance's memory).
612632 tasks , err := p .store .GetTemplateTasks (projectID , templateID , db.RetrieveQueryParams {
613633 TaskFilter : & db.TaskFilter {
614634 Status : task_logger .UnfinishedTaskStatuses (),
@@ -622,7 +642,7 @@ func (p *TaskPool) StopTasksByTemplate(projectID int, templateID int, forceStop
622642
623643 for _ , twt := range tasks {
624644
625- if _ , ok := stoppedTasks [twt .ID ]; ok { // already stopped
645+ if _ , ok := stoppedTasks [twt .ID ]; ok {
626646 continue
627647 }
628648
0 commit comments