Merge pull request #1901 from mat007/fix-races

Fix races
This commit is contained in:
Ulysses Souza 2021-07-07 11:11:11 -03:00 committed by GitHub
commit ed0b123b75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 50 additions and 39 deletions

View File

@ -285,8 +285,7 @@ func (kc KubeClient) MapPortsToLocalhost(ctx context.Context, opts PortMappingOp
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
for serviceName, servicePorts := range opts.Services { for serviceName, servicePorts := range opts.Services {
serviceName := serviceName serviceName, servicePorts := serviceName, servicePorts
servicePorts := servicePorts
pod, err := kc.GetPod(ctx, opts.ProjectName, serviceName) pod, err := kc.GetPod(ctx, opts.ProjectName, serviceName)
if err != nil { if err != nil {
return err return err

View File

@ -55,6 +55,19 @@ const (
type convergence struct { type convergence struct {
service *composeService service *composeService
observedState map[string]Containers observedState map[string]Containers
stateMutex sync.Mutex
}
func (c *convergence) getObservedState(serviceName string) Containers {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()
return c.observedState[serviceName]
}
func (c *convergence) setObservedState(serviceName string, containers Containers) {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()
c.observedState[serviceName] = containers
} }
func newConvergence(services []string, state Containers, s *composeService) *convergence { func newConvergence(services []string, state Containers, s *composeService) *convergence {
@ -97,7 +110,7 @@ var mu sync.Mutex
// updateProject updates project after service converged, so dependent services relying on `service:xx` can refer to actual containers. // updateProject updates project after service converged, so dependent services relying on `service:xx` can refer to actual containers.
func (c *convergence) updateProject(project *types.Project, service string) { func (c *convergence) updateProject(project *types.Project, service string) {
containers := c.observedState[service] containers := c.getObservedState(service)
container := containers[0] container := containers[0]
// operation is protected by a Mutex so that we can safely update project.Services while running concurrent convergence on services // operation is protected by a Mutex so that we can safely update project.Services while running concurrent convergence on services
@ -148,7 +161,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
if err != nil { if err != nil {
return err return err
} }
containers := c.observedState[service.Name] containers := c.getObservedState(service.Name)
actual := len(containers) actual := len(containers)
updated := make(Containers, expected) updated := make(Containers, expected)
@ -157,6 +170,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
for i, container := range containers { for i, container := range containers {
if i > expected { if i > expected {
// Scale Down // Scale Down
container := container
eg.Go(func() error { eg.Go(func() error {
err := c.service.apiClient.ContainerStop(ctx, container.ID, timeout) err := c.service.apiClient.ContainerStop(ctx, container.ID, timeout)
if err != nil { if err != nil {
@ -178,7 +192,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
name := getContainerProgressName(container) name := getContainerProgressName(container)
diverged := container.Labels[api.ConfigHashLabel] != configHash diverged := container.Labels[api.ConfigHashLabel] != configHash
if diverged || recreate == api.RecreateForce || service.Extensions[extLifecycle] == forceRecreate { if diverged || recreate == api.RecreateForce || service.Extensions[extLifecycle] == forceRecreate {
i := i i, container := i, container
eg.Go(func() error { eg.Go(func() error {
recreated, err := c.service.recreateContainer(ctx, project, service, container, inherit, timeout) recreated, err := c.service.recreateContainer(ctx, project, service, container, inherit, timeout)
updated[i] = recreated updated[i] = recreated
@ -197,6 +211,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
case ContainerExited: case ContainerExited:
w.Event(progress.CreatedEvent(name)) w.Event(progress.CreatedEvent(name))
default: default:
container := container
eg.Go(func() error { eg.Go(func() error {
return c.service.startContainer(ctx, container) return c.service.startContainer(ctx, container)
}) })
@ -212,16 +227,17 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
// Scale UP // Scale UP
number := next + i number := next + i
name := getContainerName(project.Name, service, number) name := getContainerName(project.Name, service, number)
i := i
eg.Go(func() error { eg.Go(func() error {
container, err := c.service.createContainer(ctx, project, service, name, number, false, true) container, err := c.service.createContainer(ctx, project, service, name, number, false, true)
updated[actual+i-1] = container updated[actual+i] = container
return err return err
}) })
continue continue
} }
err = eg.Wait() err = eg.Wait()
c.observedState[service.Name] = updated c.setObservedState(service.Name, updated)
return err return err
} }
@ -542,11 +558,11 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
for _, c := range containers { for _, container := range containers {
container := c
if container.State == ContainerRunning { if container.State == ContainerRunning {
continue continue
} }
container := container
eg.Go(func() error { eg.Go(func() error {
eventName := getContainerProgressName(container) eventName := getContainerProgressName(container)
w.Event(progress.StartingEvent(eventName)) w.Event(progress.StartingEvent(eventName))

View File

@ -81,9 +81,8 @@ func (s *composeService) Copy(ctx context.Context, project *types.Project, opts
} }
g := errgroup.Group{} g := errgroup.Group{}
for i := range containers { for _, container := range containers {
containerID := containers[i].ID containerID := container.ID
g.Go(func() error { g.Go(func() error {
switch direction { switch direction {
case fromService: case fromService:

View File

@ -91,22 +91,22 @@ func visit(ctx context.Context, project *types.Project, traversalConfig graphTra
// Note: this could be `graph.walk` or whatever // Note: this could be `graph.walk` or whatever
func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex, traversalConfig graphTraversalConfig, fn func(context.Context, string) error) error { func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex, traversalConfig graphTraversalConfig, fn func(context.Context, string) error) error {
for _, node := range nodes { for _, node := range nodes {
n := node
// Don't start this service yet if all of its children have // Don't start this service yet if all of its children have
// not been started yet. // not been started yet.
if len(traversalConfig.filterAdjacentByStatusFn(graph, n.Service, traversalConfig.adjacentServiceStatusToSkip)) != 0 { if len(traversalConfig.filterAdjacentByStatusFn(graph, node.Service, traversalConfig.adjacentServiceStatusToSkip)) != 0 {
continue continue
} }
node := node
eg.Go(func() error { eg.Go(func() error {
err := fn(ctx, n.Service) err := fn(ctx, node.Service)
if err != nil { if err != nil {
return err return err
} }
graph.UpdateStatus(n.Service, traversalConfig.targetServiceStatus) graph.UpdateStatus(node.Service, traversalConfig.targetServiceStatus)
return run(ctx, graph, eg, traversalConfig.adjacentNodesFn(n), traversalConfig, fn) return run(ctx, graph, eg, traversalConfig.adjacentNodesFn(node), traversalConfig, fn)
}) })
} }

View File

@ -217,17 +217,17 @@ func (s *composeService) stopContainers(ctx context.Context, w progress.Writer,
func (s *composeService) removeContainers(ctx context.Context, w progress.Writer, containers []moby.Container, timeout *time.Duration, volumes bool) error { func (s *composeService) removeContainers(ctx context.Context, w progress.Writer, containers []moby.Container, timeout *time.Duration, volumes bool) error {
eg, _ := errgroup.WithContext(ctx) eg, _ := errgroup.WithContext(ctx)
for _, container := range containers { for _, container := range containers {
toDelete := container container := container
eg.Go(func() error { eg.Go(func() error {
eventName := getContainerProgressName(toDelete) eventName := getContainerProgressName(container)
w.Event(progress.StoppingEvent(eventName)) w.Event(progress.StoppingEvent(eventName))
err := s.stopContainers(ctx, w, []moby.Container{toDelete}, timeout) err := s.stopContainers(ctx, w, []moby.Container{container}, timeout)
if err != nil { if err != nil {
w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping")) w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
return err return err
} }
w.Event(progress.RemovingEvent(eventName)) w.Event(progress.RemovingEvent(eventName))
err = s.apiClient.ContainerRemove(ctx, toDelete.ID, moby.ContainerRemoveOptions{ err = s.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{
Force: true, Force: true,
RemoveVolumes: volumes, RemoveVolumes: volumes,
}) })

View File

@ -29,20 +29,20 @@ import (
) )
func (s *composeService) Logs(ctx context.Context, projectName string, consumer api.LogConsumer, options api.LogOptions) error { func (s *composeService) Logs(ctx context.Context, projectName string, consumer api.LogConsumer, options api.LogOptions) error {
list, err := s.getContainers(ctx, projectName, oneOffExclude, true, options.Services...) containers, err := s.getContainers(ctx, projectName, oneOffExclude, true, options.Services...)
if err != nil { if err != nil {
return err return err
} }
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
for _, c := range list { for _, c := range containers {
c := c
service := c.Labels[api.ServiceLabel] service := c.Labels[api.ServiceLabel]
container, err := s.apiClient.ContainerInspect(ctx, c.ID) container, err := s.apiClient.ContainerInspect(ctx, c.ID)
if err != nil { if err != nil {
return err return err
} }
name := getContainerNameWithoutProject(c)
eg.Go(func() error { eg.Go(func() error {
r, err := s.apiClient.ContainerLogs(ctx, container.ID, types.ContainerLogsOptions{ r, err := s.apiClient.ContainerLogs(ctx, container.ID, types.ContainerLogsOptions{
ShowStdout: true, ShowStdout: true,
@ -58,7 +58,6 @@ func (s *composeService) Logs(ctx context.Context, projectName string, consumer
} }
defer r.Close() // nolint errcheck defer r.Close() // nolint errcheck
name := getContainerNameWithoutProject(c)
w := utils.GetWriter(func(line string) { w := utils.GetWriter(func(line string) {
consumer.Log(name, service, line) consumer.Log(name, service, line)
}) })

View File

@ -38,9 +38,8 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
summary := make([]api.ContainerSummary, len(containers)) summary := make([]api.ContainerSummary, len(containers))
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
for i, c := range containers { for i, container := range containers {
container := c i, container := i, container
i := i
eg.Go(func() error { eg.Go(func() error {
var publishers []api.PortPublisher var publishers []api.PortPublisher
sort.Slice(container.Ports, func(i, j int) bool { sort.Slice(container.Ports, func(i, j int) bool {

View File

@ -58,8 +58,8 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
for _, srv := range project.Services { for _, service := range project.Services {
service := srv service := service
if service.Image == "" { if service.Image == "" {
w.Event(progress.Event{ w.Event(progress.Event{
ID: service.Name, ID: service.Name,

View File

@ -74,12 +74,12 @@ func (s *composeService) Remove(ctx context.Context, project *types.Project, opt
func (s *composeService) remove(ctx context.Context, containers Containers, options api.RemoveOptions) error { func (s *composeService) remove(ctx context.Context, containers Containers, options api.RemoveOptions) error {
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
for _, c := range containers { for _, container := range containers {
c := c container := container
eg.Go(func() error { eg.Go(func() error {
eventName := getContainerProgressName(c) eventName := getContainerProgressName(container)
w.Event(progress.RemovingEvent(eventName)) w.Event(progress.RemovingEvent(eventName))
err := s.apiClient.ContainerRemove(ctx, c.ID, moby.ContainerRemoveOptions{ err := s.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{
RemoveVolumes: options.Volumes, RemoveVolumes: options.Volumes,
Force: options.Force, Force: options.Force,
}) })

View File

@ -49,8 +49,8 @@ func (s *composeService) restart(ctx context.Context, project *types.Project, op
return nil return nil
} }
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
for _, c := range observedState.filter(isService(service)) { for _, container := range observedState.filter(isService(service)) {
container := c container := container
eg.Go(func() error { eg.Go(func() error {
eventName := getContainerProgressName(container) eventName := getContainerProgressName(container)
w.Event(progress.RestartingEvent(eventName)) w.Event(progress.RestartingEvent(eventName))

View File

@ -34,9 +34,8 @@ func (s *composeService) Top(ctx context.Context, projectName string, services [
} }
summary := make([]api.ContainerProcSummary, len(containers)) summary := make([]api.ContainerProcSummary, len(containers))
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
for i, c := range containers { for i, container := range containers {
container := c i, container := i, container
i := i
eg.Go(func() error { eg.Go(func() error {
topContent, err := s.apiClient.ContainerTop(ctx, container.ID, []string{}) topContent, err := s.apiClient.ContainerTop(ctx, container.ID, []string{})
if err != nil { if err != nil {