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