github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/nomad/deploymentwatcher/batcher.go (about)

     1  package deploymentwatcher
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/hashicorp/nomad/nomad/structs"
     9  )
    10  
    11  // EvalBatcher is used to batch the creation of evaluations
    12  type EvalBatcher struct {
    13  	// batch is the batching duration
    14  	batch time.Duration
    15  
    16  	// raft is used to actually commit the evaluations
    17  	raft DeploymentRaftEndpoints
    18  
    19  	// future to be returned to callers
    20  	f *EvalFuture
    21  
    22  	// inCh is used to pass evaluations to the daemon process
    23  	inCh chan *structs.Evaluation
    24  
    25  	// ctx is used to exit the daemon batcher
    26  	ctx context.Context
    27  
    28  	l sync.Mutex
    29  }
    30  
    31  // NewEvalBatcher returns an EvalBatcher that uses the passed raft endpoints to
    32  // create the evaluations and exits the batcher when the passed exit channel is
    33  // closed.
    34  func NewEvalBatcher(batchDuration time.Duration, raft DeploymentRaftEndpoints, ctx context.Context) *EvalBatcher {
    35  	b := &EvalBatcher{
    36  		batch: batchDuration,
    37  		raft:  raft,
    38  		ctx:   ctx,
    39  		inCh:  make(chan *structs.Evaluation, 10),
    40  	}
    41  
    42  	go b.batcher()
    43  	return b
    44  }
    45  
    46  // CreateEval batches the creation of the evaluation and returns a future that
    47  // tracks the evaluations creation.
    48  func (b *EvalBatcher) CreateEval(e *structs.Evaluation) *EvalFuture {
    49  	b.l.Lock()
    50  	if b.f == nil {
    51  		b.f = NewEvalFuture()
    52  	}
    53  	b.l.Unlock()
    54  
    55  	b.inCh <- e
    56  	return b.f
    57  }
    58  
    59  // batcher is the long lived batcher goroutine
    60  func (b *EvalBatcher) batcher() {
    61  	var timerCh <-chan time.Time
    62  	evals := make(map[string]*structs.Evaluation)
    63  	for {
    64  		select {
    65  		case <-b.ctx.Done():
    66  			return
    67  		case e := <-b.inCh:
    68  			if timerCh == nil {
    69  				timerCh = time.After(b.batch)
    70  			}
    71  
    72  			evals[e.DeploymentID] = e
    73  		case <-timerCh:
    74  			// Capture the future
    75  			b.l.Lock()
    76  			f := b.f
    77  			b.f = nil
    78  			b.l.Unlock()
    79  
    80  			// Shouldn't be possible
    81  			if f == nil {
    82  				panic("no future")
    83  			}
    84  
    85  			// Capture the evals
    86  			all := make([]*structs.Evaluation, 0, len(evals))
    87  			for _, e := range evals {
    88  				all = append(all, e)
    89  			}
    90  
    91  			// Upsert the evals in a go routine
    92  			go f.Set(b.raft.UpsertEvals(all))
    93  
    94  			// Reset the evals list and timer
    95  			evals = make(map[string]*structs.Evaluation)
    96  			timerCh = nil
    97  
    98  		}
    99  	}
   100  }
   101  
   102  // EvalFuture is a future that can be used to retrieve the index the eval was
   103  // created at or any error in the creation process
   104  type EvalFuture struct {
   105  	index  uint64
   106  	err    error
   107  	waitCh chan struct{}
   108  }
   109  
   110  // NewEvalFuture returns a new EvalFuture
   111  func NewEvalFuture() *EvalFuture {
   112  	return &EvalFuture{
   113  		waitCh: make(chan struct{}),
   114  	}
   115  }
   116  
   117  // Set sets the results of the future, unblocking any client.
   118  func (f *EvalFuture) Set(index uint64, err error) {
   119  	f.index = index
   120  	f.err = err
   121  	close(f.waitCh)
   122  }
   123  
   124  // Results returns the creation index and any error.
   125  func (f *EvalFuture) Results() (uint64, error) {
   126  	<-f.waitCh
   127  	return f.index, f.err
   128  }