6bb0d181 |
package container |
a27b4b8c |
import ( |
45a8bba1 |
"fmt" |
7c2b085d |
"sync" |
333abbf8 |
"time" |
d33b4655 |
|
534a90a9 |
"golang.org/x/net/context"
|
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 |
abd72d40 |
// FIXME: Why do we have both paused and running if a
// container cannot be paused and running at the same time? |
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 |
dcfe9927 |
exitCode int
error string // contains last known error when starting the container |
40945fc1 |
StartedAt time.Time
FinishedAt time.Time
waitChan chan struct{} |
b6c7becb |
Health *Health |
47065b90 |
}
|
abd72d40 |
// NewState creates a default state object with a fresh channel for state changes. |
47065b90 |
func NewState() *State {
return &State{
waitChan: make(chan struct{}),
} |
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 { |
dcfe9927 |
return fmt.Sprintf("Restarting (%d) %s ago", s.exitCode, 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())
} |
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 |
|
dcfe9927 |
return fmt.Sprintf("Exited (%d) %s ago", s.exitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) |
904b0ab5 |
}
|
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 |
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" &&
s != "running" &&
s != "dead" &&
s != "created" &&
s != "exited" {
return false
}
return true
}
|
47065b90 |
func wait(waitChan <-chan struct{}, timeout time.Duration) error {
if timeout < 0 {
<-waitChan
return nil
}
select {
case <-time.After(timeout): |
a793564b |
return fmt.Errorf("Timed out: %v", timeout) |
47065b90 |
case <-waitChan:
return nil
}
}
// WaitStop waits until state is stopped. If state already stopped it returns |
fcc7ec80 |
// immediately. If you want wait forever you must supply negative timeout. |
6bb0d181 |
// Returns exit code, that was passed to SetStoppedLocking |
47065b90 |
func (s *State) WaitStop(timeout time.Duration) (int, error) { |
517ba44e |
s.Lock() |
47065b90 |
if !s.Running { |
dcfe9927 |
exitCode := s.exitCode |
517ba44e |
s.Unlock() |
47065b90 |
return exitCode, nil
}
waitChan := s.waitChan |
517ba44e |
s.Unlock() |
47065b90 |
if err := wait(waitChan, timeout); err != nil {
return -1, err
} |
dcfe9927 |
s.Lock()
defer s.Unlock()
return s.ExitCode(), nil |
47065b90 |
}
|
534a90a9 |
// WaitWithContext waits for the container to stop. Optional context can be
// passed for canceling the request. |
dcfe9927 |
func (s *State) WaitWithContext(ctx context.Context) error { |
534a90a9 |
// todo(tonistiigi): make other wait functions use this |
dcfe9927 |
s.Lock()
if !s.Running {
state := *s
defer s.Unlock()
if state.exitCode == 0 {
return nil |
534a90a9 |
} |
dcfe9927 |
return &state
}
waitChan := s.waitChan
s.Unlock()
select {
case <-waitChan:
s.Lock()
state := *s |
534a90a9 |
s.Unlock() |
dcfe9927 |
if state.exitCode == 0 {
return nil |
534a90a9 |
} |
dcfe9927 |
return &state
case <-ctx.Done():
return ctx.Err()
} |
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 {
res := s.exitCode |
47065b90 |
return res |
33e70864 |
}
|
3eb83b5b |
// SetExitCode sets current exitcode for the state. Take lock before if state |
dcfe9927 |
// may be shared.
func (s *State) SetExitCode(ec int) {
s.exitCode = ec
}
|
6bb0d181 |
// SetRunning sets the state of the container to "running". |
9c4570a9 |
func (s *State) SetRunning(pid int, initial bool) { |
dcfe9927 |
s.error = "" |
b06311a7 |
s.Running = true
s.Paused = false |
73ced636 |
s.Restarting = false |
dcfe9927 |
s.exitCode = 0 |
b06311a7 |
s.Pid = pid |
9c4570a9 |
if initial {
s.StartedAt = time.Now().UTC()
} |
a27b4b8c |
}
|
3eb83b5b |
// SetStoppedLocking locks the container state and sets it to "stopped". |
9c4570a9 |
func (s *State) SetStoppedLocking(exitStatus *ExitStatus) { |
33e70864 |
s.Lock() |
6bb0d181 |
s.SetStopped(exitStatus) |
517ba44e |
s.Unlock()
}
|
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) |
b06311a7 |
close(s.waitChan) // fire waiters for stop
s.waitChan = make(chan struct{}) |
a27b4b8c |
} |
b054569c |
|
6bb0d181 |
// SetRestartingLocking is when docker handles the auto restart of containers when they are |
a2afb2b1 |
// in the middle of a stop and being restarted again |
9c4570a9 |
func (s *State) SetRestartingLocking(exitStatus *ExitStatus) { |
a2afb2b1 |
s.Lock() |
6bb0d181 |
s.SetRestarting(exitStatus) |
a6ed9905 |
s.Unlock()
}
|
6bb0d181 |
// SetRestarting sets the container state to "restarting".
// 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
s.Pid = 0
s.FinishedAt = time.Now().UTC() |
77af7d10 |
s.setFromExitStatus(exitStatus) |
73ced636 |
close(s.waitChan) // fire waiters for stop
s.waitChan = 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) { |
dcfe9927 |
s.error = 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 |
}
|
3eb83b5b |
// 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 |
// Error returns current error for the state.
func (s *State) Error() string {
return s.error
} |