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 }