From 5cf5410bc82dd1ff3fbf03272c39f692df95108b Mon Sep 17 00:00:00 2001 From: Djordje Lukic <djordje.lukic@docker.com> Date: Sat, 21 Nov 2020 22:57:39 +0100 Subject: [PATCH] Detect cycles Signed-off-by: Djordje Lukic <djordje.lukic@docker.com> --- local/dependencies.go | 61 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/local/dependencies.go b/local/dependencies.go index 344eef918..92237370c 100644 --- a/local/dependencies.go +++ b/local/dependencies.go @@ -21,6 +21,7 @@ package local import ( "context" "fmt" + "strings" "sync" "github.com/compose-spec/compose-go/types" @@ -36,6 +37,10 @@ const ( func inDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, types.ServiceConfig) error) error { g := NewGraph(project.Services) + if b, err := g.HasCycles(); b { + return err + } + leaves := g.Leaves() eg, _ := errgroup.WithContext(ctx) @@ -50,7 +55,7 @@ func inDependencyOrder(ctx context.Context, project *types.Project, fn func(cont func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex, fn func(context.Context, types.ServiceConfig) error) error { for _, node := range nodes { n := node - // Don't start this service yet if all of their children have + // Don't start this service yet if all of its children have // not been started yet. if len(graph.FilterChildren(n.Service.Name, ServiceStopped)) != 0 { continue @@ -152,8 +157,6 @@ func (g *Graph) AddEdge(source string, destination string) error { sourceVertex.Children[destination] = destinationVertex destinationVertex.Parents[source] = sourceVertex - g.Vertices[source] = sourceVertex - g.Vertices[destination] = destinationVertex return nil } @@ -192,3 +195,55 @@ func (g *Graph) FilterChildren(key string, status ServiceStatus) []*Vertex { return res } + +func (g *Graph) HasCycles() (bool, error) { + discovered := []string{} + finished := []string{} + + for _, vertex := range g.Vertices { + path := []string{ + vertex.Key, + } + if !contains(discovered, vertex.Key) && !contains(finished, vertex.Key) { + var err error + discovered, finished, err = g.visit(vertex.Key, path, discovered, finished) + + if err != nil { + return true, err + } + } + } + + return false, nil +} + +func (g *Graph) visit(key string, path []string, discovered []string, finished []string) ([]string, []string, error) { + discovered = append(discovered, key) + + for _, v := range g.Vertices[key].Children { + path := append(path, v.Key) + if contains(discovered, v.Key) { + return nil, nil, fmt.Errorf("cycle found: %s", strings.Join(path, " -> ")) + } + + if !contains(finished, v.Key) { + if _, _, err := g.visit(v.Key, path, discovered, finished); err != nil { + return nil, nil, err + } + } + } + + discovered = remove(discovered, key) + finished = append(finished, key) + return discovered, finished, nil +} + +func remove(slice []string, item string) []string { + var s []string + for _, i := range slice { + if i != item { + s = append(s, i) + } + } + return s +}