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
} |