github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/drivers/shared/eventer/eventer.go (about) 1 package eventer 2 3 import ( 4 "context" 5 "sync" 6 "time" 7 8 hclog "github.com/hashicorp/go-hclog" 9 "github.com/hashicorp/nomad/plugins/drivers" 10 ) 11 12 var ( 13 // DefaultSendEventTimeout is the timeout used when publishing events to consumers 14 DefaultSendEventTimeout = 2 * time.Second 15 16 // ConsumerGCInterval is the interval at which garbage collection of consumers 17 // occures 18 ConsumerGCInterval = time.Minute 19 ) 20 21 // Eventer is a utility to control broadcast of TaskEvents to multiple consumers. 22 // It also implements the TaskEvents func in the DriverPlugin interface so that 23 // it can be embedded in a implementing driver struct. 24 type Eventer struct { 25 26 // events is a channel were events to be broadcasted are sent 27 // This channel is never closed, because it's lifetime is tied to the 28 // life of the driver and closing creates some subtile race conditions 29 // between closing it and emitting events. 30 events chan *drivers.TaskEvent 31 32 // consumers is a slice of eventConsumers to broadcast events to. 33 // access is gaurded by consumersLock RWMutex 34 consumers []*eventConsumer 35 consumersLock sync.RWMutex 36 37 // ctx to allow control of event loop shutdown 38 ctx context.Context 39 40 logger hclog.Logger 41 } 42 43 type eventConsumer struct { 44 timeout time.Duration 45 ctx context.Context 46 ch chan *drivers.TaskEvent 47 logger hclog.Logger 48 } 49 50 // NewEventer returns an Eventer with a running event loop that can be stopped 51 // by closing the given stop channel 52 func NewEventer(ctx context.Context, logger hclog.Logger) *Eventer { 53 e := &Eventer{ 54 events: make(chan *drivers.TaskEvent), 55 ctx: ctx, 56 logger: logger, 57 } 58 go e.eventLoop() 59 return e 60 } 61 62 // eventLoop is the main logic which pulls events from the channel and broadcasts 63 // them to all consumers 64 func (e *Eventer) eventLoop() { 65 for { 66 select { 67 case <-e.ctx.Done(): 68 e.logger.Trace("task event loop shutdown") 69 return 70 case event := <-e.events: 71 e.iterateConsumers(event) 72 case <-time.After(ConsumerGCInterval): 73 e.gcConsumers() 74 } 75 } 76 } 77 78 // iterateConsumers will iterate through all consumers and broadcast the event, 79 // cleaning up any consumers that have closed their context 80 func (e *Eventer) iterateConsumers(event *drivers.TaskEvent) { 81 e.consumersLock.Lock() 82 filtered := e.consumers[:0] 83 for _, consumer := range e.consumers { 84 85 // prioritize checking if context is cancelled prior 86 // to attempting to forwarding events 87 // golang select evaluations aren't predictable 88 if consumer.ctx.Err() != nil { 89 close(consumer.ch) 90 continue 91 } 92 93 select { 94 case <-time.After(consumer.timeout): 95 filtered = append(filtered, consumer) 96 e.logger.Warn("timeout sending event", "task_id", event.TaskID, "message", event.Message) 97 case <-consumer.ctx.Done(): 98 // consumer context finished, filtering it out of loop 99 close(consumer.ch) 100 case consumer.ch <- event: 101 filtered = append(filtered, consumer) 102 } 103 } 104 e.consumers = filtered 105 e.consumersLock.Unlock() 106 } 107 108 func (e *Eventer) gcConsumers() { 109 e.consumersLock.Lock() 110 filtered := e.consumers[:0] 111 for _, consumer := range e.consumers { 112 select { 113 case <-consumer.ctx.Done(): 114 // consumer context finished, filtering it out of loop 115 default: 116 filtered = append(filtered, consumer) 117 } 118 } 119 e.consumers = filtered 120 e.consumersLock.Unlock() 121 } 122 123 func (e *Eventer) newConsumer(ctx context.Context) *eventConsumer { 124 e.consumersLock.Lock() 125 defer e.consumersLock.Unlock() 126 127 consumer := &eventConsumer{ 128 ch: make(chan *drivers.TaskEvent), 129 ctx: ctx, 130 timeout: DefaultSendEventTimeout, 131 logger: e.logger, 132 } 133 e.consumers = append(e.consumers, consumer) 134 135 return consumer 136 } 137 138 // TaskEvents is an implementation of the DriverPlugin.TaskEvents function 139 func (e *Eventer) TaskEvents(ctx context.Context) (<-chan *drivers.TaskEvent, error) { 140 consumer := e.newConsumer(ctx) 141 return consumer.ch, nil 142 } 143 144 // EmitEvent can be used to broadcast a new event 145 func (e *Eventer) EmitEvent(event *drivers.TaskEvent) error { 146 147 select { 148 case <-e.ctx.Done(): 149 return e.ctx.Err() 150 case e.events <- event: 151 if e.logger.IsTrace() { 152 e.logger.Trace("emitting event", "event", event) 153 } 154 } 155 return nil 156 }