github.com/jgadling/terraform@v0.3.8-0.20150227214559-abd68c2c87bc/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  }