mirror of
https://github.com/moby/moby.git
synced 2026-08-03 06:30:59 +00:00
This method did not provide any special handling for accessing the field, and did not handle locking. Let's remove it for now to not pretend we're doing anything more safe than directly accessing the field. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
443 lines
13 KiB
Go
443 lines
13 KiB
Go
package container
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/docker/go-units"
|
|
"github.com/moby/moby/api/types/container"
|
|
libcontainerdtypes "github.com/moby/moby/v2/daemon/internal/libcontainerd/types"
|
|
)
|
|
|
|
// State holds the current container state, and has methods to get and
|
|
// set the state. State is embedded in the [Container] struct.
|
|
//
|
|
// State contains an exported [sync.Mutex] which is used as a global lock
|
|
// for both the State and the Container it's embedded in.
|
|
type State struct {
|
|
// This Mutex is exported by design and is used as a global lock
|
|
// for both the State and the Container it's embedded in.
|
|
sync.Mutex
|
|
// Note that [State.Running], [State.Restarting], and [State.Paused] are
|
|
// not mutually exclusive.
|
|
//
|
|
// When pausing a container (on Linux), the freezer cgroup is used to suspend
|
|
// all processes in the container. Freezing the process requires the process to
|
|
// be running. As a result, paused containers can have both [State.Running]
|
|
// and [State.Paused] set to true.
|
|
//
|
|
// In a similar fashion, [State.Running] and [State.Restarting] can both
|
|
// be true in a situation where a container is in process of being restarted.
|
|
// Refer to [State.StateString] for order of precedence.
|
|
Running bool
|
|
Paused bool
|
|
Restarting bool
|
|
OOMKilled bool
|
|
RemovalInProgress bool `json:"-"` // No need for this to be persistent on disk.
|
|
Dead bool
|
|
Pid int
|
|
ExitCode int `json:"ExitCode"`
|
|
ErrorMsg string `json:"Error"` // contains last known error during container start, stop, or remove
|
|
StartedAt time.Time
|
|
FinishedAt time.Time
|
|
Health *Health
|
|
Removed bool `json:"-"`
|
|
|
|
stopWaiters []chan<- StateStatus
|
|
removeOnlyWaiters []chan<- StateStatus
|
|
|
|
// The libcontainerd reference fields are unexported to force consumers
|
|
// to access them through the getter methods with multi-valued returns
|
|
// so that they can't forget to nil-check: the code won't compile unless
|
|
// the nil-check result is explicitly consumed or discarded.
|
|
|
|
ctr libcontainerdtypes.Container
|
|
task libcontainerdtypes.Task
|
|
}
|
|
|
|
// StateStatus is used to return container wait results.
|
|
// Implements exec.ExitCode interface.
|
|
// This type is needed as State include a sync.Mutex field which make
|
|
// copying it unsafe.
|
|
type StateStatus struct {
|
|
exitCode int
|
|
err error
|
|
}
|
|
|
|
// ExitCode returns current exitcode for the state.
|
|
func (s StateStatus) ExitCode() int {
|
|
return s.exitCode
|
|
}
|
|
|
|
// Err returns current error for the state. Returns nil if the container had
|
|
// exited on its own.
|
|
func (s StateStatus) Err() error {
|
|
return s.err
|
|
}
|
|
|
|
// String returns a human-readable description of the state
|
|
func (s *State) String() string {
|
|
if s.Running {
|
|
if s.Paused {
|
|
return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
|
|
}
|
|
if s.Restarting {
|
|
return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
|
|
}
|
|
|
|
if h := s.Health; h != nil {
|
|
return fmt.Sprintf("Up %s (%s)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)), h.String())
|
|
}
|
|
|
|
return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
|
|
}
|
|
|
|
if s.RemovalInProgress {
|
|
return "Removal In Progress"
|
|
}
|
|
|
|
if s.Dead {
|
|
return "Dead"
|
|
}
|
|
|
|
if s.StartedAt.IsZero() {
|
|
return "Created"
|
|
}
|
|
|
|
if s.FinishedAt.IsZero() {
|
|
return ""
|
|
}
|
|
|
|
return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
|
|
}
|
|
|
|
// StateString returns the container's current [ContainerState], based on the
|
|
// [State.Running], [State.Paused], [State.Restarting], [State.RemovalInProgress],
|
|
// [State.StartedAt] and [State.Dead] fields.
|
|
func (s *State) StateString() container.ContainerState {
|
|
if s.Running {
|
|
if s.Paused {
|
|
return container.StatePaused
|
|
}
|
|
if s.Restarting {
|
|
return container.StateRestarting
|
|
}
|
|
return container.StateRunning
|
|
}
|
|
|
|
// TODO(thaJeztah): should [State.Removed] also have an corresponding string?
|
|
// TODO(thaJeztah): should [State.OOMKilled] be taken into account anywhere?
|
|
if s.RemovalInProgress {
|
|
return container.StateRemoving
|
|
}
|
|
|
|
if s.Dead {
|
|
return container.StateDead
|
|
}
|
|
|
|
if s.StartedAt.IsZero() {
|
|
return container.StateCreated
|
|
}
|
|
|
|
return container.StateExited
|
|
}
|
|
|
|
// Wait waits until the container is in a certain state indicated by the given
|
|
// condition. A context must be used for cancelling the request, controlling
|
|
// timeouts, and avoiding goroutine leaks. Wait must be called without holding
|
|
// the state lock. Returns a channel from which the caller will receive the
|
|
// result. If the container exited on its own, the result's Err() method will
|
|
// be nil and its ExitCode() method will return the container's exit code,
|
|
// otherwise, the results Err() method will return an error indicating why the
|
|
// wait operation failed.
|
|
func (s *State) Wait(ctx context.Context, condition container.WaitCondition) <-chan StateStatus {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
|
|
// Buffer so we can put status and finish even nobody receives it.
|
|
resultC := make(chan StateStatus, 1)
|
|
|
|
if s.conditionAlreadyMet(condition) {
|
|
resultC <- StateStatus{
|
|
exitCode: s.ExitCode,
|
|
err: s.Err(),
|
|
}
|
|
|
|
return resultC
|
|
}
|
|
|
|
waitC := make(chan StateStatus, 1)
|
|
|
|
// Removal wakes up both removeOnlyWaiters and stopWaiters
|
|
// Container could be removed while still in "created" state
|
|
// in which case it is never actually stopped
|
|
if condition == container.WaitConditionRemoved {
|
|
s.removeOnlyWaiters = append(s.removeOnlyWaiters, waitC)
|
|
} else {
|
|
s.stopWaiters = append(s.stopWaiters, waitC)
|
|
}
|
|
|
|
go func() {
|
|
select {
|
|
case <-ctx.Done():
|
|
// Context timeout or cancellation.
|
|
resultC <- StateStatus{
|
|
exitCode: -1,
|
|
err: ctx.Err(),
|
|
}
|
|
return
|
|
case status := <-waitC:
|
|
resultC <- status
|
|
}
|
|
}()
|
|
|
|
return resultC
|
|
}
|
|
|
|
func (s *State) conditionAlreadyMet(condition container.WaitCondition) bool {
|
|
switch condition {
|
|
case container.WaitConditionNotRunning:
|
|
return !s.Running
|
|
case container.WaitConditionRemoved:
|
|
return s.Removed
|
|
default:
|
|
// TODO(thaJeztah): how do we want to handle "WaitConditionNextExit"?
|
|
return false
|
|
}
|
|
}
|
|
|
|
// IsRunning returns whether the [State.Running] flag is set.
|
|
//
|
|
// Note that [State.Running], [State.Restarting], and [State.Paused] are
|
|
// not mutually exclusive.
|
|
//
|
|
// When pausing a container (on Linux), the freezer cgroup is used to suspend
|
|
// all processes in the container. Freezing the process requires the process to
|
|
// be running. As a result, paused containers can have both [State.Running]
|
|
// and [State.Paused] set to true.
|
|
//
|
|
// In a similar fashion, [State.Running] and [State.Restarting] can both
|
|
// be true in a situation where a container is in process of being restarted.
|
|
// Refer to [State.StateString] for order of precedence.
|
|
func (s *State) IsRunning() bool {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
return s.Running
|
|
}
|
|
|
|
// GetPID holds the process id of a container.
|
|
func (s *State) GetPID() int {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
return s.Pid
|
|
}
|
|
|
|
// SetExitCode sets current exitcode for the state. Take lock before if state
|
|
// may be shared.
|
|
func (s *State) SetExitCode(ec int) {
|
|
s.ExitCode = ec
|
|
}
|
|
|
|
// SetRunning sets the running state along with StartedAt time.
|
|
func (s *State) SetRunning(ctr libcontainerdtypes.Container, tsk libcontainerdtypes.Task, start time.Time) {
|
|
s.setRunning(ctr, tsk, &start)
|
|
}
|
|
|
|
// SetRunningExternal sets the running state without setting the `StartedAt` time (used for containers not started by Docker instead of SetRunning).
|
|
func (s *State) SetRunningExternal(ctr libcontainerdtypes.Container, tsk libcontainerdtypes.Task) {
|
|
s.setRunning(ctr, tsk, nil)
|
|
}
|
|
|
|
// setRunning sets the state of the container to "running".
|
|
func (s *State) setRunning(ctr libcontainerdtypes.Container, tsk libcontainerdtypes.Task, start *time.Time) {
|
|
s.ErrorMsg = ""
|
|
s.Paused = false
|
|
s.Running = true
|
|
s.Restarting = false
|
|
if start != nil {
|
|
s.Paused = false
|
|
}
|
|
s.ExitCode = 0
|
|
s.ctr = ctr
|
|
s.task = tsk
|
|
if tsk != nil {
|
|
s.Pid = int(tsk.Pid())
|
|
} else {
|
|
s.Pid = 0
|
|
}
|
|
s.OOMKilled = false
|
|
if start != nil {
|
|
s.StartedAt = start.UTC()
|
|
}
|
|
}
|
|
|
|
// SetStopped sets the container state to "stopped" without locking.
|
|
func (s *State) SetStopped(exitStatus *ExitStatus) {
|
|
s.Running = false
|
|
s.Paused = false
|
|
s.Restarting = false
|
|
s.Pid = 0
|
|
if exitStatus.ExitedAt.IsZero() {
|
|
s.FinishedAt = time.Now().UTC()
|
|
} else {
|
|
s.FinishedAt = exitStatus.ExitedAt
|
|
}
|
|
s.ExitCode = exitStatus.ExitCode
|
|
|
|
s.notifyAndClear(&s.stopWaiters)
|
|
}
|
|
|
|
// SetRestarting sets the container state to "restarting" without locking.
|
|
// It also sets the container PID to 0.
|
|
func (s *State) SetRestarting(exitStatus *ExitStatus) {
|
|
// we should consider the container running when it is restarting because of
|
|
// all the checks in docker around rm/stop/etc
|
|
s.Running = true
|
|
s.Restarting = true
|
|
s.Paused = false
|
|
s.Pid = 0
|
|
s.FinishedAt = time.Now().UTC()
|
|
s.ExitCode = exitStatus.ExitCode
|
|
|
|
s.notifyAndClear(&s.stopWaiters)
|
|
}
|
|
|
|
// SetError sets the container's error state. This is useful when we want to
|
|
// know the error that occurred when container transits to another state
|
|
// when inspecting it
|
|
func (s *State) SetError(err error) {
|
|
s.ErrorMsg = ""
|
|
if err != nil {
|
|
s.ErrorMsg = err.Error()
|
|
}
|
|
}
|
|
|
|
// IsPaused returns whether the container is paused.
|
|
//
|
|
// Note that [State.Running], [State.Restarting], and [State.Paused] are
|
|
// not mutually exclusive.
|
|
//
|
|
// When pausing a container (on Linux), the freezer cgroup is used to suspend
|
|
// all processes in the container. Freezing the process requires the process to
|
|
// be running. As a result, paused containers can have both [State.Running]
|
|
// and [State.Paused] set to true.
|
|
//
|
|
// In a similar fashion, [State.Running] and [State.Restarting] can both
|
|
// be true in a situation where a container is in process of being restarted.
|
|
// Refer to [State.StateString] for order of precedence.
|
|
func (s *State) IsPaused() bool {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
return s.Paused
|
|
}
|
|
|
|
// IsRestarting returns whether the container is restarting.
|
|
//
|
|
// Note that [State.Running], [State.Restarting], and [State.Paused] are
|
|
// not mutually exclusive.
|
|
//
|
|
// When pausing a container (on Linux), the freezer cgroup is used to suspend
|
|
// all processes in the container. Freezing the process requires the process to
|
|
// be running. As a result, paused containers can have both [State.Running]
|
|
// and [State.Paused] set to true.
|
|
//
|
|
// In a similar fashion, [State.Running] and [State.Restarting] can both
|
|
// be true in a situation where a container is in process of being restarted.
|
|
// Refer to [State.StateString] for order of precedence.
|
|
func (s *State) IsRestarting() bool {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
return s.Restarting
|
|
}
|
|
|
|
// SetRemovalInProgress sets the container state as being removed.
|
|
// It returns true if the container was already in that state.
|
|
func (s *State) SetRemovalInProgress() bool {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
if s.RemovalInProgress {
|
|
return true
|
|
}
|
|
s.RemovalInProgress = true
|
|
return false
|
|
}
|
|
|
|
// ResetRemovalInProgress makes the RemovalInProgress state to false.
|
|
func (s *State) ResetRemovalInProgress() {
|
|
s.Lock()
|
|
s.RemovalInProgress = false
|
|
s.Unlock()
|
|
}
|
|
|
|
// IsRemovalInProgress returns whether the RemovalInProgress flag is set.
|
|
// Used by Container to check whether a container is being removed.
|
|
func (s *State) IsRemovalInProgress() bool {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
return s.RemovalInProgress
|
|
}
|
|
|
|
// IsDead returns whether the Dead flag is set. Used by Container to check whether a container is dead.
|
|
func (s *State) IsDead() bool {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
return s.Dead
|
|
}
|
|
|
|
// SetRemoved assumes this container is already in the "dead" state and notifies all waiters.
|
|
func (s *State) SetRemoved() {
|
|
s.SetRemovalError(nil)
|
|
}
|
|
|
|
// SetRemovalError is to be called in case a container remove failed.
|
|
// It sets an error and notifies all waiters.
|
|
func (s *State) SetRemovalError(err error) {
|
|
s.SetError(err)
|
|
s.Lock()
|
|
s.Removed = true
|
|
s.notifyAndClear(&s.removeOnlyWaiters)
|
|
s.notifyAndClear(&s.stopWaiters)
|
|
s.Unlock()
|
|
}
|
|
|
|
// Err returns an error if there is one.
|
|
func (s *State) Err() error {
|
|
if s.ErrorMsg != "" {
|
|
return errors.New(s.ErrorMsg)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *State) notifyAndClear(waiters *[]chan<- StateStatus) {
|
|
result := StateStatus{
|
|
exitCode: s.ExitCode,
|
|
err: s.Err(),
|
|
}
|
|
|
|
for _, c := range *waiters {
|
|
c <- result
|
|
}
|
|
*waiters = nil
|
|
}
|
|
|
|
// C8dContainer returns a reference to the libcontainerd Container object for
|
|
// the container and whether the reference is valid.
|
|
//
|
|
// The container lock must be held when calling this method.
|
|
func (s *State) C8dContainer() (_ libcontainerdtypes.Container, ok bool) {
|
|
return s.ctr, s.ctr != nil
|
|
}
|
|
|
|
// Task returns a reference to the libcontainerd Task object for the container
|
|
// and whether the reference is valid.
|
|
//
|
|
// The container lock must be held when calling this method.
|
|
//
|
|
// See also: (*Container).GetRunningTask().
|
|
func (s *State) Task() (_ libcontainerdtypes.Task, ok bool) {
|
|
return s.task, s.task != nil
|
|
}
|