github.com/LazyboyChen7/engine@v17.12.1-ce-rc2+incompatible/container/state.go (about) 1 package container 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 "time" 8 9 "golang.org/x/net/context" 10 11 "github.com/docker/docker/api/types" 12 "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 cgroups freezer 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 or remove 33 StartedAt time.Time 34 FinishedAt time.Time 35 Health *Health 36 37 waitStop chan struct{} 38 waitRemove chan struct{} 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 with a fresh channel for state changes. 62 func NewState() *State { 63 return &State{ 64 waitStop: make(chan struct{}), 65 waitRemove: make(chan struct{}), 66 } 67 } 68 69 // String returns a human-readable description of the state 70 func (s *State) String() string { 71 if s.Running { 72 if s.Paused { 73 return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) 74 } 75 if s.Restarting { 76 return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCodeValue, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) 77 } 78 79 if h := s.Health; h != nil { 80 return fmt.Sprintf("Up %s (%s)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)), h.String()) 81 } 82 83 return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) 84 } 85 86 if s.RemovalInProgress { 87 return "Removal In Progress" 88 } 89 90 if s.Dead { 91 return "Dead" 92 } 93 94 if s.StartedAt.IsZero() { 95 return "Created" 96 } 97 98 if s.FinishedAt.IsZero() { 99 return "" 100 } 101 102 return fmt.Sprintf("Exited (%d) %s ago", s.ExitCodeValue, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) 103 } 104 105 // HealthString returns a single string to describe health status. 106 func (s *State) HealthString() string { 107 if s.Health == nil { 108 return types.NoHealthcheck 109 } 110 111 return s.Health.String() 112 } 113 114 // IsValidHealthString checks if the provided string is a valid container health status or not. 115 func IsValidHealthString(s string) bool { 116 return s == types.Starting || 117 s == types.Healthy || 118 s == types.Unhealthy || 119 s == types.NoHealthcheck 120 } 121 122 // StateString returns a single string to describe state 123 func (s *State) StateString() string { 124 if s.Running { 125 if s.Paused { 126 return "paused" 127 } 128 if s.Restarting { 129 return "restarting" 130 } 131 return "running" 132 } 133 134 if s.RemovalInProgress { 135 return "removing" 136 } 137 138 if s.Dead { 139 return "dead" 140 } 141 142 if s.StartedAt.IsZero() { 143 return "created" 144 } 145 146 return "exited" 147 } 148 149 // IsValidStateString checks if the provided string is a valid container state or not. 150 func IsValidStateString(s string) bool { 151 if s != "paused" && 152 s != "restarting" && 153 s != "removing" && 154 s != "running" && 155 s != "dead" && 156 s != "created" && 157 s != "exited" { 158 return false 159 } 160 return true 161 } 162 163 // WaitCondition is an enum type for different states to wait for. 164 type WaitCondition int 165 166 // Possible WaitCondition Values. 167 // 168 // WaitConditionNotRunning (default) is used to wait for any of the non-running 169 // states: "created", "exited", "dead", "removing", or "removed". 170 // 171 // WaitConditionNextExit is used to wait for the next time the state changes 172 // to a non-running state. If the state is currently "created" or "exited", 173 // this would cause Wait() to block until either the container runs and exits 174 // or is removed. 175 // 176 // WaitConditionRemoved is used to wait for the container to be removed. 177 const ( 178 WaitConditionNotRunning WaitCondition = iota 179 WaitConditionNextExit 180 WaitConditionRemoved 181 ) 182 183 // Wait waits until the container is in a certain state indicated by the given 184 // condition. A context must be used for cancelling the request, controlling 185 // timeouts, and avoiding goroutine leaks. Wait must be called without holding 186 // the state lock. Returns a channel from which the caller will receive the 187 // result. If the container exited on its own, the result's Err() method will 188 // be nil and its ExitCode() method will return the container's exit code, 189 // otherwise, the results Err() method will return an error indicating why the 190 // wait operation failed. 191 func (s *State) Wait(ctx context.Context, condition WaitCondition) <-chan StateStatus { 192 s.Lock() 193 defer s.Unlock() 194 195 if condition == WaitConditionNotRunning && !s.Running { 196 // Buffer so we can put it in the channel now. 197 resultC := make(chan StateStatus, 1) 198 199 // Send the current status. 200 resultC <- StateStatus{ 201 exitCode: s.ExitCode(), 202 err: s.Err(), 203 } 204 205 return resultC 206 } 207 208 // If we are waiting only for removal, the waitStop channel should 209 // remain nil and block forever. 210 var waitStop chan struct{} 211 if condition < WaitConditionRemoved { 212 waitStop = s.waitStop 213 } 214 215 // Always wait for removal, just in case the container gets removed 216 // while it is still in a "created" state, in which case it is never 217 // actually stopped. 218 waitRemove := s.waitRemove 219 220 resultC := make(chan StateStatus) 221 222 go func() { 223 select { 224 case <-ctx.Done(): 225 // Context timeout or cancellation. 226 resultC <- StateStatus{ 227 exitCode: -1, 228 err: ctx.Err(), 229 } 230 return 231 case <-waitStop: 232 case <-waitRemove: 233 } 234 235 s.Lock() 236 result := StateStatus{ 237 exitCode: s.ExitCode(), 238 err: s.Err(), 239 } 240 s.Unlock() 241 242 resultC <- result 243 }() 244 245 return resultC 246 } 247 248 // IsRunning returns whether the running flag is set. Used by Container to check whether a container is running. 249 func (s *State) IsRunning() bool { 250 s.Lock() 251 res := s.Running 252 s.Unlock() 253 return res 254 } 255 256 // GetPID holds the process id of a container. 257 func (s *State) GetPID() int { 258 s.Lock() 259 res := s.Pid 260 s.Unlock() 261 return res 262 } 263 264 // ExitCode returns current exitcode for the state. Take lock before if state 265 // may be shared. 266 func (s *State) ExitCode() int { 267 return s.ExitCodeValue 268 } 269 270 // SetExitCode sets current exitcode for the state. Take lock before if state 271 // may be shared. 272 func (s *State) SetExitCode(ec int) { 273 s.ExitCodeValue = ec 274 } 275 276 // SetRunning sets the state of the container to "running". 277 func (s *State) SetRunning(pid int, initial bool) { 278 s.ErrorMsg = "" 279 s.Paused = false 280 s.Running = true 281 s.Restarting = false 282 if initial { 283 s.Paused = false 284 } 285 s.ExitCodeValue = 0 286 s.Pid = pid 287 if initial { 288 s.StartedAt = time.Now().UTC() 289 } 290 } 291 292 // SetStopped sets the container state to "stopped" without locking. 293 func (s *State) SetStopped(exitStatus *ExitStatus) { 294 s.Running = false 295 s.Paused = false 296 s.Restarting = false 297 s.Pid = 0 298 if exitStatus.ExitedAt.IsZero() { 299 s.FinishedAt = time.Now().UTC() 300 } else { 301 s.FinishedAt = exitStatus.ExitedAt 302 } 303 s.ExitCodeValue = exitStatus.ExitCode 304 s.OOMKilled = exitStatus.OOMKilled 305 close(s.waitStop) // fire waiters for stop 306 s.waitStop = make(chan struct{}) 307 } 308 309 // SetRestarting sets the container state to "restarting" without locking. 310 // It also sets the container PID to 0. 311 func (s *State) SetRestarting(exitStatus *ExitStatus) { 312 // we should consider the container running when it is restarting because of 313 // all the checks in docker around rm/stop/etc 314 s.Running = true 315 s.Restarting = true 316 s.Paused = false 317 s.Pid = 0 318 s.FinishedAt = time.Now().UTC() 319 s.ExitCodeValue = exitStatus.ExitCode 320 s.OOMKilled = exitStatus.OOMKilled 321 close(s.waitStop) // fire waiters for stop 322 s.waitStop = make(chan struct{}) 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 // SetDead sets the container state to "dead" 380 func (s *State) SetDead() { 381 s.Lock() 382 s.Dead = true 383 s.Unlock() 384 } 385 386 // IsDead returns whether the Dead flag is set. Used by Container to check whether a container is dead. 387 func (s *State) IsDead() bool { 388 s.Lock() 389 res := s.Dead 390 s.Unlock() 391 return res 392 } 393 394 // SetRemoved assumes this container is already in the "dead" state and 395 // closes the internal waitRemove channel to unblock callers waiting for a 396 // container to be removed. 397 func (s *State) SetRemoved() { 398 s.SetRemovalError(nil) 399 } 400 401 // SetRemovalError is to be called in case a container remove failed. 402 // It sets an error and closes the internal waitRemove channel to unblock 403 // callers waiting for the container to be removed. 404 func (s *State) SetRemovalError(err error) { 405 s.SetError(err) 406 s.Lock() 407 close(s.waitRemove) // Unblock those waiting on remove. 408 // Recreate the channel so next ContainerWait will work 409 s.waitRemove = make(chan struct{}) 410 s.Unlock() 411 } 412 413 // Err returns an error if there is one. 414 func (s *State) Err() error { 415 if s.ErrorMsg != "" { 416 return errors.New(s.ErrorMsg) 417 } 418 return nil 419 }