terraform/dag/dag.go

106 lines
2.1 KiB
Go

package dag
import (
"fmt"
"sync"
)
// AcyclicGraph is a specialization of Graph that cannot have cycles. With
// this property, we get the property of sane graph traversal.
type AcyclicGraph struct {
Graph
}
// WalkFunc is the callback used for walking the graph.
type WalkFunc func(Vertex)
// Root returns the root of the DAG, or an error.
//
// Complexity: O(V)
func (g *AcyclicGraph) Root() (Vertex, error) {
roots := make([]Vertex, 0, 1)
for _, v := range g.Vertices() {
if g.UpEdges(v).Len() == 0 {
roots = append(roots, v)
}
}
if len(roots) > 1 {
// TODO(mitchellh): make this error message a lot better
return nil, fmt.Errorf("multiple roots: %#v", roots)
}
if len(roots) == 0 {
return nil, fmt.Errorf("no roots found")
}
return roots[0], nil
}
// Validate validates the DAG. A DAG is valid if it has a single root
// with no cycles.
func (g *AcyclicGraph) Validate() error {
if _, err := g.Root(); err != nil {
return err
}
var cycles [][]Vertex
for _, cycle := range StronglyConnected(&g.Graph) {
if len(cycle) > 1 {
cycles = append(cycles, cycle)
}
}
if len(cycles) > 0 {
return fmt.Errorf("cycles: %#v", cycles)
}
return nil
}
// Walk walks the graph, calling your callback as each node is visited.
// This will walk nodes in parallel if it can.
func (g *AcyclicGraph) Walk(cb WalkFunc) error {
// We require a root to walk.
root, err := g.Root()
if err != nil {
return err
}
// Build the waitgroup that signals when we're done
var wg sync.WaitGroup
wg.Add(g.vertices.Len())
doneCh := make(chan struct{})
go func() {
defer close(doneCh)
wg.Wait()
}()
// Start walking!
visitCh := make(chan Vertex, g.vertices.Len())
visitCh <- root
for {
select {
case v := <-visitCh:
go g.walkVertex(v, cb, visitCh, &wg)
case <-doneCh:
goto WALKDONE
}
}
WALKDONE:
return nil
}
func (g *AcyclicGraph) walkVertex(
v Vertex, cb WalkFunc, nextCh chan<- Vertex, wg *sync.WaitGroup) {
defer wg.Done()
// Call the callback on this vertex
cb(v)
// Walk all the children in parallel
for _, v := range g.DownEdges(v).List() {
nextCh <- v.(Vertex)
}
}