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