// gcEval returns whether the eval should be garbage collected given a raft // threshold index. The eval disqualifies for garbage collection if it or its // allocs are not older than the threshold. If the eval should be garbage // collected, the associated alloc ids that should also be removed are also // returned func (c *CoreScheduler) gcEval(eval *structs.Evaluation, thresholdIndex uint64) ( bool, []string, error) { // Ignore non-terminal and new evaluations if !eval.TerminalStatus() || eval.ModifyIndex > thresholdIndex { return false, nil, nil } // Get the allocations by eval allocs, err := c.snap.AllocsByEval(eval.ID) if err != nil { c.srv.logger.Printf("[ERR] sched.core: failed to get allocs for eval %s: %v", eval.ID, err) return false, nil, err } // Scan the allocations to ensure they are terminal and old for _, alloc := range allocs { if !alloc.TerminalStatus() || alloc.ModifyIndex > thresholdIndex { return false, nil, nil } } allocIds := make([]string, len(allocs)) for i, alloc := range allocs { allocIds[i] = alloc.ID } // Evaluation is eligible for garbage collection return true, allocIds, nil }
// gcEval returns whether the eval should be garbage collected given a raft // threshold index. The eval disqualifies for garbage collection if it or its // allocs are not older than the threshold. If the eval should be garbage // collected, the associated alloc ids that should also be removed are also // returned func (c *CoreScheduler) gcEval(eval *structs.Evaluation, thresholdIndex uint64, allowBatch bool) ( bool, []string, error) { // Ignore non-terminal and new evaluations if !eval.TerminalStatus() || eval.ModifyIndex > thresholdIndex { return false, nil, nil } // If the eval is from a running "batch" job we don't want to garbage // collect its allocations. If there is a long running batch job and its // terminal allocations get GC'd the scheduler would re-run the // allocations. if eval.Type == structs.JobTypeBatch { if !allowBatch { return false, nil, nil } // Check if the job is running job, err := c.snap.JobByID(eval.JobID) if err != nil { return false, nil, err } // We don't want to gc anything related to a job which is not dead if job != nil && job.Status != structs.JobStatusDead { return false, nil, nil } } // Get the allocations by eval allocs, err := c.snap.AllocsByEval(eval.ID) if err != nil { c.srv.logger.Printf("[ERR] sched.core: failed to get allocs for eval %s: %v", eval.ID, err) return false, nil, err } // Scan the allocations to ensure they are terminal and old gcEval := true var gcAllocIDs []string for _, alloc := range allocs { if !alloc.TerminalStatus() || alloc.ModifyIndex > thresholdIndex { // Can't GC the evaluation since not all of the allocations are // terminal gcEval = false } else { // The allocation is eligible to be GC'd gcAllocIDs = append(gcAllocIDs, alloc.ID) } } return gcEval, gcAllocIDs, nil }
// evalGC is used to garbage collect old evaluations func (c *CoreScheduler) evalGC(eval *structs.Evaluation) error { // Iterate over the evaluations iter, err := c.snap.Evals() if err != nil { return err } // Compute the old threshold limit for GC using the FSM // time table. This is a rough mapping of a time to the // Raft index it belongs to. tt := c.srv.fsm.TimeTable() cutoff := time.Now().UTC().Add(-1 * c.srv.config.EvalGCThreshold) oldThreshold := tt.NearestIndex(cutoff) c.srv.logger.Printf("[DEBUG] sched.core: eval GC: scanning before index %d (%v)", oldThreshold, c.srv.config.EvalGCThreshold) // Collect the allocations and evaluations to GC var gcAlloc, gcEval []string OUTER: for { raw := iter.Next() if raw == nil { break } eval := raw.(*structs.Evaluation) // Ignore non-terminal and new evaluations if !eval.TerminalStatus() || eval.ModifyIndex > oldThreshold { continue } // Get the allocations by eval allocs, err := c.snap.AllocsByEval(eval.ID) if err != nil { c.srv.logger.Printf("[ERR] sched.core: failed to get allocs for eval %s: %v", eval.ID, err) continue } // Scan the allocations to ensure they are terminal and old for _, alloc := range allocs { if !alloc.TerminalStatus() || alloc.ModifyIndex > oldThreshold { continue OUTER } } // Evaluation is eligible for garbage collection gcEval = append(gcEval, eval.ID) for _, alloc := range allocs { gcAlloc = append(gcAlloc, alloc.ID) } } // Fast-path the nothing case if len(gcEval) == 0 && len(gcAlloc) == 0 { return nil } c.srv.logger.Printf("[DEBUG] sched.core: eval GC: %d evaluations, %d allocs eligible", len(gcEval), len(gcAlloc)) // Call to the leader to issue the reap req := structs.EvalDeleteRequest{ Evals: gcEval, Allocs: gcAlloc, WriteRequest: structs.WriteRequest{ Region: c.srv.config.Region, }, } var resp structs.GenericResponse if err := c.srv.RPC("Eval.Reap", &req, &resp); err != nil { c.srv.logger.Printf("[ERR] sched.core: eval reap failed: %v", err) return err } return nil }