github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/client/structs/broadcaster.go (about) 1 package structs 2 3 import ( 4 "errors" 5 "sync" 6 7 hclog "github.com/hashicorp/go-hclog" 8 "github.com/hashicorp/nomad/nomad/structs" 9 ) 10 11 const ( 12 // listenerCap is the capacity of the listener chans. Must be exactly 1 13 // to prevent Sends from blocking and allows them to pop old pending 14 // updates from the chan before enqueueing the latest update. 15 listenerCap = 1 16 ) 17 18 var ErrAllocBroadcasterClosed = errors.New("alloc broadcaster closed") 19 20 // AllocBroadcaster implements an allocation broadcast channel where each 21 // listener receives allocation updates. Pending updates are dropped and 22 // replaced by newer allocation updates, so listeners may not receive every 23 // allocation update. However this ensures Sends never block and listeners only 24 // receive the latest allocation update -- never a stale version. 25 type AllocBroadcaster struct { 26 mu sync.Mutex 27 28 // listeners is a map of unique ids to listener chans. lazily 29 // initialized on first listen 30 listeners map[int]chan *structs.Allocation 31 32 // nextId is the next id to assign in listener map 33 nextId int 34 35 // closed is true if broadcsater is closed 36 closed bool 37 38 // last alloc sent to prime new listeners 39 last *structs.Allocation 40 41 logger hclog.Logger 42 } 43 44 // NewAllocBroadcaster returns a new AllocBroadcaster. 45 func NewAllocBroadcaster(l hclog.Logger) *AllocBroadcaster { 46 return &AllocBroadcaster{ 47 logger: l, 48 } 49 } 50 51 // Send broadcasts an allocation update. Any pending updates are replaced with 52 // this version of the allocation to prevent blocking on slow receivers. 53 // Returns ErrAllocBroadcasterClosed if called after broadcaster is closed. 54 func (b *AllocBroadcaster) Send(v *structs.Allocation) error { 55 b.mu.Lock() 56 defer b.mu.Unlock() 57 if b.closed { 58 return ErrAllocBroadcasterClosed 59 } 60 61 b.logger.Trace("sending updated alloc", 62 "client_status", v.ClientStatus, 63 "desired_status", v.DesiredStatus, 64 ) 65 66 // Store last sent alloc for future listeners 67 b.last = v 68 69 // Send alloc to already created listeners 70 for _, l := range b.listeners { 71 select { 72 case l <- v: 73 case <-l: 74 // Pop pending update and replace with new update 75 l <- v 76 } 77 } 78 79 return nil 80 } 81 82 // Close closes the channel, disabling the sending of further allocation 83 // updates. Pending updates are still received by listeners. Safe to call 84 // concurrently and more than once. 85 func (b *AllocBroadcaster) Close() { 86 b.mu.Lock() 87 defer b.mu.Unlock() 88 if b.closed { 89 return 90 } 91 92 // Close all listener chans 93 for _, l := range b.listeners { 94 close(l) 95 } 96 97 // Clear all references and mark broadcaster as closed 98 b.listeners = nil 99 b.closed = true 100 } 101 102 // stop an individual listener 103 func (b *AllocBroadcaster) stop(id int) { 104 b.mu.Lock() 105 defer b.mu.Unlock() 106 107 // If broadcaster has been closed there's nothing more to do. 108 if b.closed { 109 return 110 } 111 112 l, ok := b.listeners[id] 113 if !ok { 114 // If this listener has been stopped already there's nothing 115 // more to do. 116 return 117 } 118 119 close(l) 120 delete(b.listeners, id) 121 } 122 123 // Listen returns a Listener for the broadcast channel. New listeners receive 124 // the last sent alloc update. 125 func (b *AllocBroadcaster) Listen() *AllocListener { 126 b.mu.Lock() 127 defer b.mu.Unlock() 128 if b.listeners == nil { 129 b.listeners = make(map[int]chan *structs.Allocation) 130 } 131 132 for b.listeners[b.nextId] != nil { 133 b.nextId++ 134 } 135 136 ch := make(chan *structs.Allocation, listenerCap) 137 138 // Send last update if there was one 139 if b.last != nil { 140 ch <- b.last 141 } 142 143 // Broadcaster is already closed, close this listener. Must be done 144 // after the last update was sent. 145 if b.closed { 146 close(ch) 147 } 148 149 b.listeners[b.nextId] = ch 150 151 return &AllocListener{ch, b, b.nextId} 152 } 153 154 // AllocListener implements a listening endpoint for an allocation broadcast 155 // channel. 156 type AllocListener struct { 157 // ch receives the broadcast messages. 158 ch <-chan *structs.Allocation 159 b *AllocBroadcaster 160 id int 161 } 162 163 func (l *AllocListener) Ch() <-chan *structs.Allocation { 164 return l.ch 165 } 166 167 // Close closes the Listener, disabling the receival of further messages. Safe 168 // to call more than once and concurrently with receiving on Ch. 169 func (l *AllocListener) Close() { 170 l.b.stop(l.id) 171 }