mirror of
https://github.com/docker/compose.git
synced 2025-07-21 12:44:54 +02:00
Use a dependency graph to start services
The algorithm is like so: * get all the leaves of the graph, these are all the service that don't have any dependency * once a service is started we take the list of its parents (dependents) * if all the dependencies of each of those dependents are started then we can start it as well * if not then we continue to the next dependent Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
This commit is contained in:
parent
162c6036b2
commit
1f43b83409
@ -174,7 +174,7 @@ func (s *local) Down(ctx context.Context, projectName string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
w := progress.ContextWriter(ctx)
|
w := progress.ContextWriter(ctx)
|
||||||
for _, c := range list {
|
for _, c := range list {
|
||||||
container := c
|
container := c
|
||||||
@ -625,7 +625,7 @@ func (s *local) ensureNetwork(ctx context.Context, n types.NetworkConfig) error
|
|||||||
StatusText: "Create",
|
StatusText: "Create",
|
||||||
Done: false,
|
Done: false,
|
||||||
})
|
})
|
||||||
if _, err := s.containerService.apiClient.NetworkCreate(context.Background(), n.Name, createOpts); err != nil {
|
if _, err := s.containerService.apiClient.NetworkCreate(ctx, n.Name, createOpts); err != nil {
|
||||||
return errors.Wrapf(err, "failed to create network %s", n.Name)
|
return errors.Wrapf(err, "failed to create network %s", n.Name)
|
||||||
}
|
}
|
||||||
w.Event(progress.Event{
|
w.Event(progress.Event{
|
||||||
|
@ -57,7 +57,7 @@ func (s *local) ensureService(ctx context.Context, project *types.Project, servi
|
|||||||
|
|
||||||
scale := getScale(service)
|
scale := getScale(service)
|
||||||
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
if len(actual) < scale {
|
if len(actual) < scale {
|
||||||
next, err := nextContainerNumber(actual)
|
next, err := nextContainerNumber(actual)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -115,7 +115,7 @@ func (s *local) ensureService(ctx context.Context, project *types.Project, servi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *local) waitDependencies(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
func (s *local) waitDependencies(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
for dep, config := range service.DependsOn {
|
for dep, config := range service.DependsOn {
|
||||||
switch config.Condition {
|
switch config.Condition {
|
||||||
case "service_healthy":
|
case "service_healthy":
|
||||||
|
@ -20,99 +20,175 @@ package local
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
func inDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, types.ServiceConfig) error) error {
|
type ServiceStatus int
|
||||||
graph := buildDependencyGraph(project.Services)
|
|
||||||
|
const (
|
||||||
|
ServiceStopped ServiceStatus = iota
|
||||||
|
ServiceStarted
|
||||||
|
)
|
||||||
|
|
||||||
|
func inDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, types.ServiceConfig) error) error {
|
||||||
|
g := NewGraph(project.Services)
|
||||||
|
leaves := g.Leaves()
|
||||||
|
|
||||||
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
|
eg.Go(func() error {
|
||||||
|
return run(ctx, g, eg, leaves, fn)
|
||||||
|
})
|
||||||
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
|
||||||
results := make(chan string)
|
|
||||||
errors := make(chan error)
|
|
||||||
scheduled := map[string]bool{}
|
|
||||||
for len(graph) > 0 {
|
|
||||||
for _, n := range graph.independents() {
|
|
||||||
service := n.service
|
|
||||||
if scheduled[service.Name] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
eg.Go(func() error {
|
|
||||||
err := fn(ctx, service)
|
|
||||||
if err != nil {
|
|
||||||
errors <- err
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
results <- service.Name
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
scheduled[service.Name] = true
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case result := <-results:
|
|
||||||
graph.resolved(result)
|
|
||||||
case err := <-errors:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return eg.Wait()
|
return eg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
type dependencyGraph map[string]node
|
// Note: this could be `graph.walk` or whatever
|
||||||
|
func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex, fn func(context.Context, types.ServiceConfig) error) error {
|
||||||
type node struct {
|
for _, node := range nodes {
|
||||||
service types.ServiceConfig
|
n := node
|
||||||
dependencies []string
|
// Don't start this service yet if all of their children have
|
||||||
dependent []string
|
// not been started yet.
|
||||||
}
|
if len(graph.FilterChildren(n.Service.Name, ServiceStopped)) != 0 {
|
||||||
|
continue
|
||||||
func (graph dependencyGraph) independents() []node {
|
|
||||||
var nodes []node
|
|
||||||
for _, node := range graph {
|
|
||||||
if len(node.dependencies) == 0 {
|
|
||||||
nodes = append(nodes, node)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
|
err := fn(ctx, n.Service)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
graph.UpdateStatus(n.Service.Name, ServiceStarted)
|
||||||
|
|
||||||
|
return run(ctx, graph, eg, n.GetParents(), fn)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return nodes
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (graph dependencyGraph) resolved(result string) {
|
type Graph struct {
|
||||||
for _, parent := range graph[result].dependent {
|
Vertices map[string]*Vertex
|
||||||
node := graph[parent]
|
lock sync.RWMutex
|
||||||
node.dependencies = remove(node.dependencies, result)
|
|
||||||
graph[parent] = node
|
|
||||||
}
|
|
||||||
delete(graph, result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildDependencyGraph(services types.Services) dependencyGraph {
|
type Vertex struct {
|
||||||
graph := dependencyGraph{}
|
Key string
|
||||||
for _, s := range services {
|
Service types.ServiceConfig
|
||||||
graph[s.Name] = node{
|
Status ServiceStatus
|
||||||
service: s,
|
Children map[string]*Vertex
|
||||||
}
|
Parents map[string]*Vertex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Vertex) GetParents() []*Vertex {
|
||||||
|
var res []*Vertex
|
||||||
|
for _, p := range v.Parents {
|
||||||
|
res = append(res, p)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGraph(services types.Services) *Graph {
|
||||||
|
graph := &Graph{
|
||||||
|
lock: sync.RWMutex{},
|
||||||
|
Vertices: map[string]*Vertex{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range services {
|
||||||
|
graph.AddVertex(s.Name, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range services {
|
for _, s := range services {
|
||||||
node := graph[s.Name]
|
|
||||||
for _, name := range s.GetDependencies() {
|
for _, name := range s.GetDependencies() {
|
||||||
dependency := graph[name]
|
graph.AddEdge(s.Name, name)
|
||||||
node.dependencies = append(node.dependencies, name)
|
|
||||||
dependency.dependent = append(dependency.dependent, s.Name)
|
|
||||||
graph[name] = dependency
|
|
||||||
}
|
}
|
||||||
graph[s.Name] = node
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return graph
|
return graph
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(slice []string, item string) []string {
|
// We then create a constructor function for the Vertex
|
||||||
var s []string
|
func NewVertex(key string, service types.ServiceConfig) *Vertex {
|
||||||
for _, i := range slice {
|
return &Vertex{
|
||||||
if i != item {
|
Key: key,
|
||||||
s = append(s, i)
|
Service: service,
|
||||||
|
Status: ServiceStopped,
|
||||||
|
Parents: map[string]*Vertex{},
|
||||||
|
Children: map[string]*Vertex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) AddVertex(key string, service types.ServiceConfig) {
|
||||||
|
g.lock.Lock()
|
||||||
|
defer g.lock.Unlock()
|
||||||
|
|
||||||
|
v := NewVertex(key, service)
|
||||||
|
g.Vertices[key] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) AddEdge(source string, destination string) error {
|
||||||
|
g.lock.Lock()
|
||||||
|
defer g.lock.Unlock()
|
||||||
|
|
||||||
|
sourceVertex := g.Vertices[source]
|
||||||
|
destinationVertex := g.Vertices[destination]
|
||||||
|
|
||||||
|
if sourceVertex == nil {
|
||||||
|
return fmt.Errorf("could not find %s", source)
|
||||||
|
}
|
||||||
|
if destinationVertex == nil {
|
||||||
|
return fmt.Errorf("could not find %s", destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If they are already connected
|
||||||
|
if _, ok := sourceVertex.Children[destination]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceVertex.Children[destination] = destinationVertex
|
||||||
|
destinationVertex.Parents[source] = sourceVertex
|
||||||
|
|
||||||
|
g.Vertices[source] = sourceVertex
|
||||||
|
g.Vertices[destination] = destinationVertex
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) Leaves() []*Vertex {
|
||||||
|
g.lock.Lock()
|
||||||
|
defer g.lock.Unlock()
|
||||||
|
|
||||||
|
var res []*Vertex
|
||||||
|
for _, v := range g.Vertices {
|
||||||
|
if len(v.Children) == 0 {
|
||||||
|
res = append(res, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) UpdateStatus(key string, status ServiceStatus) {
|
||||||
|
g.lock.Lock()
|
||||||
|
defer g.lock.Unlock()
|
||||||
|
g.Vertices[key].Status = status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) FilterChildren(key string, status ServiceStatus) []*Vertex {
|
||||||
|
g.lock.Lock()
|
||||||
|
defer g.lock.Unlock()
|
||||||
|
|
||||||
|
var res []*Vertex
|
||||||
|
vertex := g.Vertices[key]
|
||||||
|
|
||||||
|
for _, child := range vertex.Children {
|
||||||
|
if child.Status == status {
|
||||||
|
res = append(res, child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user