11package container
22
33import (
4+ "errors"
45 "fmt"
56 "sync"
67 "time"
@@ -29,23 +30,25 @@ type State struct {
2930 ErrorMsg string `json:"Error"` // contains last known error when starting the container
3031 StartedAt time.Time
3132 FinishedAt time.Time
32- waitChan chan struct {}
3333 Health * Health
34+
35+ waitStop chan struct {}
36+ waitRemove chan struct {}
3437}
3538
36- // StateStatus is used to return an error type implementing both
37- // exec.ExitCode and error .
39+ // StateStatus is used to return container wait results.
40+ // Implements exec.ExitCode interface .
3841// This type is needed as State include a sync.Mutex field which make
3942// copying it unsafe.
4043type StateStatus struct {
4144 exitCode int
42- error string
45+ err error
4346}
4447
45- func newStateStatus (ec int , err string ) * StateStatus {
48+ func newStateStatus (ec int , err error ) * StateStatus {
4649 return & StateStatus {
4750 exitCode : ec ,
48- error : err ,
51+ err : err ,
4952 }
5053}
5154
@@ -54,15 +57,17 @@ func (ss *StateStatus) ExitCode() int {
5457 return ss .exitCode
5558}
5659
57- // Error returns current error for the state.
58- func (ss * StateStatus ) Error () string {
59- return ss .error
60+ // Err returns current error for the state. Returns nil if the container had
61+ // exited on its own.
62+ func (ss * StateStatus ) Err () error {
63+ return ss .err
6064}
6165
6266// NewState creates a default state object with a fresh channel for state changes.
6367func NewState () * State {
6468 return & State {
65- waitChan : make (chan struct {}),
69+ waitStop : make (chan struct {}),
70+ waitRemove : make (chan struct {}),
6671 }
6772}
6873
@@ -160,64 +165,73 @@ func IsValidStateString(s string) bool {
160165 return true
161166}
162167
163- func wait (waitChan <- chan struct {}, timeout time.Duration ) error {
164- if timeout < 0 {
165- <- waitChan
166- return nil
167- }
168- select {
169- case <- time .After (timeout ):
170- return fmt .Errorf ("Timed out: %v" , timeout )
171- case <- waitChan :
172- return nil
168+ func (s * State ) isStopped () bool {
169+ // The state is not considered "stopped" if it is either "created",
170+ // "running", or "paused".
171+ switch s .StateString () {
172+ case "created" , "running" , "paused" :
173+ return false
174+ default :
175+ return true
173176 }
174177}
175178
176- // WaitStop waits until state is stopped. If state already stopped it returns
177- // immediately. If you want wait forever you must supply negative timeout.
178- // Returns exit code, that was passed to SetStopped
179- func (s * State ) WaitStop (timeout time.Duration ) (int , error ) {
180- ctx := context .Background ()
181- if timeout >= 0 {
182- var cancel func ()
183- ctx , cancel = context .WithTimeout (ctx , timeout )
184- defer cancel ()
179+ // Wait waits until the continer is in a "stopped" state. A context can be used
180+ // for cancelling the request or controlling timeouts. If untilRemoved is true,
181+ // Wait will block until the SetRemoved() method has been called. Wait must be
182+ // called without holding the state lock. Returns a channel which can be used
183+ // to receive the result. If the container exited on its own, the result's Err() method wil be nil and
184+ // its ExitCode() method will return the conatiners exit code, otherwise, the
185+ // results Err() method will return an error indicating why the wait operation
186+ // failed.
187+ func (s * State ) Wait (ctx context.Context , untilRemoved bool ) <- chan * StateStatus {
188+ s .Lock ()
189+ defer s .Unlock ()
190+
191+ if ! untilRemoved && s .isStopped () {
192+ // We are not waiting for removal and the container is already
193+ // in a stopped state so just return the current state.
194+ result := newStateStatus (s .ExitCode (), s .Err ())
195+
196+ // Buffer so we don't block putting it in the channel.
197+ resultC := make (chan * StateStatus , 1 )
198+ resultC <- result
199+
200+ return resultC
185201 }
186- if err := s .WaitWithContext (ctx ); err != nil {
187- if status , ok := err .(* StateStatus ); ok {
188- return status .ExitCode (), nil
189- }
190- return - 1 , err
202+
203+ // The waitStop chan will remain nil if we are waiting for removal, in
204+ // which case it would block forever.
205+ var waitStop chan struct {}
206+ if ! untilRemoved {
207+ waitStop = s .waitStop
191208 }
192- return 0 , nil
193- }
194209
195- // WaitWithContext waits for the container to stop. Optional context can be
196- // passed for canceling the request.
197- func (s * State ) WaitWithContext (ctx context.Context ) error {
198- s .Lock ()
199- if ! s .Running {
200- state := newStateStatus (s .ExitCode (), s .Error ())
201- defer s .Unlock ()
202- if state .ExitCode () == 0 {
203- return nil
210+ // Always wait for removal, just in case the container gets removed
211+ // while it is still in a "created" state, in which case it is never
212+ // actually stopped.
213+ waitRemove := s .waitRemove
214+
215+ resultC := make (chan * StateStatus )
216+
217+ go func () {
218+ select {
219+ case <- ctx .Done ():
220+ // Context timeout or cancellation.
221+ resultC <- newStateStatus (- 1 , ctx .Err ())
222+ return
223+ case <- waitStop :
224+ case <- waitRemove :
204225 }
205- return state
206- }
207- waitChan := s .waitChan
208- s .Unlock ()
209- select {
210- case <- waitChan :
226+
211227 s .Lock ()
212- state := newStateStatus (s .ExitCode (), s .Error ())
228+ result := newStateStatus (s .ExitCode (), s .Err ())
213229 s .Unlock ()
214- if state .ExitCode () == 0 {
215- return nil
216- }
217- return state
218- case <- ctx .Done ():
219- return ctx .Err ()
220- }
230+
231+ resultC <- result
232+ }()
233+
234+ return resultC
221235}
222236
223237// IsRunning returns whether the running flag is set. Used by Container to check whether a container is running.
@@ -268,8 +282,8 @@ func (s *State) SetStopped(exitStatus *ExitStatus) {
268282 s .Pid = 0
269283 s .FinishedAt = time .Now ().UTC ()
270284 s .setFromExitStatus (exitStatus )
271- close (s .waitChan ) // fire waiters for stop
272- s .waitChan = make (chan struct {})
285+ close (s .waitStop ) // Fire waiters for stop
286+ s .waitStop = make (chan struct {})
273287}
274288
275289// SetRestarting sets the container state to "restarting" without locking.
@@ -282,8 +296,8 @@ func (s *State) SetRestarting(exitStatus *ExitStatus) {
282296 s .Pid = 0
283297 s .FinishedAt = time .Now ().UTC ()
284298 s .setFromExitStatus (exitStatus )
285- close (s .waitChan ) // fire waiters for stop
286- s .waitChan = make (chan struct {})
299+ close (s .waitStop ) // Fire waiters for stop
300+ s .waitStop = make (chan struct {})
287301}
288302
289303// SetError sets the container's error state. This is useful when we want to
@@ -335,6 +349,23 @@ func (s *State) SetDead() {
335349 s .Unlock ()
336350}
337351
352+ // SetRemoved assumes this container is already in the "dead" state and
353+ // closes the internal waitRemove channel to unblock callers waiting for a
354+ // container to be removed.
355+ func (s * State ) SetRemoved () {
356+ s .Lock ()
357+ close (s .waitRemove ) // Unblock those waiting on remove.
358+ s .Unlock ()
359+ }
360+
361+ // Err returns an error if there is one.
362+ func (s * State ) Err () error {
363+ if s .ErrorMsg != "" {
364+ return errors .New (s .ErrorMsg )
365+ }
366+ return nil
367+ }
368+
338369// Error returns current error for the state.
339370func (s * State ) Error () string {
340371 return s .ErrorMsg
0 commit comments