github.com/rmenn/terraform@v0.3.8-0.20150225065417-fc84b3a78802/dag/dag.go (about) 1 package dag 2 3 import ( 4 "fmt" 5 "strings" 6 "sync" 7 8 "github.com/hashicorp/go-multierror" 9 ) 10 11 // AcyclicGraph is a specialization of Graph that cannot have cycles. With 12 // this property, we get the property of sane graph traversal. 13 type AcyclicGraph struct { 14 Graph 15 } 16 17 // WalkFunc is the callback used for walking the graph. 18 type WalkFunc func(Vertex) error 19 20 // Root returns the root of the DAG, or an error. 21 // 22 // Complexity: O(V) 23 func (g *AcyclicGraph) Root() (Vertex, error) { 24 roots := make([]Vertex, 0, 1) 25 for _, v := range g.Vertices() { 26 if g.UpEdges(v).Len() == 0 { 27 roots = append(roots, v) 28 } 29 } 30 31 if len(roots) > 1 { 32 // TODO(mitchellh): make this error message a lot better 33 return nil, fmt.Errorf("multiple roots: %#v", roots) 34 } 35 36 if len(roots) == 0 { 37 return nil, fmt.Errorf("no roots found") 38 } 39 40 return roots[0], nil 41 } 42 43 // Validate validates the DAG. A DAG is valid if it has a single root 44 // with no cycles. 45 func (g *AcyclicGraph) Validate() error { 46 if _, err := g.Root(); err != nil { 47 return err 48 } 49 50 // Look for cycles of more than 1 component 51 var err error 52 var cycles [][]Vertex 53 for _, cycle := range StronglyConnected(&g.Graph) { 54 if len(cycle) > 1 { 55 cycles = append(cycles, cycle) 56 } 57 } 58 if len(cycles) > 0 { 59 for _, cycle := range cycles { 60 cycleStr := make([]string, len(cycle)) 61 for j, vertex := range cycle { 62 cycleStr[j] = VertexName(vertex) 63 } 64 65 err = multierror.Append(err, fmt.Errorf( 66 "Cycle: %s", strings.Join(cycleStr, ", "))) 67 } 68 } 69 70 // Look for cycles to self 71 for _, e := range g.Edges() { 72 if e.Source() == e.Target() { 73 err = multierror.Append(err, fmt.Errorf( 74 "Self reference: %s", VertexName(e.Source()))) 75 } 76 } 77 78 return err 79 } 80 81 // Walk walks the graph, calling your callback as each node is visited. 82 // This will walk nodes in parallel if it can. Because the walk is done 83 // in parallel, the error returned will be a multierror. 84 func (g *AcyclicGraph) Walk(cb WalkFunc) error { 85 // Cache the vertices since we use it multiple times 86 vertices := g.Vertices() 87 88 // Build the waitgroup that signals when we're done 89 var wg sync.WaitGroup 90 wg.Add(len(vertices)) 91 doneCh := make(chan struct{}) 92 go func() { 93 defer close(doneCh) 94 wg.Wait() 95 }() 96 97 // The map of channels to watch to wait for vertices to finish 98 vertMap := make(map[Vertex]chan struct{}) 99 for _, v := range vertices { 100 vertMap[v] = make(chan struct{}) 101 } 102 103 // The map of whether a vertex errored or not during the walk 104 var errLock sync.Mutex 105 var errs error 106 errMap := make(map[Vertex]bool) 107 for _, v := range vertices { 108 // Build our list of dependencies and the list of channels to 109 // wait on until we start executing for this vertex. 110 depsRaw := g.DownEdges(v).List() 111 deps := make([]Vertex, len(depsRaw)) 112 depChs := make([]<-chan struct{}, len(deps)) 113 for i, raw := range depsRaw { 114 deps[i] = raw.(Vertex) 115 depChs[i] = vertMap[deps[i]] 116 } 117 118 // Get our channel so that we can close it when we're done 119 ourCh := vertMap[v] 120 121 // Start the goroutine to wait for our dependencies 122 readyCh := make(chan bool) 123 go func(deps []Vertex, chs []<-chan struct{}, readyCh chan<- bool) { 124 // First wait for all the dependencies 125 for _, ch := range chs { 126 <-ch 127 } 128 129 // Then, check the map to see if any of our dependencies failed 130 errLock.Lock() 131 defer errLock.Unlock() 132 for _, dep := range deps { 133 if errMap[dep] { 134 readyCh <- false 135 return 136 } 137 } 138 139 readyCh <- true 140 }(deps, depChs, readyCh) 141 142 // Start the goroutine that executes 143 go func(v Vertex, doneCh chan<- struct{}, readyCh <-chan bool) { 144 defer close(doneCh) 145 defer wg.Done() 146 147 var err error 148 if ready := <-readyCh; ready { 149 err = cb(v) 150 } 151 152 errLock.Lock() 153 defer errLock.Unlock() 154 if err != nil { 155 errMap[v] = true 156 errs = multierror.Append(errs, err) 157 } 158 }(v, ourCh, readyCh) 159 } 160 161 <-doneCh 162 return errs 163 }