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  }