github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/nomad/plan_queue.go (about) 1 package nomad 2 3 import ( 4 "container/heap" 5 "fmt" 6 "sync" 7 "time" 8 9 "github.com/armon/go-metrics" 10 "github.com/hashicorp/nomad/nomad/structs" 11 ) 12 13 var ( 14 // planQueueFlushed is the error used for all pending plans 15 // when the queue is flushed or disabled 16 planQueueFlushed = fmt.Errorf("plan queue flushed") 17 ) 18 19 // PlanFuture is used to return a future for an enqueue 20 type PlanFuture interface { 21 Wait() (*structs.PlanResult, error) 22 } 23 24 // PlanQueue is used to submit commit plans for task allocations 25 // to the current leader. The leader verifies that resources are not 26 // over-committed and commits to Raft. This allows sub-schedulers to 27 // be optimistically concurrent. In the case of an overcommit, the plan 28 // may be partially applied if allowed, or completely rejected (gang commit). 29 type PlanQueue struct { 30 enabled bool 31 stats *QueueStats 32 33 ready PendingPlans 34 waitCh chan struct{} 35 36 l sync.RWMutex 37 } 38 39 // NewPlanQueue is used to construct and return a new plan queue 40 func NewPlanQueue() (*PlanQueue, error) { 41 q := &PlanQueue{ 42 enabled: false, 43 stats: new(QueueStats), 44 ready: make([]*pendingPlan, 0, 16), 45 waitCh: make(chan struct{}, 1), 46 } 47 return q, nil 48 } 49 50 // pendingPlan is used to wrap a plan that is enqueued 51 // so that we can re-use it as a future. 52 type pendingPlan struct { 53 plan *structs.Plan 54 enqueueTime time.Time 55 result *structs.PlanResult 56 errCh chan error 57 } 58 59 // Wait is used to block for the plan result or potential error 60 func (p *pendingPlan) Wait() (*structs.PlanResult, error) { 61 err := <-p.errCh 62 return p.result, err 63 } 64 65 // respond is used to set the response and error for the future 66 func (p *pendingPlan) respond(result *structs.PlanResult, err error) { 67 p.result = result 68 p.errCh <- err 69 } 70 71 // PendingPlans is a list of waiting plans. 72 // We implement the container/heap interface so that this is a 73 // priority queue 74 type PendingPlans []*pendingPlan 75 76 // Enabled is used to check if the queue is enabled. 77 func (q *PlanQueue) Enabled() bool { 78 q.l.RLock() 79 defer q.l.RUnlock() 80 return q.enabled 81 } 82 83 // SetEnabled is used to control if the queue is enabled. The queue 84 // should only be enabled on the active leader. 85 func (q *PlanQueue) SetEnabled(enabled bool) { 86 q.l.Lock() 87 q.enabled = enabled 88 q.l.Unlock() 89 if !enabled { 90 q.Flush() 91 } 92 } 93 94 // Enqueue is used to enqueue a plan 95 func (q *PlanQueue) Enqueue(plan *structs.Plan) (PlanFuture, error) { 96 q.l.Lock() 97 defer q.l.Unlock() 98 99 // Do nothing if not enabled 100 if !q.enabled { 101 return nil, fmt.Errorf("plan queue is disabled") 102 } 103 104 // Wrap the pending plan 105 pending := &pendingPlan{ 106 plan: plan, 107 enqueueTime: time.Now(), 108 errCh: make(chan error, 1), 109 } 110 111 // Push onto the heap 112 heap.Push(&q.ready, pending) 113 114 // Update the stats 115 q.stats.Depth += 1 116 117 // Unblock any blocked reader 118 select { 119 case q.waitCh <- struct{}{}: 120 default: 121 } 122 return pending, nil 123 } 124 125 // Dequeue is used to perform a blocking dequeue 126 func (q *PlanQueue) Dequeue(timeout time.Duration) (*pendingPlan, error) { 127 SCAN: 128 q.l.Lock() 129 130 // Do nothing if not enabled 131 if !q.enabled { 132 q.l.Unlock() 133 return nil, fmt.Errorf("plan queue is disabled") 134 } 135 136 // Look for available work 137 if len(q.ready) > 0 { 138 raw := heap.Pop(&q.ready) 139 pending := raw.(*pendingPlan) 140 q.stats.Depth -= 1 141 q.l.Unlock() 142 return pending, nil 143 } 144 q.l.Unlock() 145 146 // Setup the timeout timer 147 var timerCh <-chan time.Time 148 if timerCh == nil && timeout > 0 { 149 timer := time.NewTimer(timeout) 150 defer timer.Stop() 151 timerCh = timer.C 152 } 153 154 // Wait for timeout or new work 155 select { 156 case <-q.waitCh: 157 goto SCAN 158 case <-timerCh: 159 return nil, nil 160 } 161 } 162 163 // Flush is used to reset the state of the plan queue 164 func (q *PlanQueue) Flush() { 165 q.l.Lock() 166 defer q.l.Unlock() 167 168 // Error out all the futures 169 for _, pending := range q.ready { 170 pending.respond(nil, planQueueFlushed) 171 } 172 173 // Reset the broker 174 q.stats.Depth = 0 175 q.ready = make([]*pendingPlan, 0, 16) 176 177 // Unblock any waiters 178 select { 179 case q.waitCh <- struct{}{}: 180 default: 181 } 182 } 183 184 // Stats is used to query the state of the queue 185 func (q *PlanQueue) Stats() *QueueStats { 186 // Allocate a new stats struct 187 stats := new(QueueStats) 188 189 q.l.RLock() 190 defer q.l.RUnlock() 191 192 // Copy all the stats 193 *stats = *q.stats 194 return stats 195 } 196 197 // EmitStats is used to export metrics about the broker while enabled 198 func (q *PlanQueue) EmitStats(period time.Duration, stopCh chan struct{}) { 199 for { 200 select { 201 case <-time.After(period): 202 stats := q.Stats() 203 metrics.SetGauge([]string{"nomad", "plan", "queue_depth"}, float32(stats.Depth)) 204 205 case <-stopCh: 206 return 207 } 208 } 209 } 210 211 // QueueStats returns all the stats about the plan queue 212 type QueueStats struct { 213 Depth int 214 } 215 216 // Len is for the sorting interface 217 func (p PendingPlans) Len() int { 218 return len(p) 219 } 220 221 // Less is for the sorting interface. We flip the check 222 // so that the "min" in the min-heap is the element with the 223 // highest priority. For the same priority, we use the enqueue 224 // time of the evaluation to give a FIFO ordering. 225 func (p PendingPlans) Less(i, j int) bool { 226 if p[i].plan.Priority != p[j].plan.Priority { 227 return !(p[i].plan.Priority < p[j].plan.Priority) 228 } 229 return p[i].enqueueTime.Before(p[j].enqueueTime) 230 } 231 232 // Swap is for the sorting interface 233 func (p PendingPlans) Swap(i, j int) { 234 p[i], p[j] = p[j], p[i] 235 } 236 237 // Push is used to add a new evaluation to the slice 238 func (p *PendingPlans) Push(e interface{}) { 239 *p = append(*p, e.(*pendingPlan)) 240 } 241 242 // Pop is used to remove an evaluation from the slice 243 func (p *PendingPlans) Pop() interface{} { 244 n := len(*p) 245 e := (*p)[n-1] 246 (*p)[n-1] = nil 247 *p = (*p)[:n-1] 248 return e 249 } 250 251 // Peek is used to peek at the next element that would be popped 252 func (p PendingPlans) Peek() *pendingPlan { 253 n := len(p) 254 if n == 0 { 255 return nil 256 } 257 return p[n-1] 258 }