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 }