// tick attempts to schedule the queue. func (s *Scheduler) tick(ctx context.Context) { tasksByCommonSpec := make(map[string]map[string]*api.Task) schedulingDecisions := make(map[string]schedulingDecision, s.unassignedTasks.Len()) var next *list.Element for e := s.unassignedTasks.Front(); e != nil; e = next { next = e.Next() t := s.allTasks[e.Value.(*api.Task).ID] if t == nil || t.NodeID != "" { // task deleted or already assigned s.unassignedTasks.Remove(e) continue } // Group common tasks with common specs by marshalling the spec // into taskKey and using it as a map key. // TODO(aaronl): Once specs are versioned, this will allow a // much more efficient fast path. fieldsToMarshal := api.Task{ ServiceID: t.ServiceID, Spec: t.Spec, } marshalled, err := fieldsToMarshal.Marshal() if err != nil { panic(err) } taskGroupKey := string(marshalled) if tasksByCommonSpec[taskGroupKey] == nil { tasksByCommonSpec[taskGroupKey] = make(map[string]*api.Task) } tasksByCommonSpec[taskGroupKey][t.ID] = t s.unassignedTasks.Remove(e) } for _, taskGroup := range tasksByCommonSpec { s.scheduleTaskGroup(ctx, taskGroup, schedulingDecisions) } _, failed := s.applySchedulingDecisions(ctx, schedulingDecisions) for _, decision := range failed { s.allTasks[decision.old.ID] = decision.old nodeInfo, err := s.nodeSet.nodeInfo(decision.new.NodeID) if err == nil && nodeInfo.removeTask(decision.new) { s.nodeSet.updateNode(nodeInfo) } // enqueue task for next scheduling attempt s.enqueue(decision.old) } }