github.com/aca02djr/gb@v0.4.1/executor.go (about)

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