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 }