github.com/HashDataInc/packer@v1.3.2/helper/multistep/basic_runner.go (about)

     1  package multistep
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"sync/atomic"
     7  )
     8  
     9  type runState int32
    10  
    11  const (
    12  	stateIdle runState = iota
    13  	stateRunning
    14  	stateCancelling
    15  )
    16  
    17  // BasicRunner is a Runner that just runs the given slice of steps.
    18  type BasicRunner struct {
    19  	// Steps is a slice of steps to run. Once set, this should _not_ be
    20  	// modified.
    21  	Steps []Step
    22  
    23  	cancel context.CancelFunc
    24  	doneCh chan struct{}
    25  	state  runState
    26  	l      sync.Mutex
    27  }
    28  
    29  func (b *BasicRunner) Run(state StateBag) {
    30  	ctx, cancel := context.WithCancel(context.Background())
    31  
    32  	b.l.Lock()
    33  	if b.state != stateIdle {
    34  		panic("already running")
    35  	}
    36  
    37  	doneCh := make(chan struct{})
    38  	b.cancel = cancel
    39  	b.doneCh = doneCh
    40  	b.state = stateRunning
    41  	b.l.Unlock()
    42  
    43  	defer func() {
    44  		b.l.Lock()
    45  		b.cancel = nil
    46  		b.doneCh = nil
    47  		b.state = stateIdle
    48  		close(doneCh)
    49  		b.l.Unlock()
    50  	}()
    51  
    52  	// This goroutine listens for cancels and puts the StateCancelled key
    53  	// as quickly as possible into the state bag to mark it.
    54  	go func() {
    55  		select {
    56  		case <-ctx.Done():
    57  			// Flag cancel and wait for finish
    58  			state.Put(StateCancelled, true)
    59  			<-doneCh
    60  		case <-doneCh:
    61  		}
    62  	}()
    63  
    64  	for _, step := range b.Steps {
    65  		// We also check for cancellation here since we can't be sure
    66  		// the goroutine that is running to set it actually ran.
    67  		if runState(atomic.LoadInt32((*int32)(&b.state))) == stateCancelling {
    68  			state.Put(StateCancelled, true)
    69  			break
    70  		}
    71  
    72  		action := step.Run(ctx, state)
    73  		defer step.Cleanup(state)
    74  
    75  		if _, ok := state.GetOk(StateCancelled); ok {
    76  			break
    77  		}
    78  
    79  		if action == ActionHalt {
    80  			state.Put(StateHalted, true)
    81  			break
    82  		}
    83  	}
    84  }
    85  
    86  func (b *BasicRunner) Cancel() {
    87  	b.l.Lock()
    88  	switch b.state {
    89  	case stateIdle:
    90  		// Not running, so Cancel is... done.
    91  		b.l.Unlock()
    92  		return
    93  	case stateRunning:
    94  		// Running, so mark that we cancelled and set the state
    95  		b.cancel()
    96  		b.state = stateCancelling
    97  		fallthrough
    98  	case stateCancelling:
    99  		// Already cancelling, so just wait until we're done
   100  		ch := b.doneCh
   101  		b.l.Unlock()
   102  		<-ch
   103  	}
   104  }