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 }