github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/container/state.go (about)

     1  package container // import "github.com/demonoid81/moby/container"
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/demonoid81/moby/api/types"
    11  	units "github.com/docker/go-units"
    12  )
    13  
    14  // State holds the current container state, and has methods to get and
    15  // set the state. Container has an embed, which allows all of the
    16  // functions defined against State to run against Container.
    17  type State struct {
    18  	sync.Mutex
    19  	// Note that `Running` and `Paused` are not mutually exclusive:
    20  	// When pausing a container (on Linux), the freezer cgroup is used to suspend
    21  	// all processes in the container. Freezing the process requires the process to
    22  	// be running. As a result, paused containers are both `Running` _and_ `Paused`.
    23  	Running           bool
    24  	Paused            bool
    25  	Restarting        bool
    26  	OOMKilled         bool
    27  	RemovalInProgress bool // Not need for this to be persistent on disk.
    28  	Dead              bool
    29  	Pid               int
    30  	ExitCodeValue     int    `json:"ExitCode"`
    31  	ErrorMsg          string `json:"Error"` // contains last known error during container start, stop, or remove
    32  	StartedAt         time.Time
    33  	FinishedAt        time.Time
    34  	Health            *Health
    35  
    36  	waitStop   chan struct{}
    37  	waitRemove chan struct{}
    38  }
    39  
    40  // StateStatus is used to return container wait results.
    41  // Implements exec.ExitCode interface.
    42  // This type is needed as State include a sync.Mutex field which make
    43  // copying it unsafe.
    44  type StateStatus struct {
    45  	exitCode int
    46  	err      error
    47  }
    48  
    49  // ExitCode returns current exitcode for the state.
    50  func (s StateStatus) ExitCode() int {
    51  	return s.exitCode
    52  }
    53  
    54  // Err returns current error for the state. Returns nil if the container had
    55  // exited on its own.
    56  func (s StateStatus) Err() error {
    57  	return s.err
    58  }
    59  
    60  // NewState creates a default state object with a fresh channel for state changes.
    61  func NewState() *State {
    62  	return &State{
    63  		waitStop:   make(chan struct{}),
    64  		waitRemove: make(chan struct{}),
    65  	}
    66  }
    67  
    68  // String returns a human-readable description of the state
    69  func (s *State) String() string {
    70  	if s.Running {
    71  		if s.Paused {
    72  			return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
    73  		}
    74  		if s.Restarting {
    75  			return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCodeValue, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
    76  		}
    77  
    78  		if h := s.Health; h != nil {
    79  			return fmt.Sprintf("Up %s (%s)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)), h.String())
    80  		}
    81  
    82  		return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
    83  	}
    84  
    85  	if s.RemovalInProgress {
    86  		return "Removal In Progress"
    87  	}
    88  
    89  	if s.Dead {
    90  		return "Dead"
    91  	}
    92  
    93  	if s.StartedAt.IsZero() {
    94  		return "Created"
    95  	}
    96  
    97  	if s.FinishedAt.IsZero() {
    98  		return ""
    99  	}
   100  
   101  	return fmt.Sprintf("Exited (%d) %s ago", s.ExitCodeValue, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
   102  }
   103  
   104  // IsValidHealthString checks if the provided string is a valid container health status or not.
   105  func IsValidHealthString(s string) bool {
   106  	return s == types.Starting ||
   107  		s == types.Healthy ||
   108  		s == types.Unhealthy ||
   109  		s == types.NoHealthcheck
   110  }
   111  
   112  // StateString returns a single string to describe state
   113  func (s *State) StateString() string {
   114  	if s.Running {
   115  		if s.Paused {
   116  			return "paused"
   117  		}
   118  		if s.Restarting {
   119  			return "restarting"
   120  		}
   121  		return "running"
   122  	}
   123  
   124  	if s.RemovalInProgress {
   125  		return "removing"
   126  	}
   127  
   128  	if s.Dead {
   129  		return "dead"
   130  	}
   131  
   132  	if s.StartedAt.IsZero() {
   133  		return "created"
   134  	}
   135  
   136  	return "exited"
   137  }
   138  
   139  // IsValidStateString checks if the provided string is a valid container state or not.
   140  func IsValidStateString(s string) bool {
   141  	if s != "paused" &&
   142  		s != "restarting" &&
   143  		s != "removing" &&
   144  		s != "running" &&
   145  		s != "dead" &&
   146  		s != "created" &&
   147  		s != "exited" {
   148  		return false
   149  	}
   150  	return true
   151  }
   152  
   153  // WaitCondition is an enum type for different states to wait for.
   154  type WaitCondition int
   155  
   156  // Possible WaitCondition Values.
   157  //
   158  // WaitConditionNotRunning (default) is used to wait for any of the non-running
   159  // states: "created", "exited", "dead", "removing", or "removed".
   160  //
   161  // WaitConditionNextExit is used to wait for the next time the state changes
   162  // to a non-running state. If the state is currently "created" or "exited",
   163  // this would cause Wait() to block until either the container runs and exits
   164  // or is removed.
   165  //
   166  // WaitConditionRemoved is used to wait for the container to be removed.
   167  const (
   168  	WaitConditionNotRunning WaitCondition = iota
   169  	WaitConditionNextExit
   170  	WaitConditionRemoved
   171  )
   172  
   173  // Wait waits until the container is in a certain state indicated by the given
   174  // condition. A context must be used for cancelling the request, controlling
   175  // timeouts, and avoiding goroutine leaks. Wait must be called without holding
   176  // the state lock. Returns a channel from which the caller will receive the
   177  // result. If the container exited on its own, the result's Err() method will
   178  // be nil and its ExitCode() method will return the container's exit code,
   179  // otherwise, the results Err() method will return an error indicating why the
   180  // wait operation failed.
   181  func (s *State) Wait(ctx context.Context, condition WaitCondition) <-chan StateStatus {
   182  	s.Lock()
   183  	defer s.Unlock()
   184  
   185  	if condition == WaitConditionNotRunning && !s.Running {
   186  		// Buffer so we can put it in the channel now.
   187  		resultC := make(chan StateStatus, 1)
   188  
   189  		// Send the current status.
   190  		resultC <- StateStatus{
   191  			exitCode: s.ExitCode(),
   192  			err:      s.Err(),
   193  		}
   194  
   195  		return resultC
   196  	}
   197  
   198  	// If we are waiting only for removal, the waitStop channel should
   199  	// remain nil and block forever.
   200  	var waitStop chan struct{}
   201  	if condition < WaitConditionRemoved {
   202  		waitStop = s.waitStop
   203  	}
   204  
   205  	// Always wait for removal, just in case the container gets removed
   206  	// while it is still in a "created" state, in which case it is never
   207  	// actually stopped.
   208  	waitRemove := s.waitRemove
   209  
   210  	resultC := make(chan StateStatus)
   211  
   212  	go func() {
   213  		select {
   214  		case <-ctx.Done():
   215  			// Context timeout or cancellation.
   216  			resultC <- StateStatus{
   217  				exitCode: -1,
   218  				err:      ctx.Err(),
   219  			}
   220  			return
   221  		case <-waitStop:
   222  		case <-waitRemove:
   223  		}
   224  
   225  		s.Lock()
   226  		result := StateStatus{
   227  			exitCode: s.ExitCode(),
   228  			err:      s.Err(),
   229  		}
   230  		s.Unlock()
   231  
   232  		resultC <- result
   233  	}()
   234  
   235  	return resultC
   236  }
   237  
   238  // IsRunning returns whether the running flag is set. Used by Container to check whether a container is running.
   239  func (s *State) IsRunning() bool {
   240  	s.Lock()
   241  	res := s.Running
   242  	s.Unlock()
   243  	return res
   244  }
   245  
   246  // GetPID holds the process id of a container.
   247  func (s *State) GetPID() int {
   248  	s.Lock()
   249  	res := s.Pid
   250  	s.Unlock()
   251  	return res
   252  }
   253  
   254  // ExitCode returns current exitcode for the state. Take lock before if state
   255  // may be shared.
   256  func (s *State) ExitCode() int {
   257  	return s.ExitCodeValue
   258  }
   259  
   260  // SetExitCode sets current exitcode for the state. Take lock before if state
   261  // may be shared.
   262  func (s *State) SetExitCode(ec int) {
   263  	s.ExitCodeValue = ec
   264  }
   265  
   266  // SetRunning sets the state of the container to "running".
   267  func (s *State) SetRunning(pid int, initial bool) {
   268  	s.ErrorMsg = ""
   269  	s.Paused = false
   270  	s.Running = true
   271  	s.Restarting = false
   272  	if initial {
   273  		s.Paused = false
   274  	}
   275  	s.ExitCodeValue = 0
   276  	s.Pid = pid
   277  	if initial {
   278  		s.StartedAt = time.Now().UTC()
   279  	}
   280  }
   281  
   282  // SetStopped sets the container state to "stopped" without locking.
   283  func (s *State) SetStopped(exitStatus *ExitStatus) {
   284  	s.Running = false
   285  	s.Paused = false
   286  	s.Restarting = false
   287  	s.Pid = 0
   288  	if exitStatus.ExitedAt.IsZero() {
   289  		s.FinishedAt = time.Now().UTC()
   290  	} else {
   291  		s.FinishedAt = exitStatus.ExitedAt
   292  	}
   293  	s.ExitCodeValue = exitStatus.ExitCode
   294  	s.OOMKilled = exitStatus.OOMKilled
   295  	close(s.waitStop) // fire waiters for stop
   296  	s.waitStop = make(chan struct{})
   297  }
   298  
   299  // SetRestarting sets the container state to "restarting" without locking.
   300  // It also sets the container PID to 0.
   301  func (s *State) SetRestarting(exitStatus *ExitStatus) {
   302  	// we should consider the container running when it is restarting because of
   303  	// all the checks in docker around rm/stop/etc
   304  	s.Running = true
   305  	s.Restarting = true
   306  	s.Paused = false
   307  	s.Pid = 0
   308  	s.FinishedAt = time.Now().UTC()
   309  	s.ExitCodeValue = exitStatus.ExitCode
   310  	s.OOMKilled = exitStatus.OOMKilled
   311  	close(s.waitStop) // fire waiters for stop
   312  	s.waitStop = make(chan struct{})
   313  }
   314  
   315  // SetError sets the container's error state. This is useful when we want to
   316  // know the error that occurred when container transits to another state
   317  // when inspecting it
   318  func (s *State) SetError(err error) {
   319  	s.ErrorMsg = ""
   320  	if err != nil {
   321  		s.ErrorMsg = err.Error()
   322  	}
   323  }
   324  
   325  // IsPaused returns whether the container is paused or not.
   326  func (s *State) IsPaused() bool {
   327  	s.Lock()
   328  	res := s.Paused
   329  	s.Unlock()
   330  	return res
   331  }
   332  
   333  // IsRestarting returns whether the container is restarting or not.
   334  func (s *State) IsRestarting() bool {
   335  	s.Lock()
   336  	res := s.Restarting
   337  	s.Unlock()
   338  	return res
   339  }
   340  
   341  // SetRemovalInProgress sets the container state as being removed.
   342  // It returns true if the container was already in that state.
   343  func (s *State) SetRemovalInProgress() bool {
   344  	s.Lock()
   345  	defer s.Unlock()
   346  	if s.RemovalInProgress {
   347  		return true
   348  	}
   349  	s.RemovalInProgress = true
   350  	return false
   351  }
   352  
   353  // ResetRemovalInProgress makes the RemovalInProgress state to false.
   354  func (s *State) ResetRemovalInProgress() {
   355  	s.Lock()
   356  	s.RemovalInProgress = false
   357  	s.Unlock()
   358  }
   359  
   360  // IsRemovalInProgress returns whether the RemovalInProgress flag is set.
   361  // Used by Container to check whether a container is being removed.
   362  func (s *State) IsRemovalInProgress() bool {
   363  	s.Lock()
   364  	res := s.RemovalInProgress
   365  	s.Unlock()
   366  	return res
   367  }
   368  
   369  // IsDead returns whether the Dead flag is set. Used by Container to check whether a container is dead.
   370  func (s *State) IsDead() bool {
   371  	s.Lock()
   372  	res := s.Dead
   373  	s.Unlock()
   374  	return res
   375  }
   376  
   377  // SetRemoved assumes this container is already in the "dead" state and
   378  // closes the internal waitRemove channel to unblock callers waiting for a
   379  // container to be removed.
   380  func (s *State) SetRemoved() {
   381  	s.SetRemovalError(nil)
   382  }
   383  
   384  // SetRemovalError is to be called in case a container remove failed.
   385  // It sets an error and closes the internal waitRemove channel to unblock
   386  // callers waiting for the container to be removed.
   387  func (s *State) SetRemovalError(err error) {
   388  	s.SetError(err)
   389  	s.Lock()
   390  	close(s.waitRemove) // Unblock those waiting on remove.
   391  	// Recreate the channel so next ContainerWait will work
   392  	s.waitRemove = make(chan struct{})
   393  	s.Unlock()
   394  }
   395  
   396  // Err returns an error if there is one.
   397  func (s *State) Err() error {
   398  	if s.ErrorMsg != "" {
   399  		return errors.New(s.ErrorMsg)
   400  	}
   401  	return nil
   402  }