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 }