github.com/emate/nomad@v0.8.2-wo-binpacking/nomad/deploymentwatcher/batcher.go (about)

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