container/state.go
6bb0d181
 package container
a27b4b8c
 
 import (
cfdf84d5
 	"errors"
45a8bba1
 	"fmt"
7c2b085d
 	"sync"
333abbf8
 	"time"
d33b4655
 
534a90a9
 	"golang.org/x/net/context"
 
1a149a0e
 	"github.com/docker/docker/api/types"
4fef42ba
 	"github.com/docker/go-units"
a27b4b8c
 )
 
abd72d40
 // State holds the current container state, and has methods to get and
 // set the state. Container has an embed, which allows all of the
 // functions defined against State to run against Container.
a27b4b8c
 type State struct {
517ba44e
 	sync.Mutex
b654b624
 	// Note that `Running` and `Paused` are not mutually exclusive:
 	// When pausing a container (on Linux), the cgroups freezer is used to suspend
 	// all processes in the container. Freezing the process requires the process to
 	// be running. As a result, paused containers are both `Running` _and_ `Paused`.
40945fc1
 	Running           bool
 	Paused            bool
 	Restarting        bool
 	OOMKilled         bool
6bb0d181
 	RemovalInProgress bool // Not need for this to be persistent on disk.
40945fc1
 	Dead              bool
 	Pid               int
2998945a
 	ExitCodeValue     int    `json:"ExitCode"`
 	ErrorMsg          string `json:"Error"` // contains last known error when starting the container
40945fc1
 	StartedAt         time.Time
 	FinishedAt        time.Time
b6c7becb
 	Health            *Health
cfdf84d5
 
 	waitStop   chan struct{}
 	waitRemove chan struct{}
47065b90
 }
 
cfdf84d5
 // StateStatus is used to return container wait results.
 // Implements exec.ExitCode interface.
2998945a
 // This type is needed as State include a sync.Mutex field which make
 // copying it unsafe.
 type StateStatus struct {
 	exitCode int
cfdf84d5
 	err      error
2998945a
 }
 
 // ExitCode returns current exitcode for the state.
49211715
 func (s StateStatus) ExitCode() int {
 	return s.exitCode
2998945a
 }
 
cfdf84d5
 // Err returns current error for the state. Returns nil if the container had
 // exited on its own.
49211715
 func (s StateStatus) Err() error {
 	return s.err
2998945a
 }
 
abd72d40
 // NewState creates a default state object with a fresh channel for state changes.
47065b90
 func NewState() *State {
 	return &State{
cfdf84d5
 		waitStop:   make(chan struct{}),
 		waitRemove: make(chan struct{}),
47065b90
 	}
a27b4b8c
 }
 
