github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/nomad/drainer/drain_heap.go (about)

     1  package drainer
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  )
     8  
     9  // DrainDeadlineNotifier allows batch notification of nodes that have reached
    10  // their drain deadline.
    11  type DrainDeadlineNotifier interface {
    12  	// NextBatch returns the next batch of nodes that have reached their
    13  	// deadline.
    14  	NextBatch() <-chan []string
    15  
    16  	// Remove removes the given node from being tracked for a deadline.
    17  	Remove(nodeID string)
    18  
    19  	// Watch marks the given node for being watched for its deadline.
    20  	Watch(nodeID string, deadline time.Time)
    21  }
    22  
    23  // deadlineHeap implements the DrainDeadlineNotifier and is backed by a min-heap
    24  // to efficiently determine the next deadlining node. It also supports
    25  // coalescing several deadlines into a single emission.
    26  type deadlineHeap struct {
    27  	ctx            context.Context
    28  	coalesceWindow time.Duration
    29  	batch          chan []string
    30  	nodes          map[string]time.Time
    31  	trigger        chan struct{}
    32  	mu             sync.Mutex
    33  }
    34  
    35  // NewDeadlineHeap returns a new deadline heap that coalesces for the given
    36  // duration and will stop watching when the passed context is cancelled.
    37  func NewDeadlineHeap(ctx context.Context, coalesceWindow time.Duration) *deadlineHeap {
    38  	d := &deadlineHeap{
    39  		ctx:            ctx,
    40  		coalesceWindow: coalesceWindow,
    41  		batch:          make(chan []string),
    42  		nodes:          make(map[string]time.Time, 64),
    43  		trigger:        make(chan struct{}, 1),
    44  	}
    45  
    46  	go d.watch()
    47  	return d
    48  }
    49  
    50  func (d *deadlineHeap) watch() {
    51  	timer := time.NewTimer(0)
    52  	timer.Stop()
    53  	select {
    54  	case <-timer.C:
    55  	default:
    56  	}
    57  	defer timer.Stop()
    58  
    59  	var nextDeadline time.Time
    60  	for {
    61  		select {
    62  		case <-d.ctx.Done():
    63  			return
    64  		case <-timer.C:
    65  			var batch []string
    66  
    67  			d.mu.Lock()
    68  			for nodeID, nodeDeadline := range d.nodes {
    69  				if !nodeDeadline.After(nextDeadline) {
    70  					batch = append(batch, nodeID)
    71  					delete(d.nodes, nodeID)
    72  				}
    73  			}
    74  			d.mu.Unlock()
    75  
    76  			if len(batch) > 0 {
    77  				// Send the batch
    78  				select {
    79  				case d.batch <- batch:
    80  				case <-d.ctx.Done():
    81  					return
    82  				}
    83  			}
    84  
    85  		case <-d.trigger:
    86  		}
    87  
    88  		// Calculate the next deadline
    89  		deadline, ok := d.calculateNextDeadline()
    90  		if !ok {
    91  			continue
    92  		}
    93  
    94  		// If the deadline is zero, it is a force drain. Otherwise if the
    95  		// deadline is in the future, see if we already have a timer setup to
    96  		// handle it. If we don't create the timer.
    97  		if deadline.IsZero() || !deadline.Equal(nextDeadline) {
    98  			timer.Reset(deadline.Sub(time.Now()))
    99  			nextDeadline = deadline
   100  		}
   101  	}
   102  }
   103  
   104  // calculateNextDeadline returns the next deadline in which to scan for
   105  // deadlined nodes. It applies the coalesce window.
   106  func (d *deadlineHeap) calculateNextDeadline() (time.Time, bool) {
   107  	d.mu.Lock()
   108  	defer d.mu.Unlock()
   109  
   110  	if len(d.nodes) == 0 {
   111  		return time.Time{}, false
   112  	}
   113  
   114  	// Calculate the new timer value
   115  	var deadline time.Time
   116  	for _, v := range d.nodes {
   117  		if deadline.IsZero() || v.Before(deadline) {
   118  			deadline = v
   119  		}
   120  	}
   121  
   122  	var maxWithinWindow time.Time
   123  	coalescedDeadline := deadline.Add(d.coalesceWindow)
   124  	for _, nodeDeadline := range d.nodes {
   125  		if nodeDeadline.Before(coalescedDeadline) {
   126  			if maxWithinWindow.IsZero() || nodeDeadline.After(maxWithinWindow) {
   127  				maxWithinWindow = nodeDeadline
   128  			}
   129  		}
   130  	}
   131  
   132  	return maxWithinWindow, true
   133  }
   134  
   135  // NextBatch returns the next batch of nodes to be drained.
   136  func (d *deadlineHeap) NextBatch() <-chan []string {
   137  	return d.batch
   138  }
   139  
   140  func (d *deadlineHeap) Remove(nodeID string) {
   141  	d.mu.Lock()
   142  	defer d.mu.Unlock()
   143  	delete(d.nodes, nodeID)
   144  
   145  	select {
   146  	case d.trigger <- struct{}{}:
   147  	default:
   148  	}
   149  }
   150  
   151  func (d *deadlineHeap) Watch(nodeID string, deadline time.Time) {
   152  	d.mu.Lock()
   153  	defer d.mu.Unlock()
   154  	d.nodes[nodeID] = deadline
   155  
   156  	select {
   157  	case d.trigger <- struct{}{}:
   158  	default:
   159  	}
   160  }