github.com/excavador/gb@v0.4.4-0.20160819171537-b99310e56bc2/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 }