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  }