904b0ab5
 // String returns a human-readable description of the state
 func (s *State) String() string {
 	if s.Running {
b054569c
 		if s.Paused {
 			return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
 		}
c4a00d54
 		if s.Restarting {
2998945a
 			return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCodeValue, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
c4a00d54
 		}
a2afb2b1
 
b6c7becb
 		if h := s.Health; h != nil {
 			return fmt.Sprintf("Up %s (%s)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)), h.String())
 		}
1a149a0e
 
d33b4655
 		return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
904b0ab5
 	}
a2afb2b1
 
6bb0d181
 	if s.RemovalInProgress {
40945fc1
 		return "Removal In Progress"
 	}
 
 	if s.Dead {
 		return "Dead"
 	}
 
dc697b1c
 	if s.StartedAt.IsZero() {
 		return "Created"
 	}
 
4002eac8
 	if s.FinishedAt.IsZero() {
 		return ""
 	}
a2afb2b1
 
2998945a
 	return fmt.Sprintf("Exited (%d) %s ago", s.ExitCodeValue, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
904b0ab5
 }
 
1a149a0e
 // HealthString returns a single string to describe health status.
 func (s *State) HealthString() string {
 	if s.Health == nil {
 		return types.NoHealthcheck
 	}
 
 	return s.Health.String()
 }
 
 // IsValidHealthString checks if the provided string is a valid container health status or not.
 func IsValidHealthString(s string) bool {
 	return s == types.Starting ||
 		s == types.Healthy ||
 		s == types.Unhealthy ||
 		s == types.NoHealthcheck
 }
 
ea09f036
 // StateString returns a single string to describe state
 func (s *State) StateString() string {
 	if s.Running {
 		if s.Paused {
 			return "paused"
 		}
 		if s.Restarting {
 			return "restarting"
 		}
 		return "running"
 	}
40945fc1
 
37466cc8
 	if s.RemovalInProgress {
 		return "removing"
 	}
 
40945fc1
 	if s.Dead {
 		return "dead"
 	}
 
dc697b1c
 	if s.StartedAt.IsZero() {
 		return "created"
 	}
 
ea09f036
 	return "exited"
 }
 
6bb0d181
 // IsValidStateString checks if the provided string is a valid container state or not.
 func IsValidStateString(s string) bool {
7bf26d44
 	if s != "paused" &&
 		s != "restarting" &&
37466cc8
 		s != "removing" &&
7bf26d44
 		s != "running" &&
 		s != "dead" &&
 		s != "created" &&
 		s != "exited" {
 		return false
 	}
 	return true
 }
 
49211715
 // WaitCondition is an enum type for different states to wait for.
 type WaitCondition int
 
 // Possible WaitCondition Values.
 //
 // WaitConditionNotRunning (default) is used to wait for any of the non-running
 // states: "created", "exited", "dead", "removing", or "removed".
 //
 // WaitConditionNextExit is used to wait for the next time the state changes
 // to a non-running state. If the state is currently "created" or "exited",
 // this would cause Wait() to block until either the container runs and exits
 // or is removed.
 //
 // WaitConditionRemoved is used to wait for the container to be removed.
 const (
 	WaitConditionNotRunning WaitCondition = iota
 	WaitConditionNextExit
 	WaitConditionRemoved
 )
47065b90
 
2b62eb43
 // Wait waits until the container is in a certain state indicated by the given
49211715
 // 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
39bcaee4
 // be nil and its ExitCode() method will return the container's exit code,
49211715
 // otherwise, the results Err() method will return an error indicating why the
 // wait operation failed.
 func (s *State) Wait(ctx context.Context, condition WaitCondition) <-chan StateStatus {
cfdf84d5
 	s.Lock()
 	defer s.Unlock()
 
49211715
 	if condition == WaitConditionNotRunning && !s.Running {
 		// Buffer so we can put it in the channel now.
 		resultC := make(chan StateStatus, 1)
cfdf84d5
 
49211715
 		// Send the current status.
 		resultC <- StateStatus{
 			exitCode: s.ExitCode(),
 			err:      s.Err(),
 		}
cfdf84d5
 
 		return resultC
36d6d76a
 	}
cfdf84d5
 
49211715
 	// If we are waiting only for removal, the waitStop channel should
 	// remain nil and block forever.
cfdf84d5
 	var waitStop chan struct{}
49211715
 	if condition < WaitConditionRemoved {
cfdf84d5
 		waitStop = s.waitStop
47065b90
 	}
 
cfdf84d5
 	// Always wait for removal, just in case the container gets removed
 	// while it is still in a "created" state, in which case it is never
 	// actually stopped.
 	waitRemove := s.waitRemove
 
49211715
 	resultC := make(chan StateStatus)
cfdf84d5
 
 	go func() {
 		select {
 		case <-ctx.Done():
 			// Context timeout or cancellation.
49211715
 			resultC <- StateStatus{
 				exitCode: -1,
 				err:      ctx.Err(),
 			}
cfdf84d5
 			return
 		case <-waitStop:
 		case <-waitRemove:
534a90a9
 		}
cfdf84d5
 
dcfe9927
 		s.Lock()
49211715
 		result := StateStatus{
 			exitCode: s.ExitCode(),
 			err:      s.Err(),
 		}
534a90a9
 		s.Unlock()
cfdf84d5
 
 		resultC <- result
 	}()
 
 	return resultC
534a90a9
 }
 
abd72d40
 // IsRunning returns whether the running flag is set. Used by Container to check whether a container is running.
33e70864
 func (s *State) IsRunning() bool {
517ba44e
 	s.Lock()
47065b90
 	res := s.Running
517ba44e
 	s.Unlock()
47065b90
 	return res
 }
33e70864
 
abd72d40
 // GetPID holds the process id of a container.
4f2a5ba3
 func (s *State) GetPID() int {
517ba44e
 	s.Lock()
47065b90
 	res := s.Pid
517ba44e
 	s.Unlock()
47065b90
 	return res
33e70864
 }
 
dcfe9927
 // ExitCode returns current exitcode for the state. Take lock before if state
 // may be shared.
 func (s *State) ExitCode() int {
2998945a
 	return s.ExitCodeValue
33e70864
 }
 
edc307cb
 // SetExitCode sets current exitcode for the state. Take lock before if state
dcfe9927
 // may be shared.
 func (s *State) SetExitCode(ec int) {
2998945a
 	s.ExitCodeValue = ec
dcfe9927
 }
 
6bb0d181
 // SetRunning sets the state of the container to "running".
9c4570a9
 func (s *State) SetRunning(pid int, initial bool) {
2998945a
 	s.ErrorMsg = ""
b06311a7
 	s.Running = true
73ced636
 	s.Restarting = false
fe1b4cfb
 	s.Paused = false
2998945a
 	s.ExitCodeValue = 0
b06311a7
 	s.Pid = pid
9c4570a9
 	if initial {
 		s.StartedAt = time.Now().UTC()
 	}
a27b4b8c
 }
 
6bb0d181
 // SetStopped sets the container state to "stopped" without locking.
9c4570a9
 func (s *State) SetStopped(exitStatus *ExitStatus) {
b06311a7
 	s.Running = false
9c4570a9
 	s.Paused = false
73ced636
 	s.Restarting = false
b06311a7
 	s.Pid = 0
 	s.FinishedAt = time.Now().UTC()
77af7d10
 	s.setFromExitStatus(exitStatus)
cfdf84d5
 	close(s.waitStop) // Fire waiters for stop
 	s.waitStop = make(chan struct{})
a27b4b8c
 }
b054569c
 
a28c389d
 // SetRestarting sets the container state to "restarting" without locking.
6bb0d181
 // It also sets the container PID to 0.
9c4570a9
 func (s *State) SetRestarting(exitStatus *ExitStatus) {
73ced636
 	// 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
fe1b4cfb
 	s.Paused = false
73ced636
 	s.Pid = 0
 	s.FinishedAt = time.Now().UTC()
77af7d10
 	s.setFromExitStatus(exitStatus)
cfdf84d5
 	close(s.waitStop) // Fire waiters for stop
 	s.waitStop = make(chan struct{})
a2afb2b1
 }
 
6bb0d181
 // SetError sets the container's error state. This is useful when we want to
fb6ee865
 // know the error that occurred when container transits to another state
 // when inspecting it
6bb0d181
 func (s *State) SetError(err error) {
2998945a
 	s.ErrorMsg = err.Error()
fb6ee865
 }
 
6bb0d181
 // IsPaused returns whether the container is paused or not.
 func (s *State) IsPaused() bool {
517ba44e
 	s.Lock()
47065b90
 	res := s.Paused
517ba44e
 	s.Unlock()
47065b90
 	return res
b054569c
 }
40945fc1
 
1d2208fe
 // IsRestarting returns whether the container is restarting or not.
 func (s *State) IsRestarting() bool {
 	s.Lock()
 	res := s.Restarting
 	s.Unlock()
 	return res
 }
 
6bb0d181
 // SetRemovalInProgress sets the container state as being removed.
a793564b
 // It returns true if the container was already in that state.
 func (s *State) SetRemovalInProgress() bool {
40945fc1
 	s.Lock()
 	defer s.Unlock()
6bb0d181
 	if s.RemovalInProgress {
a793564b
 		return true
40945fc1
 	}
6bb0d181
 	s.RemovalInProgress = true
a793564b
 	return false
40945fc1
 }
 
edc307cb
 // ResetRemovalInProgress makes the RemovalInProgress state to false.
6bb0d181
 func (s *State) ResetRemovalInProgress() {
40945fc1
 	s.Lock()
6bb0d181
 	s.RemovalInProgress = false
40945fc1
 	s.Unlock()
 }
 
6bb0d181
 // SetDead sets the container state to "dead"
 func (s *State) SetDead() {
40945fc1
 	s.Lock()
 	s.Dead = true
 	s.Unlock()
 }
dcfe9927
 
cfdf84d5
 // SetRemoved assumes this container is already in the "dead" state and
 // closes the internal waitRemove channel to unblock callers waiting for a
 // container to be removed.
 func (s *State) SetRemoved() {
 	s.Lock()
 	close(s.waitRemove) // Unblock those waiting on remove.
 	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
 }