diff options
author | Valentin Rothberg <rothberg@redhat.com> | 2021-01-07 13:13:36 +0100 |
---|---|---|
committer | Valentin Rothberg <rothberg@redhat.com> | 2021-01-14 17:45:30 +0100 |
commit | d54478d8eaec9481d482942b87065af36995d39a (patch) | |
tree | 6c18acf17797a234b3c3f5e95f6e50b71a5d5b1b /libpod/container_internal.go | |
parent | d2503ae99b773db7b9dbdf3abf3be0160ac78399 (diff) | |
download | podman-d54478d8eaec9481d482942b87065af36995d39a.tar.gz podman-d54478d8eaec9481d482942b87065af36995d39a.tar.bz2 podman-d54478d8eaec9481d482942b87065af36995d39a.zip |
container stop: release lock before calling the runtime
Podman defers stopping the container to the runtime, which can take some
time. Keeping the lock while waiting for the runtime to complete the
stop procedure, prevents other commands from acquiring the lock as shown
in #8501.
To improve the user experience, release the lock before invoking the
runtime, and re-acquire the lock when the runtime is finished. Also
introduce an intermediate "stopping" to properly distinguish from
"stopped" containers etc.
Fixes: #8501
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
Diffstat (limited to 'libpod/container_internal.go')
-rw-r--r-- | libpod/container_internal.go | 43 |
1 files changed, 41 insertions, 2 deletions
diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 540230c26..028710849 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -758,7 +758,7 @@ func (c *Container) isStopped() (bool, error) { return true, err } - return !c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused), nil + return !c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused, define.ContainerStateStopping), nil } // save container state to the database @@ -1284,10 +1284,49 @@ func (c *Container) stop(timeout uint) error { return err } + // Set the container state to "stopping" and unlock the container + // before handing it over to conmon to unblock other commands. #8501 + // demonstrates nicely that a high stop timeout will block even simple + // commands such as `podman ps` from progressing if the container lock + // is held when busy-waiting for the container to be stopped. + c.state.State = define.ContainerStateStopping + if err := c.save(); err != nil { + return errors.Wrapf(err, "error saving container %s state before stopping", c.ID()) + } + if !c.batched { + c.lock.Unlock() + } + if err := c.ociRuntime.StopContainer(c, timeout, all); err != nil { return err } + if !c.batched { + c.lock.Lock() + if err := c.syncContainer(); err != nil { + switch errors.Cause(err) { + // If the container has already been removed (e.g., via + // the cleanup process), there's nothing left to do. + case define.ErrNoSuchCtr, define.ErrCtrRemoved: + return nil + default: + return err + } + } + } + + // Since we're now subject to a race condition with other processes who + // may have altered the state (and other data), let's check if the + // state has changed. If so, we should return immediately and log a + // warning. + if c.state.State != define.ContainerStateStopping { + logrus.Warnf( + "Container %q state changed from %q to %q while waiting for it to be stopped: discontinuing stop procedure as another process interfered", + c.ID(), define.ContainerStateStopping, c.state.State, + ) + return nil + } + c.newContainerEvent(events.Stop) c.state.PID = 0 @@ -2094,7 +2133,7 @@ func (c *Container) sortUserVolumes(ctrSpec *spec.Spec) ([]*ContainerNamedVolume // Check for an exit file, and handle one if present func (c *Container) checkExitFile() error { // If the container's not running, nothing to do. - if !c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused) { + if !c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused, define.ContainerStateStopping) { return nil } |