|
|
@@ -18,6 +18,7 @@ package compose
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
+ "sync/atomic"
|
|
|
|
|
|
"github.com/docker/compose/v2/pkg/api"
|
|
|
)
|
|
|
@@ -33,32 +34,37 @@ type logPrinter interface {
|
|
|
type printer struct {
|
|
|
queue chan api.ContainerEvent
|
|
|
consumer api.LogConsumer
|
|
|
- stopCh chan struct{}
|
|
|
+ stopped atomic.Bool
|
|
|
}
|
|
|
|
|
|
// newLogPrinter builds a LogPrinter passing containers logs to LogConsumer
|
|
|
func newLogPrinter(consumer api.LogConsumer) logPrinter {
|
|
|
queue := make(chan api.ContainerEvent)
|
|
|
- stopCh := make(chan struct{}, 1) // printer MAY stop on his own, so Stop MUST not be blocking
|
|
|
printer := printer{
|
|
|
consumer: consumer,
|
|
|
queue: queue,
|
|
|
- stopCh: stopCh,
|
|
|
}
|
|
|
return &printer
|
|
|
}
|
|
|
|
|
|
func (p *printer) Cancel() {
|
|
|
- p.queue <- api.ContainerEvent{
|
|
|
- Type: api.UserCancel,
|
|
|
- }
|
|
|
+ // note: HandleEvent is used to ensure this doesn't deadlock
|
|
|
+ p.HandleEvent(api.ContainerEvent{Type: api.UserCancel})
|
|
|
}
|
|
|
|
|
|
func (p *printer) Stop() {
|
|
|
- p.stopCh <- struct{}{}
|
|
|
+ if p.stopped.CompareAndSwap(false, true) {
|
|
|
+ // only close if this is the first call to stop
|
|
|
+ close(p.queue)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
func (p *printer) HandleEvent(event api.ContainerEvent) {
|
|
|
+ // prevent deadlocking, if the printer is done, there's no reader for
|
|
|
+ // queue, so this write could block indefinitely
|
|
|
+ if p.stopped.Load() {
|
|
|
+ return
|
|
|
+ }
|
|
|
p.queue <- event
|
|
|
}
|
|
|
|
|
|
@@ -69,61 +75,57 @@ func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error
|
|
|
exitCode int
|
|
|
)
|
|
|
containers := map[string]struct{}{}
|
|
|
- for {
|
|
|
- select {
|
|
|
- case <-p.stopCh:
|
|
|
- return exitCode, nil
|
|
|
- case event := <-p.queue:
|
|
|
- container, id := event.Container, event.ID
|
|
|
- switch event.Type {
|
|
|
- case api.UserCancel:
|
|
|
- aborting = true
|
|
|
- case api.ContainerEventAttach:
|
|
|
- if _, ok := containers[id]; ok {
|
|
|
- continue
|
|
|
- }
|
|
|
- containers[id] = struct{}{}
|
|
|
- p.consumer.Register(container)
|
|
|
- case api.ContainerEventExit, api.ContainerEventStopped, api.ContainerEventRecreated:
|
|
|
- if !event.Restarting {
|
|
|
- delete(containers, id)
|
|
|
+ for event := range p.queue {
|
|
|
+ container, id := event.Container, event.ID
|
|
|
+ switch event.Type {
|
|
|
+ case api.UserCancel:
|
|
|
+ aborting = true
|
|
|
+ case api.ContainerEventAttach:
|
|
|
+ if _, ok := containers[id]; ok {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ containers[id] = struct{}{}
|
|
|
+ p.consumer.Register(container)
|
|
|
+ case api.ContainerEventExit, api.ContainerEventStopped, api.ContainerEventRecreated:
|
|
|
+ if !event.Restarting {
|
|
|
+ delete(containers, id)
|
|
|
+ }
|
|
|
+ if !aborting {
|
|
|
+ p.consumer.Status(container, fmt.Sprintf("exited with code %d", event.ExitCode))
|
|
|
+ if event.Type == api.ContainerEventRecreated {
|
|
|
+ p.consumer.Status(container, "has been recreated")
|
|
|
}
|
|
|
+ }
|
|
|
+ if cascadeStop {
|
|
|
if !aborting {
|
|
|
- p.consumer.Status(container, fmt.Sprintf("exited with code %d", event.ExitCode))
|
|
|
- if event.Type == api.ContainerEventRecreated {
|
|
|
- p.consumer.Status(container, "has been recreated")
|
|
|
+ aborting = true
|
|
|
+ err := stopFn()
|
|
|
+ if err != nil {
|
|
|
+ return 0, err
|
|
|
}
|
|
|
}
|
|
|
- if cascadeStop {
|
|
|
- if !aborting {
|
|
|
- aborting = true
|
|
|
- err := stopFn()
|
|
|
- if err != nil {
|
|
|
- return 0, err
|
|
|
- }
|
|
|
+ if event.Type == api.ContainerEventExit {
|
|
|
+ if exitCodeFrom == "" {
|
|
|
+ exitCodeFrom = event.Service
|
|
|
}
|
|
|
- if event.Type == api.ContainerEventExit {
|
|
|
- if exitCodeFrom == "" {
|
|
|
- exitCodeFrom = event.Service
|
|
|
- }
|
|
|
- if exitCodeFrom == event.Service {
|
|
|
- exitCode = event.ExitCode
|
|
|
- }
|
|
|
+ if exitCodeFrom == event.Service {
|
|
|
+ exitCode = event.ExitCode
|
|
|
}
|
|
|
}
|
|
|
- if len(containers) == 0 {
|
|
|
- // Last container terminated, done
|
|
|
- return exitCode, nil
|
|
|
- }
|
|
|
- case api.ContainerEventLog:
|
|
|
- if !aborting {
|
|
|
- p.consumer.Log(container, event.Line)
|
|
|
- }
|
|
|
- case api.ContainerEventErr:
|
|
|
- if !aborting {
|
|
|
- p.consumer.Err(container, event.Line)
|
|
|
- }
|
|
|
+ }
|
|
|
+ if len(containers) == 0 {
|
|
|
+ // Last container terminated, done
|
|
|
+ return exitCode, nil
|
|
|
+ }
|
|
|
+ case api.ContainerEventLog:
|
|
|
+ if !aborting {
|
|
|
+ p.consumer.Log(container, event.Line)
|
|
|
+ }
|
|
|
+ case api.ContainerEventErr:
|
|
|
+ if !aborting {
|
|
|
+ p.consumer.Err(container, event.Line)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ return exitCode, nil
|
|
|
}
|