container/state.go
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
 }