2021-06-07 14:21:55 +02:00
|
|
|
/*
|
|
|
|
Copyright 2020 Docker Compose CLI authors
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package compose
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/signal"
|
2024-04-20 08:09:26 +02:00
|
|
|
"sync/atomic"
|
2021-06-07 14:21:55 +02:00
|
|
|
"syscall"
|
|
|
|
|
2023-11-08 10:19:24 +01:00
|
|
|
"github.com/compose-spec/compose-go/v2/types"
|
2025-05-29 10:37:19 +02:00
|
|
|
cerrdefs "github.com/containerd/errdefs"
|
2021-06-07 14:21:55 +02:00
|
|
|
"github.com/docker/cli/cli"
|
2024-03-08 14:07:51 +00:00
|
|
|
"github.com/docker/compose/v2/cmd/formatter"
|
up: fix various race/deadlock conditions on exit (#10934)
If running `up` in foreground mode (i.e. not `-d`),
when exiting via `Ctrl-C`, Compose stops all the
services it launched directly as part of that `up`
command.
In one of the E2E tests (`TestUpDependenciesNotStopped`),
this was occasionally flaking because the stop
behavior was racy: the return might not block on
the stop operation because it gets added to the
error group in a goroutine. As a result, it was
possible for no services to get terminated on exit.
There were a few other related pieces here that
I uncovered and tried to fix while stressing this.
For example, the printer could cause a deadlock if
an event was sent to it after it stopped.
Also, an error group wasn't really appropriate here;
each goroutine is a different operation for printing,
signal-handling, etc. If one part fails, we don't
actually want printing to stop, for example. This has
been switched to a `multierror.Group`, which has the
same API but coalesces errors instead of canceling a
context the moment the first one fails and returning
that single error.
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-08-31 10:47:14 -04:00
|
|
|
"github.com/docker/compose/v2/internal/tracing"
|
2022-12-07 14:43:07 +01:00
|
|
|
"github.com/docker/compose/v2/pkg/api"
|
|
|
|
"github.com/docker/compose/v2/pkg/progress"
|
2024-03-08 14:07:51 +00:00
|
|
|
"github.com/eiannone/keyboard"
|
up: fix various race/deadlock conditions on exit (#10934)
If running `up` in foreground mode (i.e. not `-d`),
when exiting via `Ctrl-C`, Compose stops all the
services it launched directly as part of that `up`
command.
In one of the E2E tests (`TestUpDependenciesNotStopped`),
this was occasionally flaking because the stop
behavior was racy: the return might not block on
the stop operation because it gets added to the
error group in a goroutine. As a result, it was
possible for no services to get terminated on exit.
There were a few other related pieces here that
I uncovered and tried to fix while stressing this.
For example, the printer could cause a deadlock if
an event was sent to it after it stopped.
Also, an error group wasn't really appropriate here;
each goroutine is a different operation for printing,
signal-handling, etc. If one part fails, we don't
actually want printing to stop, for example. This has
been switched to a `multierror.Group`, which has the
same API but coalesces errors instead of canceling a
context the moment the first one fails and returning
that single error.
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-08-31 10:47:14 -04:00
|
|
|
"github.com/hashicorp/go-multierror"
|
2024-03-28 15:02:00 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2021-06-07 14:21:55 +02:00
|
|
|
)
|
|
|
|
|
2023-12-20 17:45:51 +00:00
|
|
|
func (s *composeService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error { //nolint:gocyclo
|
2024-02-12 14:52:58 +00:00
|
|
|
err := progress.Run(ctx, tracing.SpanWrapFunc("project/up", tracing.ProjectOptions(ctx, project), func(ctx context.Context) error {
|
2024-04-15 08:54:55 +02:00
|
|
|
err := s.create(ctx, project, options.Create)
|
2021-06-07 14:21:55 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-08-24 10:50:44 +02:00
|
|
|
if options.Start.Attach == nil {
|
2022-02-05 10:27:52 +03:30
|
|
|
return s.start(ctx, project.Name, options.Start, nil)
|
2021-08-24 10:50:44 +02:00
|
|
|
}
|
|
|
|
return nil
|
2023-07-18 23:53:10 -04:00
|
|
|
}), s.stdinfo())
|
2021-06-07 14:21:55 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if options.Start.Attach == nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-05-04 15:08:38 +02:00
|
|
|
if s.dryRun {
|
fix linting issues with golangci-lint 1.60.2
pkg/watch/watcher_darwin.go:96:16: Error return value of `d.stream.Start` is not checked (errcheck)
d.stream.Start()
^
pkg/prompt/prompt.go:97:12: Error return value of `fmt.Fprint` is not checked (errcheck)
fmt.Fprint(u.stdout, message)
^
pkg/prompt/prompt.go:99:12: Error return value of `fmt.Scanln` is not checked (errcheck)
fmt.Scanln(&answer)
^
cmd/formatter/logs.go:118:15: Error return value of `fmt.Fprintf` is not checked (errcheck)
fmt.Fprintf(w, "%s%s%s\n", p.prefix, timestamp, line)
^
cmd/formatter/logs.go:120:15: Error return value of `fmt.Fprintf` is not checked (errcheck)
fmt.Fprintf(w, "%s%s\n", p.prefix, line)
^
pkg/progress/json.go:67:15: Error return value of `fmt.Fprintln` is not checked (errcheck)
fmt.Fprintln(p.out, string(marshal))
^
pkg/progress/json.go:87:15: Error return value of `fmt.Fprintln` is not checked (errcheck)
fmt.Fprintln(p.out, string(marshal))
^
pkg/progress/plain.go:47:14: Error return value of `fmt.Fprintln` is not checked (errcheck)
fmt.Fprintln(p.out, prefix, e.ID, e.Text, e.StatusText)
^
pkg/progress/tty.go:162:12: Error return value of `fmt.Fprint` is not checked (errcheck)
fmt.Fprint(w.out, b.Column(0).ANSI)
^
pkg/progress/tty.go:165:12: Error return value of `fmt.Fprint` is not checked (errcheck)
fmt.Fprint(w.out, aec.Hide)
^
pkg/compose/attach.go:53:13: Error return value of `fmt.Fprintf` is not checked (errcheck)
fmt.Fprintf(s.stdout(), "Attaching to %s\n", strings.Join(names, ", "))
^
pkg/compose/compose.go:194:6: emptyStringTest: replace `len(dependencies) > 0` with `dependencies != ""` (gocritic)
if len(dependencies) > 0 {
^
pkg/compose/convergence.go:461:2: builtinShadow: shadowing of predeclared identifier: max (gocritic)
max := 0
^
pkg/compose/run.go:127:5: emptyStringTest: replace `len(opts.User) > 0` with `opts.User != ""` (gocritic)
if len(opts.User) > 0 {
^
pkg/compose/run.go:139:5: emptyStringTest: replace `len(opts.WorkingDir) > 0` with `opts.WorkingDir != ""` (gocritic)
if len(opts.WorkingDir) > 0 {
^
pkg/compose/viz.go:91:8: emptyStringTest: replace `len(portConfig.HostIP) > 0` with `portConfig.HostIP != ""` (gocritic)
if len(portConfig.HostIP) > 0 {
^
cmd/compatibility/convert.go:66:6: emptyStringTest: replace `len(arg) > 0` with `arg != ""` (gocritic)
if len(arg) > 0 && arg[0] != '-' {
^
pkg/e2e/watch_test.go:208:25: printf: non-constant format string in call to gotest.tools/v3/poll.Continue (govet)
return poll.Continue(res.Combined())
^
pkg/e2e/watch_test.go:290:25: printf: non-constant format string in call to gotest.tools/v3/poll.Continue (govet)
return poll.Continue(r.Combined())
^
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-11 12:52:55 +02:00
|
|
|
_, _ = fmt.Fprintln(s.stdout(), "end of 'compose up' output, interactive run is not supported in dry-run mode")
|
2023-05-04 15:08:38 +02:00
|
|
|
return err
|
|
|
|
}
|
2021-06-07 14:21:55 +02:00
|
|
|
|
2023-10-24 18:15:26 +02:00
|
|
|
var eg multierror.Group
|
|
|
|
|
up: fix various race/deadlock conditions on exit (#10934)
If running `up` in foreground mode (i.e. not `-d`),
when exiting via `Ctrl-C`, Compose stops all the
services it launched directly as part of that `up`
command.
In one of the E2E tests (`TestUpDependenciesNotStopped`),
this was occasionally flaking because the stop
behavior was racy: the return might not block on
the stop operation because it gets added to the
error group in a goroutine. As a result, it was
possible for no services to get terminated on exit.
There were a few other related pieces here that
I uncovered and tried to fix while stressing this.
For example, the printer could cause a deadlock if
an event was sent to it after it stopped.
Also, an error group wasn't really appropriate here;
each goroutine is a different operation for printing,
signal-handling, etc. If one part fails, we don't
actually want printing to stop, for example. This has
been switched to a `multierror.Group`, which has the
same API but coalesces errors instead of canceling a
context the moment the first one fails and returning
that single error.
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-08-31 10:47:14 -04:00
|
|
|
// if we get a second signal during shutdown, we kill the services
|
|
|
|
// immediately, so the channel needs to have sufficient capacity or
|
|
|
|
// we might miss a signal while setting up the second channel read
|
|
|
|
// (this is also why signal.Notify is used vs signal.NotifyContext)
|
|
|
|
signalChan := make(chan os.Signal, 2)
|
2023-10-24 18:15:26 +02:00
|
|
|
defer close(signalChan)
|
2024-04-20 08:09:26 +02:00
|
|
|
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
defer signal.Stop(signalChan)
|
|
|
|
var isTerminated atomic.Bool
|
2023-11-28 17:30:25 +03:00
|
|
|
printer := newLogPrinter(options.Start.Attach)
|
2023-10-24 18:15:26 +02:00
|
|
|
|
2025-06-06 09:57:19 +02:00
|
|
|
watcher, err := NewWatcher(project, options, s.watch)
|
|
|
|
if err != nil && options.Start.Watch {
|
|
|
|
return err
|
2025-05-22 18:04:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var navigationMenu *formatter.LogKeyboard
|
2025-02-07 16:55:00 +01:00
|
|
|
var kEvents <-chan keyboard.KeyEvent
|
|
|
|
if options.Start.NavigationMenu {
|
|
|
|
kEvents, err = keyboard.GetKeys(100)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warnf("could not start menu, an error occurred while starting: %v", err)
|
|
|
|
options.Start.NavigationMenu = false
|
|
|
|
} else {
|
|
|
|
defer keyboard.Close() //nolint:errcheck
|
|
|
|
isDockerDesktopActive := s.isDesktopIntegrationActive()
|
2025-05-22 18:04:38 +02:00
|
|
|
tracing.KeyboardMetrics(ctx, options.Start.NavigationMenu, isDockerDesktopActive, watcher != nil)
|
|
|
|
navigationMenu = formatter.NewKeyboardManager(isDockerDesktopActive, signalChan, options.Start.Watch, watcher)
|
2025-02-07 16:55:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-24 18:15:26 +02:00
|
|
|
doneCh := make(chan bool)
|
up: fix various race/deadlock conditions on exit (#10934)
If running `up` in foreground mode (i.e. not `-d`),
when exiting via `Ctrl-C`, Compose stops all the
services it launched directly as part of that `up`
command.
In one of the E2E tests (`TestUpDependenciesNotStopped`),
this was occasionally flaking because the stop
behavior was racy: the return might not block on
the stop operation because it gets added to the
error group in a goroutine. As a result, it was
possible for no services to get terminated on exit.
There were a few other related pieces here that
I uncovered and tried to fix while stressing this.
For example, the printer could cause a deadlock if
an event was sent to it after it stopped.
Also, an error group wasn't really appropriate here;
each goroutine is a different operation for printing,
signal-handling, etc. If one part fails, we don't
actually want printing to stop, for example. This has
been switched to a `multierror.Group`, which has the
same API but coalesces errors instead of canceling a
context the moment the first one fails and returning
that single error.
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-08-31 10:47:14 -04:00
|
|
|
eg.Go(func() error {
|
2023-10-24 18:15:26 +02:00
|
|
|
first := true
|
2023-12-20 17:45:51 +00:00
|
|
|
gracefulTeardown := func() {
|
|
|
|
printer.Cancel()
|
fix linting issues with golangci-lint 1.60.2
pkg/watch/watcher_darwin.go:96:16: Error return value of `d.stream.Start` is not checked (errcheck)
d.stream.Start()
^
pkg/prompt/prompt.go:97:12: Error return value of `fmt.Fprint` is not checked (errcheck)
fmt.Fprint(u.stdout, message)
^
pkg/prompt/prompt.go:99:12: Error return value of `fmt.Scanln` is not checked (errcheck)
fmt.Scanln(&answer)
^
cmd/formatter/logs.go:118:15: Error return value of `fmt.Fprintf` is not checked (errcheck)
fmt.Fprintf(w, "%s%s%s\n", p.prefix, timestamp, line)
^
cmd/formatter/logs.go:120:15: Error return value of `fmt.Fprintf` is not checked (errcheck)
fmt.Fprintf(w, "%s%s\n", p.prefix, line)
^
pkg/progress/json.go:67:15: Error return value of `fmt.Fprintln` is not checked (errcheck)
fmt.Fprintln(p.out, string(marshal))
^
pkg/progress/json.go:87:15: Error return value of `fmt.Fprintln` is not checked (errcheck)
fmt.Fprintln(p.out, string(marshal))
^
pkg/progress/plain.go:47:14: Error return value of `fmt.Fprintln` is not checked (errcheck)
fmt.Fprintln(p.out, prefix, e.ID, e.Text, e.StatusText)
^
pkg/progress/tty.go:162:12: Error return value of `fmt.Fprint` is not checked (errcheck)
fmt.Fprint(w.out, b.Column(0).ANSI)
^
pkg/progress/tty.go:165:12: Error return value of `fmt.Fprint` is not checked (errcheck)
fmt.Fprint(w.out, aec.Hide)
^
pkg/compose/attach.go:53:13: Error return value of `fmt.Fprintf` is not checked (errcheck)
fmt.Fprintf(s.stdout(), "Attaching to %s\n", strings.Join(names, ", "))
^
pkg/compose/compose.go:194:6: emptyStringTest: replace `len(dependencies) > 0` with `dependencies != ""` (gocritic)
if len(dependencies) > 0 {
^
pkg/compose/convergence.go:461:2: builtinShadow: shadowing of predeclared identifier: max (gocritic)
max := 0
^
pkg/compose/run.go:127:5: emptyStringTest: replace `len(opts.User) > 0` with `opts.User != ""` (gocritic)
if len(opts.User) > 0 {
^
pkg/compose/run.go:139:5: emptyStringTest: replace `len(opts.WorkingDir) > 0` with `opts.WorkingDir != ""` (gocritic)
if len(opts.WorkingDir) > 0 {
^
pkg/compose/viz.go:91:8: emptyStringTest: replace `len(portConfig.HostIP) > 0` with `portConfig.HostIP != ""` (gocritic)
if len(portConfig.HostIP) > 0 {
^
cmd/compatibility/convert.go:66:6: emptyStringTest: replace `len(arg) > 0` with `arg != ""` (gocritic)
if len(arg) > 0 && arg[0] != '-' {
^
pkg/e2e/watch_test.go:208:25: printf: non-constant format string in call to gotest.tools/v3/poll.Continue (govet)
return poll.Continue(res.Combined())
^
pkg/e2e/watch_test.go:290:25: printf: non-constant format string in call to gotest.tools/v3/poll.Continue (govet)
return poll.Continue(r.Combined())
^
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-11 12:52:55 +02:00
|
|
|
_, _ = fmt.Fprintln(s.stdinfo(), "Gracefully stopping... (press Ctrl+C again to force)")
|
2023-12-20 17:45:51 +00:00
|
|
|
eg.Go(func() error {
|
2024-04-20 08:09:26 +02:00
|
|
|
err := s.Stop(context.WithoutCancel(ctx), project.Name, api.StopOptions{
|
2023-12-20 17:45:51 +00:00
|
|
|
Services: options.Create.Services,
|
|
|
|
Project: project,
|
|
|
|
})
|
2024-04-20 08:09:26 +02:00
|
|
|
isTerminated.Store(true)
|
2023-12-20 17:45:51 +00:00
|
|
|
return err
|
|
|
|
})
|
|
|
|
first = false
|
|
|
|
}
|
2024-03-08 14:07:51 +00:00
|
|
|
|
2023-10-24 18:15:26 +02:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-doneCh:
|
2025-05-22 18:04:38 +02:00
|
|
|
if watcher != nil {
|
|
|
|
return watcher.Stop()
|
|
|
|
}
|
2023-10-24 18:15:26 +02:00
|
|
|
return nil
|
2023-12-20 17:45:51 +00:00
|
|
|
case <-ctx.Done():
|
|
|
|
if first {
|
|
|
|
gracefulTeardown()
|
|
|
|
}
|
2023-10-24 18:15:26 +02:00
|
|
|
case <-signalChan:
|
|
|
|
if first {
|
2025-05-22 18:04:38 +02:00
|
|
|
keyboard.Close() //nolint:errcheck
|
2023-12-20 17:45:51 +00:00
|
|
|
gracefulTeardown()
|
2024-04-12 19:37:35 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
eg.Go(func() error {
|
2024-04-20 08:09:26 +02:00
|
|
|
err := s.kill(context.WithoutCancel(ctx), project.Name, api.KillOptions{
|
2024-04-12 19:37:35 +02:00
|
|
|
Services: options.Create.Services,
|
|
|
|
Project: project,
|
|
|
|
All: true,
|
|
|
|
})
|
|
|
|
// Ignore errors indicating that some of the containers were already stopped or removed.
|
2025-05-29 10:37:19 +02:00
|
|
|
if cerrdefs.IsNotFound(err) || cerrdefs.IsConflict(err) {
|
2024-04-12 19:37:35 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
})
|
2024-04-12 22:08:36 +02:00
|
|
|
return nil
|
2024-03-08 14:07:51 +00:00
|
|
|
case event := <-kEvents:
|
2025-05-22 18:04:38 +02:00
|
|
|
navigationMenu.HandleKeyEvents(ctx, event, project, options)
|
2023-10-24 18:15:26 +02:00
|
|
|
}
|
up: fix various race/deadlock conditions on exit (#10934)
If running `up` in foreground mode (i.e. not `-d`),
when exiting via `Ctrl-C`, Compose stops all the
services it launched directly as part of that `up`
command.
In one of the E2E tests (`TestUpDependenciesNotStopped`),
this was occasionally flaking because the stop
behavior was racy: the return might not block on
the stop operation because it gets added to the
error group in a goroutine. As a result, it was
possible for no services to get terminated on exit.
There were a few other related pieces here that
I uncovered and tried to fix while stressing this.
For example, the printer could cause a deadlock if
an event was sent to it after it stopped.
Also, an error group wasn't really appropriate here;
each goroutine is a different operation for printing,
signal-handling, etc. If one part fails, we don't
actually want printing to stop, for example. This has
been switched to a `multierror.Group`, which has the
same API but coalesces errors instead of canceling a
context the moment the first one fails and returning
that single error.
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-08-31 10:47:14 -04:00
|
|
|
}
|
|
|
|
})
|
2021-06-07 14:21:55 +02:00
|
|
|
|
|
|
|
var exitCode int
|
|
|
|
eg.Go(func() error {
|
2024-04-03 09:51:11 +02:00
|
|
|
code, err := printer.Run(options.Start.OnExit, options.Start.ExitCodeFrom, func() error {
|
fix linting issues with golangci-lint 1.60.2
pkg/watch/watcher_darwin.go:96:16: Error return value of `d.stream.Start` is not checked (errcheck)
d.stream.Start()
^
pkg/prompt/prompt.go:97:12: Error return value of `fmt.Fprint` is not checked (errcheck)
fmt.Fprint(u.stdout, message)
^
pkg/prompt/prompt.go:99:12: Error return value of `fmt.Scanln` is not checked (errcheck)
fmt.Scanln(&answer)
^
cmd/formatter/logs.go:118:15: Error return value of `fmt.Fprintf` is not checked (errcheck)
fmt.Fprintf(w, "%s%s%s\n", p.prefix, timestamp, line)
^
cmd/formatter/logs.go:120:15: Error return value of `fmt.Fprintf` is not checked (errcheck)
fmt.Fprintf(w, "%s%s\n", p.prefix, line)
^
pkg/progress/json.go:67:15: Error return value of `fmt.Fprintln` is not checked (errcheck)
fmt.Fprintln(p.out, string(marshal))
^
pkg/progress/json.go:87:15: Error return value of `fmt.Fprintln` is not checked (errcheck)
fmt.Fprintln(p.out, string(marshal))
^
pkg/progress/plain.go:47:14: Error return value of `fmt.Fprintln` is not checked (errcheck)
fmt.Fprintln(p.out, prefix, e.ID, e.Text, e.StatusText)
^
pkg/progress/tty.go:162:12: Error return value of `fmt.Fprint` is not checked (errcheck)
fmt.Fprint(w.out, b.Column(0).ANSI)
^
pkg/progress/tty.go:165:12: Error return value of `fmt.Fprint` is not checked (errcheck)
fmt.Fprint(w.out, aec.Hide)
^
pkg/compose/attach.go:53:13: Error return value of `fmt.Fprintf` is not checked (errcheck)
fmt.Fprintf(s.stdout(), "Attaching to %s\n", strings.Join(names, ", "))
^
pkg/compose/compose.go:194:6: emptyStringTest: replace `len(dependencies) > 0` with `dependencies != ""` (gocritic)
if len(dependencies) > 0 {
^
pkg/compose/convergence.go:461:2: builtinShadow: shadowing of predeclared identifier: max (gocritic)
max := 0
^
pkg/compose/run.go:127:5: emptyStringTest: replace `len(opts.User) > 0` with `opts.User != ""` (gocritic)
if len(opts.User) > 0 {
^
pkg/compose/run.go:139:5: emptyStringTest: replace `len(opts.WorkingDir) > 0` with `opts.WorkingDir != ""` (gocritic)
if len(opts.WorkingDir) > 0 {
^
pkg/compose/viz.go:91:8: emptyStringTest: replace `len(portConfig.HostIP) > 0` with `portConfig.HostIP != ""` (gocritic)
if len(portConfig.HostIP) > 0 {
^
cmd/compatibility/convert.go:66:6: emptyStringTest: replace `len(arg) > 0` with `arg != ""` (gocritic)
if len(arg) > 0 && arg[0] != '-' {
^
pkg/e2e/watch_test.go:208:25: printf: non-constant format string in call to gotest.tools/v3/poll.Continue (govet)
return poll.Continue(res.Combined())
^
pkg/e2e/watch_test.go:290:25: printf: non-constant format string in call to gotest.tools/v3/poll.Continue (govet)
return poll.Continue(r.Combined())
^
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-11 12:52:55 +02:00
|
|
|
_, _ = fmt.Fprintln(s.stdinfo(), "Aborting on container exit...")
|
2023-10-24 18:15:26 +02:00
|
|
|
return progress.Run(ctx, func(ctx context.Context) error {
|
|
|
|
return s.Stop(ctx, project.Name, api.StopOptions{
|
|
|
|
Services: options.Create.Services,
|
|
|
|
Project: project,
|
|
|
|
})
|
|
|
|
}, s.stdinfo())
|
|
|
|
})
|
2021-06-07 14:21:55 +02:00
|
|
|
exitCode = code
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
|
2025-05-22 18:04:38 +02:00
|
|
|
if options.Start.Watch && watcher != nil {
|
|
|
|
err = watcher.Start(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-02-19 15:38:45 +01:00
|
|
|
}
|
|
|
|
|
2024-06-01 16:39:34 -05:00
|
|
|
// We use the parent context without cancellation as we manage sigterm to stop the stack
|
2024-04-20 08:09:26 +02:00
|
|
|
err = s.start(context.WithoutCancel(ctx), project.Name, options.Start, printer.HandleEvent)
|
|
|
|
if err != nil && !isTerminated.Load() { // Ignore error if the process is terminated
|
2021-06-07 14:21:55 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-04-20 08:09:26 +02:00
|
|
|
// Signal for the signal-handler goroutines to stop
|
|
|
|
close(doneCh)
|
|
|
|
|
2022-12-07 14:43:07 +01:00
|
|
|
printer.Stop()
|
2023-10-24 18:15:26 +02:00
|
|
|
|
up: fix various race/deadlock conditions on exit (#10934)
If running `up` in foreground mode (i.e. not `-d`),
when exiting via `Ctrl-C`, Compose stops all the
services it launched directly as part of that `up`
command.
In one of the E2E tests (`TestUpDependenciesNotStopped`),
this was occasionally flaking because the stop
behavior was racy: the return might not block on
the stop operation because it gets added to the
error group in a goroutine. As a result, it was
possible for no services to get terminated on exit.
There were a few other related pieces here that
I uncovered and tried to fix while stressing this.
For example, the printer could cause a deadlock if
an event was sent to it after it stopped.
Also, an error group wasn't really appropriate here;
each goroutine is a different operation for printing,
signal-handling, etc. If one part fails, we don't
actually want printing to stop, for example. This has
been switched to a `multierror.Group`, which has the
same API but coalesces errors instead of canceling a
context the moment the first one fails and returning
that single error.
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2023-08-31 10:47:14 -04:00
|
|
|
err = eg.Wait().ErrorOrNil()
|
2021-06-07 14:21:55 +02:00
|
|
|
if exitCode != 0 {
|
|
|
|
errMsg := ""
|
|
|
|
if err != nil {
|
|
|
|
errMsg = err.Error()
|
|
|
|
}
|
|
|
|
return cli.StatusError{StatusCode: exitCode, Status: errMsg}
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|