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