github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/container/state.go (about)

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