moby/daemon/restart.go
Sebastiaan van Stijn aeb8972281
daemon: daemon.containerRestart: don't cancel restart on context cancel
commit def549c8f6 passed through the context
to the daemon.ContainerStart function. As a result, restarting containers
no longer is an atomic operation, because a context cancellation could
interrupt the restart (between "stopping" and "(re)starting"), resulting
in the container being stopped, but not restarted.

Restarting a container, or more factually; making a successful request on
the `/containers/{id]/restart` endpoint, should be an atomic operation.

This patch uses a context.WithoutCancel for restart requests.

It's worth noting that daemon.containerStop already uses context.WithoutCancel,
so in that function, we'll be wrapping the context twice, but this should
likely not cause issues (just redundant for this code-path).

Before this patch, starting a container that bind-mounts the docker socket,
then restarting itself from within the container would cancel the restart
operation. The container would be stopped, but not started after that:

    docker run -dit --name myself -v /var/run/docker.sock:/var/run/docker.sock docker:cli sh
    docker exec myself sh -c 'docker restart myself'

    docker ps -a
    CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS                       PORTS     NAMES
    3a2a741c65ff   docker:cli    "docker-entrypoint.s…"   26 seconds ago   Exited (128) 7 seconds ago             myself

With this patch: the stop still cancels the exec, but does not cancel the
restart operation, and the container is started again:

    docker run -dit --name myself -v /var/run/docker.sock:/var/run/docker.sock docker:cli sh
    docker exec myself sh -c 'docker restart myself'
    docker ps
    CONTAINER ID   IMAGE        COMMAND                  CREATED              STATUS         PORTS     NAMES
    4393a01f7c75   docker:cli   "docker-entrypoint.s…"   About a minute ago   Up 4 seconds             myself

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-10-24 12:56:45 +02:00

75 lines
2.7 KiB
Go

package daemon // import "github.com/docker/docker/daemon"
import (
"context"
"fmt"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/container"
"github.com/docker/docker/internal/compatcontext"
)
// ContainerRestart stops and starts a container. It attempts to
// gracefully stop the container within the given timeout, forcefully
// stopping it if the timeout is exceeded. If given a negative
// timeout, ContainerRestart will wait forever until a graceful
// stop. Returns an error if the container cannot be found, or if
// there is an underlying error at any stage of the restart.
func (daemon *Daemon) ContainerRestart(ctx context.Context, name string, options containertypes.StopOptions) error {
ctr, err := daemon.GetContainer(name)
if err != nil {
return err
}
err = daemon.containerRestart(ctx, daemon.config(), ctr, options)
if err != nil {
return fmt.Errorf("Cannot restart container %s: %v", name, err)
}
return nil
}
// containerRestart attempts to gracefully stop and then start the
// container. When stopping, wait for the given duration in seconds to
// gracefully stop, before forcefully terminating the container. If
// given a negative duration, wait forever for a graceful stop.
func (daemon *Daemon) containerRestart(ctx context.Context, daemonCfg *configStore, container *container.Container, options containertypes.StopOptions) error {
// Restarting is expected to be an atomic operation, and cancelling
// the request should not cancel the stop -> start sequence.
ctx = compatcontext.WithoutCancel(ctx)
// Determine isolation. If not specified in the hostconfig, use daemon default.
actualIsolation := container.HostConfig.Isolation
if containertypes.Isolation.IsDefault(actualIsolation) {
actualIsolation = daemon.defaultIsolation
}
// Avoid unnecessarily unmounting and then directly mounting
// the container when the container stops and then starts
// again. We do not do this for Hyper-V isolated containers
// (implying also on Windows) as the HCS must have exclusive
// access to mount the containers filesystem inside the utility
// VM.
if !containertypes.Isolation.IsHyperV(actualIsolation) {
if err := daemon.Mount(container); err == nil {
defer daemon.Unmount(container)
}
}
if container.IsRunning() {
container.Lock()
container.HasBeenManuallyRestarted = true
container.Unlock()
err := daemon.containerStop(ctx, container, options)
if err != nil {
return err
}
}
if err := daemon.containerStart(ctx, daemonCfg, container, "", "", true); err != nil {
return err
}
daemon.LogContainerEvent(container, events.ActionRestart)
return nil
}