github.com/kekek/gb@v0.4.5-0.20170222120241-d4ba64b0b297/executor.go (about)

     1  package gb
     2  
     3  import (
     4  	"sync"
     5  
     6  	"github.com/pkg/errors"
     7  )
     8  
     9  // Execute executes a tree of *Actions sequentually in depth first order.
    10  func Execute(a *Action) error {
    11  	seen := make(map[*Action]error)
    12  	return execute(seen, a)
    13  }
    14  
    15  func execute(seen map[*Action]error, a *Action) error {
    16  	// step 0, have we been here before
    17  	if err, ok := seen[a]; ok {
    18  		return err
    19  	}
    20  
    21  	// step 1, build all dependencies
    22  	for _, d := range a.Deps {
    23  		if err := execute(seen, d); err != nil {
    24  			return err
    25  		}
    26  	}
    27  
    28  	// step 2, now execute ourselves
    29  	err := a.Run()
    30  	seen[a] = err
    31  	return err
    32  }
    33  
    34  // ExecuteConcurrent executes all actions in a tree concurrently.
    35  // Each Action will wait until its dependant actions are complete.
    36  func ExecuteConcurrent(a *Action, n int, interrupt <-chan struct{}) error {
    37  	var mu sync.Mutex // protects seen
    38  	seen := make(map[*Action]chan error)
    39  
    40  	get := func(result chan error) error {
    41  		err := <-result
    42  		result <- err
    43  		return err
    44  	}
    45  
    46  	permits := make(chan bool, n)
    47  	for i := 0; i < cap(permits); i++ {
    48  		permits <- true
    49  	}
    50  
    51  	// wg tracks all the outstanding actions
    52  	var wg sync.WaitGroup
    53  
    54  	var execute func(map[*Action]chan error, *Action) chan error
    55  	execute = func(seen map[*Action]chan error, a *Action) chan error {
    56  
    57  		// step 0, have we seen this action before ?
    58  		mu.Lock()
    59  		if result, ok := seen[a]; ok {
    60  			// yes! return the result channel so others can wait
    61  			//  on our action
    62  			mu.Unlock()
    63  			return result
    64  		}
    65  
    66  		// step 1, we're the first to run this action
    67  		result := make(chan error, 1)
    68  		seen[a] = result
    69  		mu.Unlock()
    70  
    71  		// queue all dependant actions.
    72  		var results []chan error
    73  		for _, dep := range a.Deps {
    74  			results = append(results, execute(seen, dep))
    75  		}
    76  
    77  		wg.Add(1)
    78  		go func() {
    79  			defer wg.Done()
    80  			// wait for dependant actions
    81  			for _, r := range results {
    82  				if err := get(r); err != nil {
    83  					result <- err
    84  					return
    85  				}
    86  			}
    87  			// wait for a permit and execute our action
    88  			select {
    89  			case <-permits:
    90  				result <- a.Run()
    91  				permits <- true
    92  			case <-interrupt:
    93  				result <- errors.New("interrupted")
    94  				return
    95  			}
    96  		}()
    97  
    98  		return result
    99  
   100  	}
   101  	err := get(execute(seen, a))
   102  	wg.Wait()
   103  	return err
   104  }